perf(api): relationships built by attribute values (#572)

This commit is contained in:
pycook 2024-07-08 11:42:18 +08:00 committed by GitHub
parent ed46a1e1c1
commit ff2b8ea198
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 149 additions and 58 deletions

View File

@ -23,6 +23,7 @@ from api.lib.cmdb.ci_type import CITypeGroupManager
from api.lib.cmdb.const import AutoDiscoveryType from api.lib.cmdb.const import AutoDiscoveryType
from api.lib.cmdb.const import CMDB_QUEUE from api.lib.cmdb.const import CMDB_QUEUE
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.custom_dashboard import SystemConfigManager from api.lib.cmdb.custom_dashboard import SystemConfigManager
from api.lib.cmdb.resp_format import ErrFormat from api.lib.cmdb.resp_format import ErrFormat
@ -246,6 +247,9 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
rules = cls.cls.get_by(to_dict=True) rules = cls.cls.get_by(to_dict=True)
for rule in rules: for rule in rules:
if not rule['enabled']:
continue
if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'): if isinstance(rule.get("extra_option"), dict) and rule['extra_option'].get('secret'):
if not (current_user.username in PRIVILEGED_USERS or current_user.uid == rule['uid']): if not (current_user.username in PRIVILEGED_USERS or current_user.uid == rule['uid']):
rule['extra_option'].pop('secret', None) rule['extra_option'].pop('secret', None)
@ -274,7 +278,7 @@ class AutoDiscoveryCITypeCRUD(DBMixin):
break break
elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']: elif not rule['agent_id'] and not rule['query_expr'] and rule['adr_id']:
try: try:
if not int(oneagent_id, 16): # excludes master if not int(oneagent_id, 16): # excludes master
continue continue
except Exception: except Exception:
pass pass
@ -717,10 +721,15 @@ class AutoDiscoveryCICRUD(DBMixin):
for relation_ci in response: for relation_ci in response:
relation_ci_id = relation_ci['_id'] relation_ci_id = relation_ci['_id']
try: try:
CIRelationManager.add(ci_id, relation_ci_id, valid=False) CIRelationManager.add(ci_id, relation_ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except: except:
try: try:
CIRelationManager.add(relation_ci_id, ci_id, valid=False) CIRelationManager.add(relation_ci_id, ci_id,
valid=False,
source=RelationSourceEnum.AUTO_DISCOVERY)
except: except:
pass pass

View File

@ -41,7 +41,7 @@ CLOUD_MAP = {
"items": ["云服务器 ECS", "云服务器 Disk"], "items": ["云服务器 ECS", "云服务器 Disk"],
"map": { "map": {
"云服务器 ECS": {"template": "templates/aliyun_ecs.json", "mapping": "ecs"}, "云服务器 ECS": {"template": "templates/aliyun_ecs.json", "mapping": "ecs"},
"云服务器 Disk": {"template": "templates/aliyun_ecs_disk2.json", "mapping": "evs"}, "云服务器 Disk": {"template": "templates/aliyun_ecs_disk.json", "mapping": "evs"},
}, },
"collect_key_map": { "collect_key_map": {
"云服务器 ECS": "ali.ecs", "云服务器 ECS": "ali.ecs",

View File

@ -4,12 +4,12 @@
import copy import copy
import datetime import datetime
import json import json
import threading
import redis_lock import redis_lock
import threading
from flask import abort from flask import abort
from flask import current_app from flask import current_app
from flask_login import current_user from flask_login import current_user
from sqlalchemy.orm import aliased
from werkzeug.exceptions import BadRequest from werkzeug.exceptions import BadRequest
from api.extensions import db from api.extensions import db
@ -28,6 +28,7 @@ from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import PermEnum from api.lib.cmdb.const import PermEnum
from api.lib.cmdb.const import REDIS_PREFIX_CI from api.lib.cmdb.const import REDIS_PREFIX_CI
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.const import ResourceTypeEnum from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RetKey from api.lib.cmdb.const import RetKey
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
@ -1133,7 +1134,14 @@ class CIRelationManager(object):
return abort(400, ErrFormat.relation_constraint.format("1-N")) return abort(400, ErrFormat.relation_constraint.format("1-N"))
@classmethod @classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None, ancestor_ids=None, valid=True): def add(cls, first_ci_id, second_ci_id,
more=None,
relation_type_id=None,
ancestor_ids=None,
valid=True,
apply_async=True,
source=None,
uid=None):
first_ci = CIManager.confirm_ci_existed(first_ci_id) first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id) second_ci = CIManager.confirm_ci_existed(second_ci_id)
@ -1145,9 +1153,10 @@ class CIRelationManager(object):
first=True) first=True)
if existed is not None: if existed is not None:
if existed.relation_type_id != relation_type_id and relation_type_id is not None: if existed.relation_type_id != relation_type_id and relation_type_id is not None:
existed.update(relation_type_id=relation_type_id) source = existed.source or source
existed.update(relation_type_id=relation_type_id, source=source)
CIRelationHistoryManager().add(existed, OperateType.UPDATE) CIRelationHistoryManager().add(existed, OperateType.UPDATE, uid=uid)
else: else:
if relation_type_id is None: if relation_type_id is None:
type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id, type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id,
@ -1177,11 +1186,13 @@ class CIRelationManager(object):
existed = CIRelation.create(first_ci_id=first_ci_id, existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id, second_ci_id=second_ci_id,
relation_type_id=relation_type_id, relation_type_id=relation_type_id,
ancestor_ids=ancestor_ids) ancestor_ids=ancestor_ids,
source=source)
CIRelationHistoryManager().add(existed, OperateType.ADD) CIRelationHistoryManager().add(existed, OperateType.ADD, uid=uid)
if apply_async:
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE) ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
else:
ci_relation_cache(first_ci_id, second_ci_id, ancestor_ids)
if more is not None: if more is not None:
existed.upadte(more=more) existed.upadte(more=more)
@ -1189,7 +1200,7 @@ class CIRelationManager(object):
return existed.id return existed.id
@staticmethod @staticmethod
def delete(cr_id): def delete(cr_id, apply_async=True):
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id))) cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))
if current_app.config.get('USE_ACL') and current_user.username != 'worker': if current_app.config.get('USE_ACL') and current_user.username != 'worker':
@ -1205,8 +1216,12 @@ class CIRelationManager(object):
his_manager = CIRelationHistoryManager() his_manager = CIRelationHistoryManager()
his_manager.add(cr, operate_type=OperateType.DELETE) his_manager.add(cr, operate_type=OperateType.DELETE)
ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE) if apply_async:
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE) ci_relation_delete.apply_async(args=(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(cr.second_ci_id,), queue=CMDB_QUEUE)
else:
ci_relation_delete(cr.first_ci_id, cr.second_ci_id, cr.ancestor_ids)
delete_id_filter(cr.second_ci_id)
return cr_id return cr_id
@ -1221,23 +1236,23 @@ class CIRelationManager(object):
if cr is not None: if cr is not None:
cls.delete(cr.id) cls.delete(cr.id)
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE) # ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE) # delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
return cr return cr
@classmethod @classmethod
def delete_3(cls, first_ci_id, second_ci_id): def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
cr = CIRelation.get_by(first_ci_id=first_ci_id, cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id, second_ci_id=second_ci_id,
to_dict=False, to_dict=False,
first=True) first=True)
if cr is not None: if cr is not None:
ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE) # ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE) # delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)
cls.delete(cr.id) cls.delete(cr.id, apply_async=apply_async)
return cr return cr
@ -1276,6 +1291,27 @@ class CIRelationManager(object):
for ci_id in ci_ids: for ci_id in ci_ids:
cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids) cls.delete_2(parent_id, ci_id, ancestor_ids=ancestor_ids)
@classmethod
def delete_relations_by_source(cls, source,
first_ci_id=None, second_ci_type_id=None,
second_ci_id=None, first_ci_type_id=None,
added=None):
existed = []
if first_ci_id is not None and second_ci_type_id is not None:
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
source=source, first_ci_id=first_ci_id, only_query=True).join(
CI, CIRelation.second_ci_id == CI.id).filter(CI.type_id == second_ci_type_id)]
if second_ci_id is not None and first_ci_type_id is not None:
existed = [(i.first_ci_id, i.second_ci_id) for i in CIRelation.get_by(
source=source, second_ci_id=second_ci_id, only_query=True).join(
CI, CIRelation.first_ci_id == CI.id).filter(CI.type_id == first_ci_type_id)]
deleted = set(existed) - set(added or [])
for first, second in deleted:
cls.delete_3(first, second, apply_async=False)
@classmethod @classmethod
def build_by_attribute(cls, ci_dict): def build_by_attribute(cls, ci_dict):
type_id = ci_dict['_type'] type_id = ci_dict['_type']
@ -1296,8 +1332,15 @@ class CIRelationManager(object):
relations = _relations relations = _relations
else: else:
relations &= _relations relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
first_ci_id=ci_dict['_id'],
second_ci_type_id=item.child_id,
added=relations)
for parent_ci_id, child_ci_id in (relations or []): for parent_ci_id, child_ci_id in (relations or []):
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False) cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter( parent_items = CITypeRelation.get_by(child_id=type_id, only_query=True).filter(
CITypeRelation.child_attr_ids.isnot(None)) CITypeRelation.child_attr_ids.isnot(None))
@ -1316,11 +1359,18 @@ class CIRelationManager(object):
relations = _relations relations = _relations
else: else:
relations &= _relations relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
second_ci_id=ci_dict['_id'],
first_ci_type_id=item.parent_id,
added=relations)
for parent_ci_id, child_ci_id in (relations or []): for parent_ci_id, child_ci_id in (relations or []):
CIRelationManager.add(parent_ci_id, child_ci_id, valid=False) cls.add(parent_ci_id, child_ci_id,
valid=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES)
@classmethod @classmethod
def rebuild_all_by_attribute(cls, ci_type_relation): def rebuild_all_by_attribute(cls, ci_type_relation, uid):
relations = None relations = None
for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [], for parent_attr_id, child_attr_id in zip(ci_type_relation['parent_attr_ids'] or [],
ci_type_relation['child_attr_ids'] or []): ci_type_relation['child_attr_ids'] or []):
@ -1352,11 +1402,29 @@ class CIRelationManager(object):
else: else:
relations &= _relations relations &= _relations
t1 = aliased(CI)
t2 = aliased(CI)
query = db.session.query(CIRelation).join(t1, t1.id == CIRelation.first_ci_id).join(
t2, t2.id == CIRelation.second_ci_id).filter(t1.type_id == ci_type_relation['parent_id']).filter(
t2.type_id == ci_type_relation['child_id'])
for i in query:
db.session.delete(i)
ci_relation_delete(i.first_ci_id, i.second_ci_id, i.ancestor_ids)
try:
db.session.commit()
except Exception as e:
current_app.logger.error(e)
db.session.rollback()
for parent_ci_id, child_ci_id in (relations or []): for parent_ci_id, child_ci_id in (relations or []):
try: try:
cls.add(parent_ci_id, child_ci_id, valid=False) cls.add(parent_ci_id, child_ci_id,
except: valid=False,
pass apply_async=False,
source=RelationSourceEnum.ATTRIBUTE_VALUES,
uid=uid)
except Exception as e:
current_app.logger.error(e)
class CITriggerManager(object): class CITriggerManager(object):

View File

@ -993,7 +993,7 @@ class CITypeRelationManager(object):
if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or if ((parent_attr_ids and parent_attr_ids != old_parent_attr_ids) or
(child_attr_ids and child_attr_ids != old_child_attr_ids)): (child_attr_ids and child_attr_ids != old_child_attr_ids)):
from api.tasks.cmdb import rebuild_relation_for_attribute_changed from api.tasks.cmdb import rebuild_relation_for_attribute_changed
rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(),)) rebuild_relation_for_attribute_changed.apply_async(args=(existed.to_dict(), current_user.uid))
CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id, CITypeHistoryManager.add(CITypeOperateType.ADD_RELATION, p.id,
change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id)) change=dict(parent=p.to_dict(), child=c.to_dict(), relation_type_id=relation_type_id))

View File

@ -41,23 +41,23 @@ class OperateType(BaseEnum):
class CITypeOperateType(BaseEnum): class CITypeOperateType(BaseEnum):
ADD = "0" # 新增模型 ADD = "0" # add CIType
UPDATE = "1" # 修改模型 UPDATE = "1" # update CIType
DELETE = "2" # 删除模型 DELETE = "2" # delete CIType
ADD_ATTRIBUTE = "3" # 新增属性 ADD_ATTRIBUTE = "3"
UPDATE_ATTRIBUTE = "4" # 修改属性 UPDATE_ATTRIBUTE = "4"
DELETE_ATTRIBUTE = "5" # 删除属性 DELETE_ATTRIBUTE = "5"
ADD_TRIGGER = "6" # 新增触发器 ADD_TRIGGER = "6"
UPDATE_TRIGGER = "7" # 修改触发器 UPDATE_TRIGGER = "7"
DELETE_TRIGGER = "8" # 删除触发器 DELETE_TRIGGER = "8"
ADD_UNIQUE_CONSTRAINT = "9" # 新增联合唯一 ADD_UNIQUE_CONSTRAINT = "9"
UPDATE_UNIQUE_CONSTRAINT = "10" # 修改联合唯一 UPDATE_UNIQUE_CONSTRAINT = "10"
DELETE_UNIQUE_CONSTRAINT = "11" # 删除联合唯一 DELETE_UNIQUE_CONSTRAINT = "11"
ADD_RELATION = "12" # 新增关系 ADD_RELATION = "12"
DELETE_RELATION = "13" # 删除关系 DELETE_RELATION = "13"
ADD_RECONCILIATION = "14" # 新增数据合规 ADD_RECONCILIATION = "14"
UPDATE_RECONCILIATION = "15" # 修改数据合规 UPDATE_RECONCILIATION = "15"
DELETE_RECONCILIATION = "16" # 删除数据合规 DELETE_RECONCILIATION = "16"
class RetKey(BaseEnum): class RetKey(BaseEnum):
@ -93,7 +93,7 @@ class RoleEnum(BaseEnum):
class AutoDiscoveryType(BaseEnum): class AutoDiscoveryType(BaseEnum):
AGENT = "agent" AGENT = "agent"
SNMP = "snmp" SNMP = "snmp"
HTTP = "http" # cloud HTTP = "http" # cloud
COMPONENTS = "components" COMPONENTS = "components"
@ -108,6 +108,10 @@ class ExecuteStatusEnum(BaseEnum):
FAILED = '1' FAILED = '1'
RUNNING = '2' RUNNING = '2'
class RelationSourceEnum(BaseEnum):
ATTRIBUTE_VALUES = "0"
AUTO_DISCOVERY = "1"
CMDB_QUEUE = "one_cmdb_async" CMDB_QUEUE = "one_cmdb_async"
REDIS_PREFIX_CI = "ONE_CMDB" REDIS_PREFIX_CI = "ONE_CMDB"

View File

@ -232,8 +232,8 @@ class AttributeHistoryManger(object):
class CIRelationHistoryManager(object): class CIRelationHistoryManager(object):
@staticmethod @staticmethod
def add(rel_obj, operate_type=OperateType.ADD): def add(rel_obj, operate_type=OperateType.ADD, uid=None):
record = OperationRecord.create(uid=current_user.uid) record = OperationRecord.create(uid=uid or current_user.uid)
CIRelationHistory.create(relation_id=rel_obj.id, CIRelationHistory.create(relation_id=rel_obj.id,
record_id=record.id, record_id=record.id,

View File

@ -28,10 +28,7 @@ def string2int(x):
return v return v
def str2datetime(x): def str2date(x):
x = x.replace('T', ' ')
x = x.replace('Z', '')
try: try:
return datetime.datetime.strptime(x, "%Y-%m-%d").date() return datetime.datetime.strptime(x, "%Y-%m-%d").date()
@ -43,9 +40,21 @@ def str2datetime(x):
except ValueError: except ValueError:
pass pass
def str2datetime(x):
x = x.replace('T', ' ')
x = x.replace('Z', '')
try:
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")
except ValueError:
pass
return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M") return datetime.datetime.strptime(x, "%Y-%m-%d %H:%M")
class ValueTypeMap(object): class ValueTypeMap(object):
deserialize = { deserialize = {
ValueTypeEnum.INT: string2int, ValueTypeEnum.INT: string2int,
@ -53,7 +62,7 @@ class ValueTypeMap(object):
ValueTypeEnum.TEXT: lambda x: x, ValueTypeEnum.TEXT: lambda x: x,
ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0], ValueTypeEnum.TIME: lambda x: TIME_RE.findall(x)[0],
ValueTypeEnum.DATETIME: str2datetime, ValueTypeEnum.DATETIME: str2datetime,
ValueTypeEnum.DATE: str2datetime, ValueTypeEnum.DATE: str2date,
ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x, ValueTypeEnum.JSON: lambda x: json.loads(x) if isinstance(x, six.string_types) and x else x,
} }

View File

@ -2,7 +2,6 @@
import datetime import datetime
from sqlalchemy.dialects.mysql import DOUBLE from sqlalchemy.dialects.mysql import DOUBLE
from api.extensions import db from api.extensions import db
@ -11,6 +10,7 @@ from api.lib.cmdb.const import CIStatusEnum
from api.lib.cmdb.const import CITypeOperateType from api.lib.cmdb.const import CITypeOperateType
from api.lib.cmdb.const import ConstraintEnum from api.lib.cmdb.const import ConstraintEnum
from api.lib.cmdb.const import OperateType from api.lib.cmdb.const import OperateType
from api.lib.cmdb.const import RelationSourceEnum
from api.lib.cmdb.const import ValueTypeEnum from api.lib.cmdb.const import ValueTypeEnum
from api.lib.database import Model from api.lib.database import Model
from api.lib.database import Model2 from api.lib.database import Model2
@ -260,6 +260,7 @@ class CIRelation(Model):
second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False) second_ci_id = db.Column(db.Integer, db.ForeignKey("c_cis.id"), nullable=False)
relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False) relation_type_id = db.Column(db.Integer, db.ForeignKey("c_relation_types.id"), nullable=False)
more = db.Column(db.Integer, db.ForeignKey("c_cis.id")) more = db.Column(db.Integer, db.ForeignKey("c_cis.id"))
source = db.Column(db.Enum(*RelationSourceEnum.all()), name="source")
ancestor_ids = db.Column(db.String(128), index=True) ancestor_ids = db.Column(db.String(128), index=True)

View File

@ -58,10 +58,10 @@ def ci_cache(ci_id, operate_type, record_id):
@celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE) @celery.task(name="cmdb.rebuild_relation_for_attribute_changed", queue=CMDB_QUEUE)
@reconnect_db @reconnect_db
def rebuild_relation_for_attribute_changed(ci_type_relation): def rebuild_relation_for_attribute_changed(ci_type_relation, uid):
from api.lib.cmdb.ci import CIRelationManager from api.lib.cmdb.ci import CIRelationManager
CIRelationManager.rebuild_all_by_attribute(ci_type_relation) CIRelationManager.rebuild_all_by_attribute(ci_type_relation, uid)
@celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE) @celery.task(name="cmdb.batch_ci_cache", queue=CMDB_QUEUE)