diff --git a/cmdb-api/api/lib/cmdb/ci.py b/cmdb-api/api/lib/cmdb/ci.py index d3f0875..f7e2c90 100644 --- a/cmdb-api/api/lib/cmdb/ci.py +++ b/cmdb-api/api/lib/cmdb/ci.py @@ -1274,52 +1274,83 @@ class CIRelationManager(object): def build_by_attribute(cls, ci_dict): type_id = ci_dict['_type'] child_items = CITypeRelation.get_by(parent_id=type_id, only_query=True).filter( - CITypeRelation.parent_attr_id.isnot(None)) + CITypeRelation.parent_attr_ids.isnot(None)) for item in child_items: - parent_attr = AttributeCache.get(item.parent_attr_id) - child_attr = AttributeCache.get(item.child_attr_id) - attr_value = ci_dict.get(parent_attr.name) - value_table = TableMap(attr=child_attr).table - for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join( - CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id): - CIRelationManager.add(ci_dict['_id'], child.ci_id, valid=False) + relations = None + for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids): + _relations = set() + parent_attr = AttributeCache.get(parent_attr_id) + child_attr = AttributeCache.get(child_attr_id) + attr_value = ci_dict.get(parent_attr.name) + value_table = TableMap(attr=child_attr).table + for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join( + CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id): + _relations.add((ci_dict['_id'], child.ci_id)) + if relations is None: + relations = _relations + else: + relations &= _relations + for parent_ci_id, child_ci_id in relations: + CIRelationManager.add(parent_ci_id, child_ci_id, valid=False) parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter( - CITypeRelation.child_attr_id.isnot(None)) + CITypeRelation.child_attr_ids.isnot(None)) for item in parent_items: - parent_attr = AttributeCache.get(item.parent_attr_id) - child_attr = AttributeCache.get(item.child_attr_id) - attr_value = ci_dict.get(child_attr.name) - value_table = TableMap(attr=parent_attr).table - for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join( - CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id): - CIRelationManager.add(parent.ci_id, ci_dict['_id'], valid=False) + relations = None + for parent_attr_id, child_attr_id in zip(item.parent_attr_ids, item.child_attr_ids): + _relations = set() + parent_attr = AttributeCache.get(parent_attr_id) + child_attr = AttributeCache.get(child_attr_id) + attr_value = ci_dict.get(child_attr.name) + value_table = TableMap(attr=parent_attr).table + for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join( + CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id): + _relations.add((parent.ci_id, ci_dict['_id'])) + if relations is None: + relations = _relations + else: + relations &= _relations + for parent_ci_id, child_ci_id in relations: + CIRelationManager.add(parent_ci_id, child_ci_id, valid=False) @classmethod def rebuild_all_by_attribute(cls, ci_type_relation): - parent_attr = AttributeCache.get(ci_type_relation['parent_attr_id']) - child_attr = AttributeCache.get(ci_type_relation['child_attr_id']) - if not parent_attr or not child_attr: - return + relations = None + for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [], + ci_type_relation['child_attr_ids'] or []): - parent_value_table = TableMap(attr=parent_attr).table - child_value_table = TableMap(attr=child_attr).table + _relations = set() + parent_attr = AttributeCache.get(parent_attr_id) + child_attr = AttributeCache.get(child_attr_id) + if not parent_attr or not child_attr: + continue - parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join( - CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id']) - child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join( - CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id']) + parent_value_table = TableMap(attr=parent_attr).table + child_value_table = TableMap(attr=child_attr).table - child_value2ci_ids = {} - for child in child_values: - child_value2ci_ids.setdefault(child.value, []).append(child.ci_id) + parent_values = parent_value_table.get_by(attr_id=parent_attr.id, only_query=True).join( + CI, CI.id == parent_value_table.ci_id).filter(CI.type_id == ci_type_relation['parent_id']) + child_values = child_value_table.get_by(attr_id=child_attr.id, only_query=True).join( + CI, CI.id == child_value_table.ci_id).filter(CI.type_id == ci_type_relation['child_id']) - for parent in parent_values: - for child_ci_id in child_value2ci_ids.get(parent.value, []): - try: - cls.add(parent.ci_id, child_ci_id, valid=False) - except: - pass + child_value2ci_ids = {} + for child in child_values: + child_value2ci_ids.setdefault(child.value, []).append(child.ci_id) + + for parent in parent_values: + for child_ci_id in child_value2ci_ids.get(parent.value, []): + _relations.add((parent.ci_id, child_ci_id)) + + if relations is None: + relations = _relations + else: + relations &= _relations + + for parent_ci_id, child_ci_id in (relations or []): + try: + cls.add(parent_ci_id, child_ci_id, valid=False) + except: + pass class CITriggerManager(object): diff --git a/cmdb-api/api/lib/cmdb/ci_type.py b/cmdb-api/api/lib/cmdb/ci_type.py index a0cf7d7..e3d3055 100644 --- a/cmdb-api/api/lib/cmdb/ci_type.py +++ b/cmdb-api/api/lib/cmdb/ci_type.py @@ -35,6 +35,7 @@ from api.lib.perm.acl.acl import validate_permission from api.models.cmdb import Attribute from api.models.cmdb import AutoDiscoveryCI from api.models.cmdb import AutoDiscoveryCIType +from api.models.cmdb import AutoDiscoveryCITypeRelation from api.models.cmdb import CI from api.models.cmdb import CIFilterPerms from api.models.cmdb import CIType @@ -49,12 +50,12 @@ from api.models.cmdb import CITypeTrigger from api.models.cmdb import CITypeUniqueConstraint from api.models.cmdb import CustomDashboard from api.models.cmdb import PreferenceCITypeOrder -from api.models.cmdb import TopologyView from api.models.cmdb import PreferenceRelationView from api.models.cmdb import PreferenceSearchOption from api.models.cmdb import PreferenceShowAttributes from api.models.cmdb import PreferenceTreeView from api.models.cmdb import RelationType +from api.models.cmdb import TopologyView class CITypeManager(object): @@ -251,6 +252,12 @@ class CITypeManager(object): for item in AutoDiscoveryCI.get_by(type_id=type_id, to_dict=False): item.delete(commit=False) + for item in AutoDiscoveryCITypeRelation.get_by(ad_type_id=type_id, to_dict=False): + item.delete(commit=False) + + for item in AutoDiscoveryCITypeRelation.get_by(peer_type_id=type_id, to_dict=False): + item.delete(commit=False) + for item in CITypeInheritance.get_by(parent_id=type_id, to_dict=False): item.delete(commit=False) @@ -686,9 +693,19 @@ class CITypeAttributeManager(object): item = CITypeTrigger.get_by(type_id=_type_id, attr_id=attr_id, to_dict=False, first=True) item and item.soft_delete(commit=False) - for item in (CITypeRelation.get_by(parent_id=type_id, parent_attr_id=attr_id, to_dict=False) + - CITypeRelation.get_by(child_id=type_id, child_attr_id=attr_id, to_dict=False)): - item.soft_delete(commit=False) + for item in (CITypeRelation.get_by(parent_id=type_id, to_dict=False) + + CITypeRelation.get_by(child_id=type_id, to_dict=False)): + if item.parent_id == type_id and attr_id in (item.parent_attr_ids or []): + item_dict = item.to_dict() + pop_idx = item.parent_attr_ids.index(attr_id) + elif item.child_id == type_id and attr_id in (item.child_attr_ids or []): + item_dict = item.to_dict() + pop_idx = item.child_attr_ids.index(attr_id) + else: + continue + item.update(parent_attr_ids=item_dict['parent_attr_ids'].pop(pop_idx), + child_attr_ids=item_dict['child_attr_ids'].pop(pop_idx), + commit=False) db.session.commit() @@ -757,10 +774,12 @@ class CITypeRelationManager(object): res[idx] = _item res[idx]['parent'] = item.parent.to_dict() if item.parent_id not in type2attributes: - type2attributes[item.parent_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.parent_id)] + type2attributes[item.parent_id] = [i[1].to_dict() for i in + CITypeAttributeManager.get_all_attributes(item.parent_id)] res[idx]['child'] = item.child.to_dict() if item.child_id not in type2attributes: - type2attributes[item.child_id] = [i[1].to_dict() for i in CITypeAttributesCache.get2(item.child_id)] + type2attributes[item.child_id] = [i[1].to_dict() for i in + CITypeAttributeManager.get_all_attributes(item.child_id)] res[idx]['relation_type'] = item.relation_type.to_dict() return res, type2attributes @@ -795,8 +814,8 @@ class CITypeRelationManager(object): ci_type_dict["relation_type"] = relation_inst.relation_type.name ci_type_dict["constraint"] = relation_inst.constraint - ci_type_dict["parent_attr_id"] = relation_inst.parent_attr_id - ci_type_dict["child_attr_id"] = relation_inst.child_attr_id + ci_type_dict["parent_attr_ids"] = relation_inst.parent_attr_ids + ci_type_dict["child_attr_ids"] = relation_inst.child_attr_ids return ci_type_dict @@ -906,7 +925,7 @@ class CITypeRelationManager(object): @classmethod def add(cls, parent, child, relation_type_id, constraint=ConstraintEnum.One2Many, - parent_attr_id=None, child_attr_id=None): + parent_attr_ids=None, child_attr_ids=None): p = CITypeManager.check_is_existed(parent) c = CITypeManager.check_is_existed(child) @@ -921,21 +940,22 @@ class CITypeRelationManager(object): current_app.logger.warning(str(e)) return abort(400, ErrFormat.circular_dependency_error) - old_parent_attr_id = None + old_parent_attr_ids, old_child_attr_ids = None, None existed = cls._get(p.id, c.id) if existed is not None: - old_parent_attr_id = existed.parent_attr_id + old_parent_attr_ids = copy.deepcopy(existed.parent_attr_ids) + old_child_attr_ids = copy.deepcopy(existed.child_attr_ids) existed = existed.update(relation_type_id=relation_type_id, constraint=constraint, - parent_attr_id=parent_attr_id, - child_attr_id=child_attr_id, + parent_attr_ids=parent_attr_ids, + child_attr_ids=child_attr_ids, filter_none=False) else: existed = CITypeRelation.create(parent_id=p.id, child_id=c.id, relation_type_id=relation_type_id, - parent_attr_id=parent_attr_id, - child_attr_id=child_attr_id, + parent_attr_ids=parent_attr_ids, + child_attr_ids=child_attr_ids, constraint=constraint) if current_app.config.get("USE_ACL"): @@ -949,10 +969,10 @@ class CITypeRelationManager(object): current_user.username, ResourceTypeEnum.CI_TYPE_RELATION) - if parent_attr_id and parent_attr_id != old_parent_attr_id: - if parent_attr_id and parent_attr_id != existed.parent_attr_id: - from api.tasks.cmdb import rebuild_relation_for_attribute_changed - rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict())) + if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or + (child_attr_ids and child_attr_ids != old_child_attr_ids)): + from api.tasks.cmdb import rebuild_relation_for_attribute_changed + rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(),)) CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id, change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id)) @@ -1246,8 +1266,8 @@ class CITypeTemplateManager(object): id2obj_dicts[added_id].get('child_id'), id2obj_dicts[added_id].get('relation_type_id'), id2obj_dicts[added_id].get('constraint'), - id2obj_dicts[added_id].get('parent_attr_id'), - id2obj_dicts[added_id].get('child_attr_id'), + id2obj_dicts[added_id].get('parent_attr_ids'), + id2obj_dicts[added_id].get('child_attr_ids'), ) else: obj = cls.create(flush=True, **id2obj_dicts[added_id]) diff --git a/cmdb-api/api/models/cmdb.py b/cmdb-api/api/models/cmdb.py index 2289ef3..64a69aa 100644 --- a/cmdb-api/api/models/cmdb.py +++ b/cmdb-api/api/models/cmdb.py @@ -79,8 +79,11 @@ class CITypeRelation(Model): relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False) constraint = db.Column(db.Enum(*ConstraintEnum.all()), default=ConstraintEnum.One2Many) - parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) - child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) + parent_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated + child_attr_id = db.Column(db.Integer, db.ForeignKey("c_attributes.id")) # CMDB > 2.4.5: deprecated + + parent_attr_ids = db.Column(db.JSON) # [parent_attr_id, ] + child_attr_ids = db.Column(db.JSON) # [child_attr_id, ] parent = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.parent_id") child = db.relationship("CIType", primaryjoin="CIType.id==CITypeRelation.child_id") diff --git a/cmdb-api/api/views/cmdb/ci_type_relation.py b/cmdb-api/api/views/cmdb/ci_type_relation.py index eeae0a5..cac6371 100644 --- a/cmdb-api/api/views/cmdb/ci_type_relation.py +++ b/cmdb-api/api/views/cmdb/ci_type_relation.py @@ -57,10 +57,10 @@ class CITypeRelationView(APIView): def post(self, parent_id, child_id): relation_type_id = request.values.get("relation_type_id") constraint = request.values.get("constraint") - parent_attr_id = request.values.get("parent_attr_id") - child_attr_id = request.values.get("child_attr_id") + parent_attr_ids = request.values.get("parent_attr_ids") + child_attr_ids = request.values.get("child_attr_ids") ctr_id = CITypeRelationManager.add(parent_id, child_id, relation_type_id, constraint, - parent_attr_id, child_attr_id) + parent_attr_ids, child_attr_ids) return self.jsonify(ctr_id=ctr_id)