diff --git a/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py b/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py index 2134b39a..89ea8765 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py @@ -158,3 +158,91 @@ class TestPolicyTransaction(policy_testcase.TestPolicyApi): 'IpAddressPool': pool}]} self.assert_infra_patch_call(expected_body) + + def test_groups_only(self): + + g1 = {'resource_type': 'Group', 'id': 'group1', + 'display_name': 'g1', + 'description': 'first group'} + g2 = {'resource_type': 'Group', 'id': 'group2', + 'description': 'second group', + 'display_name': 'g2'} + d1 = {'resource_type': 'Domain', 'id': 'domain1'} + + d2 = {'resource_type': 'Domain', 'id': 'domain2'} + + with trans.NsxPolicyTransaction(): + + for d in (d1, d2): + d['children'] = [] + + for g in (g1, g2): + self.policy_lib.group.create_or_overwrite( + g['display_name'], + d['id'], + g['id'], + g['description']) + + d['children'].append({'resource_type': 'ChildGroup', + 'Group': g}) + + expected_body = {'resource_type': 'Infra', + 'children': [{'resource_type': 'ChildDomain', + 'Domain': d1}, + {'resource_type': 'ChildDomain', + 'Domain': d2}]} + + self.assert_infra_patch_call(expected_body) + + def test_segment_ports(self): + + port1 = {'id': 'port_on_seg1', + 'resource_type': 'SegmentPort', + 'display_name': 'port_on_seg1', + 'attachment': {'type': 'VIF', + 'app_id': 'app1', + 'traffic_tag': 5} + } + + port2 = {'id': 'port1_on_seg2', + 'resource_type': 'SegmentPort', + 'display_name': 'port_on_seg2', + 'attachment': {'type': 'CHILD', + 'app_id': 'app2', + 'traffic_tag': None} + } + + seg1 = {'id': 'seg1', + 'resource_type': 'Segment', + 'children': [{'resource_type': 'ChildSegmentPort', + 'SegmentPort': port1}]} + seg2 = {'id': 'seg2', + 'resource_type': 'Segment', + 'children': [{'resource_type': 'ChildSegmentPort', + 'SegmentPort': port2}]} + + with trans.NsxPolicyTransaction(): + + self.policy_lib.segment_port.create_or_overwrite( + port1['display_name'], + seg1['id'], + port1['id'], + attachment_type=port1['attachment']['type'], + app_id=port1['attachment']['app_id'], + traffic_tag=port1['attachment']['traffic_tag']) + + self.policy_lib.segment_port.create_or_overwrite( + port2['display_name'], + seg2['id'], + port2['id'], + attachment_type=port2['attachment']['type'], + app_id=port2['attachment']['app_id'], + traffic_tag=port2['attachment']['traffic_tag']) + + expected_body = {'resource_type': 'Infra', + 'children': [{'resource_type': 'ChildSegment', + 'Segment': seg1}, + {'resource_type': 'ChildSegment', + 'Segment': seg2}]} + + self.assert_infra_patch_call(expected_body) diff --git a/vmware_nsxlib/v3/policy/transaction.py b/vmware_nsxlib/v3/policy/transaction.py index 1c338cc1..fb4cad0a 100644 --- a/vmware_nsxlib/v3/policy/transaction.py +++ b/vmware_nsxlib/v3/policy/transaction.py @@ -96,15 +96,28 @@ class NsxPolicyTransaction(object): self.defs = sorted_defs + def _build_wrapper_dict(self, resource_class, node): + return {'resource_type': 'Child%s' % resource_class, + resource_class: node} + def _find_parent_in_dict(self, d, resource_def, level=1): if len(resource_def.path_defs()) <= level: return parent_type = resource_def.path_defs()[level] + is_leaf = (level + 1 == len(resource_def.path_defs())) resource_type = parent_type.resource_type() + resource_class = parent_type.resource_class() parent_id = resource_def.get_attr(resource_def.path_ids[level]) + + def create_missing_node(): + node = {'resource_type': resource_type, + 'id': parent_id, + 'children': []} + return self._build_wrapper_dict(resource_class, node), node + # iterate over all objects in d, and look for resource type for child in d: if resource_type in child and child[resource_type]: @@ -113,17 +126,20 @@ class NsxPolicyTransaction(object): if parent['id'] == parent_id: if is_leaf: return parent - if 'children' in parent: - return self._find_parent_in_dict( - parent['children'], resource_def, level + 1) + if 'children' not in parent: + parent['children'] = [] - # Parent not found - for now, raise an exception - # Support for this will come later - # TODO(annak): remove this when missing parent body is - # created on demand - raise NsxPolicyTransactionException( - "Transactional create is supported for infra level" - " objects and their children") + return self._find_parent_in_dict( + parent['children'], resource_def, level + 1) + + # Parent not found - create a node for missing parent + wrapper, node = create_missing_node() + d.append(wrapper) + if is_leaf: + # This is the last parent that needs creation + return node + return self._find_parent_in_dict(node['children'], resource_def, + level + 1) def apply_defs(self): # TODO(annak): find longest common URL, for now always @@ -136,7 +152,8 @@ class NsxPolicyTransaction(object): top_def = self.defs[0] url = top_def.get_resource_path() - body = {'resource_type': top_def.resource_type()} + body = {'resource_type': top_def.resource_type(), + 'children': []} # iterate over defs (except top level def) for resource_def in self.defs[1:]: parent_dict = None @@ -145,16 +162,16 @@ class NsxPolicyTransaction(object): resource_def) if not parent_dict: + # Top level resource parent_dict = body if 'children' not in parent_dict: parent_dict['children'] = [] resource_class = resource_def.resource_class() - parent_dict['children'].append({ - 'resource_type': 'Child%s' % resource_class, - resource_class: resource_def.get_obj_dict() - }) + parent_dict['children'].append( + self._build_wrapper_dict(resource_class, + resource_def.get_obj_dict())) if body: self.client.patch(url, body)