Skip to content

Commit 0b1dfa4

Browse files
committed
feat(api): multi-value attribute handling in relation building
1 parent 56a310c commit 0b1dfa4

2 files changed

Lines changed: 84 additions & 19 deletions

File tree

cmdb-api/api/lib/cmdb/ci.py

Lines changed: 82 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,28 @@ def add(cls, ci_type_name,
360360
_sync=False,
361361
**ci_dict):
362362
"""
363-
add ci
364-
:param ci_type_name:
365-
:param exist_policy: replace or reject or need
366-
:param _no_attribute_policy: ignore or reject
367-
:param is_auto_discovery: default is False
368-
:param _is_admin: default is False
369-
:param ticket_id:
370-
:param _sync:
371-
:param ci_dict:
372-
:return:
363+
Create a new Configuration Item (CI) or update existing based on unique constraints.
364+
365+
Handles complete CI creation workflow including validation, uniqueness checks,
366+
password encryption, computed attributes, relationship creation, and caching.
367+
368+
Args:
369+
ci_type_name (str): Name of the CI type to create
370+
exist_policy (ExistPolicy): How to handle existing CIs (REPLACE/REJECT/NEED)
371+
_no_attribute_policy (ExistPolicy): How to handle unknown attributes (IGNORE/REJECT)
372+
is_auto_discovery (bool): Whether CI is created by auto-discovery process
373+
_is_admin (bool): Whether to skip permission checks
374+
ticket_id (int, optional): Associated ticket ID for audit trail
375+
_sync (bool): Whether to execute cache/relation tasks synchronously
376+
**ci_dict: CI attribute values as key-value pairs
377+
378+
Returns:
379+
int: ID of the created or updated CI
380+
381+
Raises:
382+
400: If unique constraints violated, required attributes missing, or validation fails
383+
403: If user lacks permissions for restricted attributes
384+
404: If CI type not found or referenced CI not exists
373385
"""
374386
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
375387
ci_type = CITypeManager.check_is_existed(ci_type_name)
@@ -512,6 +524,24 @@ def add(cls, ci_type_name,
512524
return ci.id
513525

514526
def update(self, ci_id, _is_admin=False, ticket_id=None, _sync=False, **ci_dict):
527+
"""
528+
Update an existing Configuration Item with new attribute values.
529+
530+
Performs comprehensive CI update including validation, constraint checks,
531+
password handling, computed attributes processing, and change tracking.
532+
533+
Args:
534+
ci_id (int): ID of the CI to update
535+
_is_admin (bool): Whether to skip permission checks
536+
ticket_id (int, optional): Associated ticket ID for audit trail
537+
_sync (bool): Whether to execute cache/relation tasks synchronously
538+
**ci_dict: CI attribute values to update as key-value pairs
539+
540+
Raises:
541+
400: If unique constraints violated or validation fails
542+
403: If user lacks permissions for restricted attributes
543+
404: If CI not found
544+
"""
515545
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
516546
ci = self.confirm_ci_existed(ci_id)
517547
ci_type = ci.ci_type
@@ -1420,14 +1450,31 @@ def build_by_attribute(cls, ci_dict):
14201450
parent_attr = AttributeCache.get(parent_attr_id)
14211451
child_attr = AttributeCache.get(child_attr_id)
14221452
attr_value = ci_dict.get(parent_attr.name)
1453+
if attr_value != 0 and not attr_value:
1454+
continue
14231455
value_table = TableMap(attr=child_attr).table
1424-
for child in value_table.get_by(attr_id=child_attr.id, value=attr_value, only_query=True).join(
1425-
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.child_id):
1426-
_relations.add((ci_dict['_id'], child.ci_id))
1456+
attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
1457+
1458+
matching_cis = value_table.get_by(
1459+
attr_id=child_attr.id,
1460+
only_query=True
1461+
).join(
1462+
CI, CI.id == value_table.ci_id
1463+
).filter(
1464+
CI.type_id == item.child_id,
1465+
value_table.value.in_(attr_value_list)
1466+
).all()
1467+
1468+
for ci in matching_cis:
1469+
_relations.add((ci_dict['_id'], ci.ci_id))
1470+
14271471
if relations is None:
14281472
relations = _relations
14291473
else:
1430-
relations &= _relations
1474+
if item.constraint == ConstraintEnum.Many2Many:
1475+
relations |= _relations
1476+
else:
1477+
relations &= _relations
14311478

14321479
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
14331480
first_ci_id=ci_dict['_id'],
@@ -1447,14 +1494,31 @@ def build_by_attribute(cls, ci_dict):
14471494
parent_attr = AttributeCache.get(parent_attr_id)
14481495
child_attr = AttributeCache.get(child_attr_id)
14491496
attr_value = ci_dict.get(child_attr.name)
1497+
if attr_value != 0 and not attr_value:
1498+
continue
14501499
value_table = TableMap(attr=parent_attr).table
1451-
for parent in value_table.get_by(attr_id=parent_attr.id, value=attr_value, only_query=True).join(
1452-
CI, CI.id == value_table.ci_id).filter(CI.type_id == item.parent_id):
1453-
_relations.add((parent.ci_id, ci_dict['_id']))
1500+
attr_value_list = [attr_value] if not isinstance(attr_value, list) else attr_value
1501+
1502+
matching_cis = value_table.get_by(
1503+
attr_id=parent_attr.id,
1504+
only_query=True
1505+
).join(
1506+
CI, CI.id == value_table.ci_id
1507+
).filter(
1508+
CI.type_id == item.parent_id,
1509+
value_table.value.in_(attr_value_list)
1510+
).all()
1511+
1512+
for ci in matching_cis:
1513+
_relations.add((ci.ci_id, ci_dict['_id']))
1514+
14541515
if relations is None:
14551516
relations = _relations
14561517
else:
1457-
relations &= _relations
1518+
if item.constraint == ConstraintEnum.Many2Many:
1519+
relations |= _relations
1520+
else:
1521+
relations &= _relations
14581522

14591523
cls.delete_relations_by_source(RelationSourceEnum.ATTRIBUTE_VALUES,
14601524
second_ci_id=ci_dict['_id'],

cmdb-api/api/lib/cmdb/ipam/address.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def assign_ips(self, ips, subnet_id, cidr, **kwargs):
9292
else:
9393
return abort(400, ErrFormat.ipam_address_model_not_found)
9494

95-
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id), expire=10)):
95+
with (redis_lock.Lock(rd.r, "IPAM_ASSIGN_ADDRESS_{}".format(subnet_id),
96+
expire=60, auto_renewal=True)):
9697
cis = self._get_cis(subnet_id, ips)
9798
ip2ci = {ci[IPAddressBuiltinAttributes.IP]: ci for ci in cis}
9899

0 commit comments

Comments
 (0)