feat(api): multi-value attribute handling in relation building

This commit is contained in:
pycook
2025-08-15 20:32:22 +08:00
parent 56a310c667
commit 0b1dfa4538
2 changed files with 84 additions and 19 deletions

View File

@@ -360,16 +360,28 @@ class CIManager(object):
_sync=False, _sync=False,
**ci_dict): **ci_dict):
""" """
add ci Create a new Configuration Item (CI) or update existing based on unique constraints.
:param ci_type_name:
:param exist_policy: replace or reject or need Handles complete CI creation workflow including validation, uniqueness checks,
:param _no_attribute_policy: ignore or reject password encryption, computed attributes, relationship creation, and caching.
:param is_auto_discovery: default is False
:param _is_admin: default is False Args:
:param ticket_id: ci_type_name (str): Name of the CI type to create
:param _sync: exist_policy (ExistPolicy): How to handle existing CIs (REPLACE/REJECT/NEED)
:param ci_dict: _no_attribute_policy (ExistPolicy): How to handle unknown attributes (IGNORE/REJECT)
:return: is_auto_discovery (bool): Whether CI is created by auto-discovery process
_is_admin (bool): Whether to skip permission checks
ticket_id (int, optional): Associated ticket ID for audit trail
_sync (bool): Whether to execute cache/relation tasks synchronously
**ci_dict: CI attribute values as key-value pairs
Returns:
int: ID of the created or updated CI
Raises:
400: If unique constraints violated, required attributes missing, or validation fails
403: If user lacks permissions for restricted attributes
404: If CI type not found or referenced CI not exists
""" """
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci_type = CITypeManager.check_is_existed(ci_type_name) ci_type = CITypeManager.check_is_existed(ci_type_name)
@@ -512,6 +524,24 @@ class CIManager(object):
return ci.id return ci.id
def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict): def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict):
"""
Update an existing Configuration Item with new attribute values.
Performs comprehensive CI update including validation, constraint checks,
password handling, computed attributes processing, and change tracking.
Args:
ci_id (int): ID of the CI to update
_is_admin (bool): Whether to skip permission checks
ticket_id (int, optional): Associated ticket ID for audit trail
_sync (bool): Whether to execute cache/relation tasks synchronously
**ci_dict: CI attribute values to update as key-value pairs
Raises:
400: If unique constraints violated or validation fails
403: If user lacks permissions for restricted attributes
404: If CI not found
"""
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
ci = self.confirm_ci_existed(ci_id) ci = self.confirm_ci_existed(ci_id)
ci_type = ci.ci_type ci_type = ci.ci_type
@@ -1420,14 +1450,31 @@ class CIRelationManager(object):
parent_attr = AttributeCache.get(parent_attr_id) parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id) child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(parent_attr.name) attr_value = ci_dict.get(parent_attr.name)
if attr_value != 0 and not attr_value:
continue
value_table = TableMap(attr=child_attr).table 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( attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
_relations.add((ci_dict['_id'], child.ci_id)) matching_cis = value_table.get_by(
attr_id=child_attr.id,
only_query=True
).join(
CI, CI.id == value_table.ci_id
).filter(
CI.type_id == item.child_id,
value_table.value.in_(attr_value_list)
).all()
for ci in matching_cis:
_relations.add((ci_dict['_id'], ci.ci_id))
if relations is None: if relations is None:
relations = _relations relations = _relations
else: else:
relations &= _relations if item.constraint == ConstraintEnum.Many2Many:
relations |= _relations
else:
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES, cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
first_ci_id=ci_dict['_id'], first_ci_id=ci_dict['_id'],
@@ -1447,14 +1494,31 @@ class CIRelationManager(object):
parent_attr = AttributeCache.get(parent_attr_id) parent_attr = AttributeCache.get(parent_attr_id)
child_attr = AttributeCache.get(child_attr_id) child_attr = AttributeCache.get(child_attr_id)
attr_value = ci_dict.get(child_attr.name) attr_value = ci_dict.get(child_attr.name)
if attr_value != 0 and not attr_value:
continue
value_table = TableMap(attr=parent_attr).table 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( attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
_relations.add((parent.ci_id, ci_dict['_id'])) matching_cis = value_table.get_by(
attr_id=parent_attr.id,
only_query=True
).join(
CI, CI.id == value_table.ci_id
).filter(
CI.type_id == item.parent_id,
value_table.value.in_(attr_value_list)
).all()
for ci in matching_cis:
_relations.add((ci.ci_id, ci_dict['_id']))
if relations is None: if relations is None:
relations = _relations relations = _relations
else: else:
relations &= _relations if item.constraint == ConstraintEnum.Many2Many:
relations |= _relations
else:
relations &= _relations
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES, cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
second_ci_id=ci_dict['_id'], second_ci_id=ci_dict['_id'],

View File

@@ -92,7 +92,8 @@ class IpAddressManager(object):
else: else:
return abort(400, ErrFormat.ipam_address_model_not_found) return abort(400, ErrFormat.ipam_address_model_not_found)
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id), expire=10)): with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id),
expire=60, auto_renewal=True)):
cis = self._get_cis(subnet_id, ips) cis = self._get_cis(subnet_id, ips)
ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis} ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis}