From 31f2095cc1bf6ac6da07097fece776116e04240d Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Mon, 26 Feb 2018 11:25:32 +0200 Subject: [PATCH] Refactor policy code to match the new NSX The NSX policy manager changed the supported apis: - Use PATCH instead of POST - Remove the Communication profiles - Add ID to communication maps, and allow multiple maps Change-Id: I8cb2dd21892fd9e0878653f5667187fef76a3315 --- .../tests/unit/v3/test_policy_api.py | 98 ++-- .../tests/unit/v3/test_policy_resources.py | 439 +++++------------- vmware_nsxlib/v3/__init__.py | 2 - vmware_nsxlib/v3/policy_constants.py | 6 + vmware_nsxlib/v3/policy_defs.py | 178 +++---- vmware_nsxlib/v3/policy_resources.py | 373 ++++++--------- 6 files changed, 361 insertions(+), 735 deletions(-) diff --git a/vmware_nsxlib/tests/unit/v3/test_policy_api.py b/vmware_nsxlib/tests/unit/v3/test_policy_api.py index de2e2b95..29f31a8d 100644 --- a/vmware_nsxlib/tests/unit/v3/test_policy_api.py +++ b/vmware_nsxlib/tests/unit/v3/test_policy_api.py @@ -44,7 +44,7 @@ class TestPolicyDomain(TestPolicyApi): 'prokaryotic cells', 'typically characterized by membrane lipids') self.policy_api.create_or_update(domain_def) - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/archaea', data=domain_def.get_obj_dict()) @@ -74,7 +74,7 @@ class TestPolicyGroup(TestPolicyApi): 'cats', 'felis catus') self.policy_api.create_or_update(group_def) - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/eukarya/groups/cats', data=group_def.get_obj_dict()) @@ -89,7 +89,7 @@ class TestPolicyGroup(TestPolicyApi): self.policy_api.create_with_parent(domain_def, group_def) data = domain_def.get_obj_dict() data['groups'] = [group_def.get_obj_dict()] - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/eukarya', data=data) @@ -115,7 +115,7 @@ class TestPolicyGroup(TestPolicyApi): 'display_name': None, 'description': None, 'groups': [expected_group]} - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/eukarya', data=expected_data) @@ -132,7 +132,7 @@ class TestPolicyGroup(TestPolicyApi): self.policy_api.create_with_parent(domain_def, group_def) data = domain_def.get_obj_dict() data['groups'] = [group_def.get_obj_dict()] - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/eukarya', data=data) @@ -148,7 +148,7 @@ class TestPolicyService(TestPolicyApi): def test_create(self): service_def = policy.ServiceDef('roomservice') self.policy_api.create_or_update(service_def) - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/services/roomservice', data=service_def.get_obj_dict()) @@ -170,7 +170,7 @@ class TestPolicyService(TestPolicyApi): 'display_name': None, 'description': None, 'service_entries': [expected_entry]} - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/services/roomservice', data=expected_data) @@ -190,123 +190,95 @@ class TestPolicyService(TestPolicyApi): 'display_name': None, 'description': None, 'service_entries': [expected_entry]} - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/services/icmpservice', data=expected_data) -class TestPolicyCommunicationProfile(TestPolicyApi): - - def test_create(self): - profile_def = policy.CommunicationProfileDef('rental') - self.policy_api.create_or_update(profile_def) - self.assert_json_call('POST', self.client, - 'infra/communication-profiles/rental', - data=profile_def.get_obj_dict()) - - def test_create_with_parent(self): - profile_def = policy.CommunicationProfileDef('rental') - entry_def = policy.CommunicationProfileEntryDef( - 'rental', - 'room1', - description='includes roomservice', - services=["roomservice"]) - - self.policy_api.create_with_parent(profile_def, entry_def) - expected_entry = {'id': 'room1', - 'display_name': None, - 'description': 'includes roomservice', - 'services': ["roomservice"], - 'action': 'ALLOW'} - expected_data = {'id': 'rental', - 'display_name': None, - 'description': None, - 'communication_profile_entries': [expected_entry]} - self.assert_json_call('POST', self.client, - 'infra/communication-profiles/rental', - data=expected_data) - - class TestPolicyCommunicationMap(TestPolicyApi): def setUp(self): super(TestPolicyCommunicationMap, self).setUp() self.entry1 = policy.CommunicationMapEntryDef( - 'd1', 'cm1', + 'd1', 'cm1', 'en1', sequence_number=12, source_groups=["group1", "group2"], dest_groups=["group1"], - profile_id="profile1") + service_id="service1") self.entry2 = policy.CommunicationMapEntryDef( - 'd1', 'cm2', + 'd1', 'cm2', 'en2', sequence_number=13, source_groups=["group1", "group2"], dest_groups=["group3"], - profile_id="profile2") + service_id="service2") - self.expected_data1 = {'id': 'cm1', + self.expected_data1 = {'id': 'en1', 'display_name': None, 'description': None, 'sequence_number': 12, + 'action': 'ALLOW', + 'scope': ['ANY'], 'source_groups': ['/infra/domains/d1/groups/group1', '/infra/domains/d1/groups/group2'], 'destination_groups': ['/infra/domains/d1/groups/group1'], - 'communication_profile_path': - '/infra/communication-profiles/profile1'} + 'services': + ['/infra/services/service1']} - self.expected_data2 = {'id': 'cm2', + self.expected_data2 = {'id': 'en2', 'display_name': None, 'description': None, 'sequence_number': 13, + 'action': 'ALLOW', + 'scope': ['ANY'], 'source_groups': ['/infra/domains/d1/groups/group1', '/infra/domains/d1/groups/group2'], 'destination_groups': ['/infra/domains/d1/groups/group3'], - 'communication_profile_path': - '/infra/communication-profiles/profile2'} + 'services': + ['/infra/services/service2']} def test_create_with_one_entry(self): - map_def = policy.CommunicationMapDef('d1') + map_def = policy.CommunicationMapDef(domain_id='d1', map_id='cm1') self.policy_api.create_with_parent(map_def, self.entry1) expected_data = map_def.get_obj_dict() expected_data['communication_entries'] = [self.expected_data1] - self.assert_json_call('POST', self.client, - 'infra/domains/d1/communication-map', + self.assert_json_call('PATCH', self.client, + 'infra/domains/d1/communication-maps/cm1', data=expected_data) def test_create_with_two_entries(self): - map_def = policy.CommunicationMapDef('d1') + map_def = policy.CommunicationMapDef(domain_id='d1', map_id='cm1') self.policy_api.create_with_parent(map_def, [self.entry1, self.entry2]) expected_data = map_def.get_obj_dict() expected_data['communication_entries'] = [self.expected_data1, self.expected_data2] - self.assert_json_call('POST', self.client, - 'infra/domains/d1/communication-map', + self.assert_json_call('PATCH', self.client, + 'infra/domains/d1/communication-maps/cm1', data=expected_data) def test_update_entry(self): self.policy_api.create_or_update(self.entry1) - self.assert_json_call('POST', self.client, - 'infra/domains/d1/communication-map/' - 'communication-entries/cm1', + self.assert_json_call('PATCH', self.client, + 'infra/domains/d1/communication-maps/cm1/' + 'communication-entries/en1', data=self.expected_data1) def test_delete_entry(self): self.policy_api.delete(self.entry2) self.assert_json_call('DELETE', self.client, - 'infra/domains/d1/communication-map/' - 'communication-entries/cm2') + 'infra/domains/d1/communication-maps/cm2/' + 'communication-entries/en2') class TestPolicyEnforcementPoint(TestPolicyApi): @@ -319,7 +291,7 @@ class TestPolicyEnforcementPoint(TestPolicyApi): self.policy_api.create_or_update(ep_def) ep_path = policy.EnforcementPointDef('ep1').get_resource_path() - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, ep_path, data=ep_def.get_obj_dict()) @@ -336,6 +308,6 @@ class TestPolicyDeploymentMap(TestPolicyApi): 'description': None, 'enforcement_point_path': ep_path} - self.assert_json_call('POST', self.client, + self.assert_json_call('PATCH', self.client, 'infra/domains/d1/domain-deployment-maps/dm1', data=expected_data) diff --git a/vmware_nsxlib/tests/unit/v3/test_policy_resources.py b/vmware_nsxlib/tests/unit/v3/test_policy_resources.py index d960e0b9..e27bdff1 100644 --- a/vmware_nsxlib/tests/unit/v3/test_policy_resources.py +++ b/vmware_nsxlib/tests/unit/v3/test_policy_resources.py @@ -418,7 +418,7 @@ class TestPolicyService(NsxPolicyLibTestCase): def test_update(self): id = '111' - name = 'new name' + name = 'newName' description = 'new desc' with mock.patch.object(self.policy_api, "get", return_value={}) as get_call,\ @@ -431,47 +431,14 @@ class TestPolicyService(NsxPolicyLibTestCase): expected_def = policy_defs.ServiceDef(service_id=id, tenant=TEST_TENANT) expected_dict = {'display_name': name, - 'description': description, - 'service_entries': []} + 'description': description} self.assert_called_with_def(get_call, expected_def) self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) - def test_update_entry(self): - id = '111' - protocol = 'udp' - dest_ports = [555] - service_entry_id = '222' - service_entry = {'id': service_entry_id} - - with mock.patch.object( - self.policy_api, "get", - return_value={'service_entries': [service_entry]}) as get_call,\ - mock.patch.object(self.policy_api, - "create_or_update") as update_call: - self.resourceApi.update(id, - protocol=protocol, - dest_ports=dest_ports, - tenant=TEST_TENANT) - # get will be called for the entire service - expected_def = policy_defs.ServiceDef(service_id=id, - tenant=TEST_TENANT) - self.assert_called_with_def(get_call, expected_def) - - # update will be called for the service entry only - expected_entry_def = policy_defs.L4ServiceEntryDef( - service_id=id, - service_entry_id=service_entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': service_entry_id, - 'l4_protocol': protocol.upper(), - 'destination_ports': dest_ports} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict) - def test_update_all(self): id = '111' - name = 'new name' + name = 'newName' description = 'new desc' protocol = 'udp' dest_ports = [555] @@ -496,26 +463,17 @@ class TestPolicyService(NsxPolicyLibTestCase): tenant=TEST_TENANT) self.assert_called_with_def(get_call, expected_def) - # update will be called for the service and entry (2 calls) expected_dict = {'display_name': name, 'description': description, - 'service_entries': []} + 'service_entries': [{ + 'id': service_entry_id, + 'display_name': name, + 'description': description, + 'l4_protocol': protocol.upper(), + 'destination_ports': dest_ports}]} self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) - expected_entry_def = policy_defs.L4ServiceEntryDef( - service_id=id, - service_entry_id=service_entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': service_entry_id, - 'display_name': name, - 'description': description, - 'l4_protocol': protocol.upper(), - 'destination_ports': dest_ports} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict, - call_num=1) - class TestPolicyIcmpService(NsxPolicyLibTestCase): @@ -582,7 +540,7 @@ class TestPolicyIcmpService(NsxPolicyLibTestCase): def test_update(self): id = '111' - name = 'new name' + name = 'new_name' description = 'new desc' with mock.patch.object(self.policy_api, "get", return_value={}) as get_call,\ @@ -595,44 +553,14 @@ class TestPolicyIcmpService(NsxPolicyLibTestCase): expected_def = policy_defs.ServiceDef(service_id=id, tenant=TEST_TENANT) expected_dict = {'display_name': name, - 'description': description, - 'service_entries': []} + 'description': description} self.assert_called_with_def(get_call, expected_def) self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) - def test_update_entry(self): - id = '111' - icmp_code = 12 - service_entry_id = '222' - service_entry = {'id': service_entry_id} - - with mock.patch.object( - self.policy_api, "get", - return_value={'service_entries': [service_entry]}) as get_call,\ - mock.patch.object(self.policy_api, - "create_or_update") as update_call: - self.resourceApi.update(id, - icmp_code=icmp_code, - tenant=TEST_TENANT) - # get will be called for the entire service - expected_def = policy_defs.ServiceDef(service_id=id, - tenant=TEST_TENANT) - self.assert_called_with_def(get_call, expected_def) - - # update will be called for the service entry only - expected_entry_def = policy_defs.IcmpServiceEntryDef( - service_id=id, - service_entry_id=service_entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': service_entry_id, - 'icmp_code': icmp_code} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict) - def test_update_all(self): id = '111' - name = 'new name' + name = 'newName' description = 'new desc' version = 6 icmp_type = 3 @@ -659,194 +587,18 @@ class TestPolicyIcmpService(NsxPolicyLibTestCase): tenant=TEST_TENANT) self.assert_called_with_def(get_call, expected_def) - # update will be called for the service and entry (2 calls) expected_dict = {'display_name': name, 'description': description, - 'service_entries': []} + 'service_entries': [{ + 'id': service_entry_id, + 'display_name': name, + 'description': description, + 'protocol': 'ICMPv6', + 'icmp_type': icmp_type, + 'icmp_code': icmp_code}]} self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) - expected_entry_def = policy_defs.IcmpServiceEntryDef( - service_id=id, - service_entry_id=service_entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': service_entry_id, - 'display_name': name, - 'description': description, - 'protocol': 'ICMPv6', - 'icmp_type': icmp_type, - 'icmp_code': icmp_code} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict, - call_num=1) - - -class TestPolicyCommunicationProfile(NsxPolicyLibTestCase): - - def setUp(self, *args, **kwargs): - super(TestPolicyCommunicationProfile, self).setUp() - self.resourceApi = self.policy_lib.comm_profile - - def test_create(self): - name = 'c1' - description = 'desc' - service_id = '333' - action = 'DENY' - with mock.patch.object(self.policy_api, - "create_with_parent") as api_call: - self.resourceApi.create_or_overwrite(name, description=description, - services=[service_id], - action=action, - tenant=TEST_TENANT) - exp_srv_def = policy_defs.CommunicationProfileDef( - profile_id=mock.ANY, - name=name, - description=description, - tenant=TEST_TENANT) - exp_entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=mock.ANY, - name=name, - description=description, - services=[service_id], - action=action, - tenant=TEST_TENANT) - self.assert_called_with_defs( - api_call, [exp_srv_def, exp_entry_def]) - - def test_delete(self): - id = '111' - with mock.patch.object(self.policy_api, "delete") as api_call,\ - mock.patch.object(self.policy_api, "get") as get_call: - self.resourceApi.delete(id, tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationProfileDef( - profile_id=id, tenant=TEST_TENANT) - self.assert_called_with_def(get_call, expected_def) - self.assert_called_with_def(api_call, expected_def) - - def test_get(self): - id = '111' - with mock.patch.object(self.policy_api, "get") as api_call: - self.resourceApi.get(id, tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationProfileDef( - profile_id=id, tenant=TEST_TENANT) - self.assert_called_with_def(api_call, expected_def) - - def test_get_by_name(self): - name = 'c1' - with mock.patch.object( - self.policy_api, "list", - return_value={'results': [{'display_name': name}]}) as api_call: - obj = self.resourceApi.get_by_name(name, tenant=TEST_TENANT) - self.assertIsNotNone(obj) - expected_def = policy_defs.CommunicationProfileDef( - tenant=TEST_TENANT) - self.assert_called_with_def(api_call, expected_def) - - def test_list(self): - with mock.patch.object(self.policy_api, "list") as api_call: - self.resourceApi.list(tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationProfileDef( - tenant=TEST_TENANT) - self.assert_called_with_def(api_call, expected_def) - - def test_update(self): - id = '111' - name = 'new name' - description = 'new desc' - with mock.patch.object(self.policy_api, "get", - return_value={}) as get_call,\ - mock.patch.object(self.policy_api, - "create_or_update") as update_call: - self.resourceApi.update(id, - name=name, - description=description, - tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationProfileDef( - profile_id=id, tenant=TEST_TENANT) - expected_dict = {'display_name': name, - 'description': description, - 'communication_profile_entries': []} - self.assert_called_with_def(get_call, expected_def) - self.assert_called_with_def_and_dict( - update_call, expected_def, expected_dict) - - def test_update_entry(self): - id = '111' - service_id = '333' - action = 'deny' - entry_id = '222' - profile_entry = {'id': entry_id} - entries_dict = {'communication_profile_entries': [profile_entry]} - - with mock.patch.object( - self.policy_api, "get", return_value=entries_dict) as get_call,\ - mock.patch.object(self.policy_api, - "create_or_update") as update_call: - self.resourceApi.update(id, - services=[service_id], - action=action, - tenant=TEST_TENANT) - # get will be called for the entire service - expected_def = policy_defs.CommunicationProfileDef( - profile_id=id, tenant=TEST_TENANT) - self.assert_called_with_def(get_call, expected_def) - - # update will be called for the service entry only - expected_entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=id, - profile_entry_id=entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': entry_id, - 'action': action.upper(), - 'services': [service_id]} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict) - - def test_update_all(self): - id = '111' - name = 'new name' - description = 'new desc' - service_id = '333' - action = 'deny' - entry_id = '222' - profile_entry = {'id': entry_id} - entries_dict = {'communication_profile_entries': [profile_entry]} - - with mock.patch.object( - self.policy_api, "get", return_value=entries_dict) as get_call,\ - mock.patch.object(self.policy_api, - "create_or_update") as update_call: - self.resourceApi.update(id, - name=name, - description=description, - services=[service_id], - action=action, - tenant=TEST_TENANT) - # get will be called for the entire service - expected_def = policy_defs.CommunicationProfileDef( - profile_id=id, tenant=TEST_TENANT) - self.assert_called_with_def(get_call, expected_def) - - # update will be called for the service and entry (2 calls) - expected_dict = {'display_name': name, - 'description': description, - 'communication_profile_entries': []} - self.assert_called_with_def_and_dict( - update_call, expected_def, expected_dict) - - expected_entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=id, - profile_entry_id=entry_id, - tenant=TEST_TENANT) - expected_entry_dict = {'id': entry_id, - 'display_name': name, - 'description': description, - 'action': action.upper(), - 'services': [service_id]} - self.assert_called_with_def_and_dict( - update_call, expected_entry_def, expected_entry_dict, - call_num=1) - class TestPolicyCommunicationMap(NsxPolicyLibTestCase): @@ -854,67 +606,96 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): super(TestPolicyCommunicationMap, self).setUp() self.resourceApi = self.policy_lib.comm_map - def test_create(self): + def test_create_another(self): domain_id = '111' + map_id = '222' name = 'cm1' description = 'desc' source_group = 'g1' dest_group = 'g2' seq_num = 7 - profile_id = 'c1' - list_return_value = {'results': [{'sequence_number': 1}]} + service_id = 'c1' + get_return_value = {'communication_entries': [{'sequence_number': 1}]} with mock.patch.object(self.policy_api, "create_or_update") as api_call,\ - mock.patch.object(self.policy_api, "list", - return_value=list_return_value): + mock.patch.object(self.policy_api, "get", + return_value=get_return_value): self.resourceApi.create_or_overwrite(name, domain_id, + map_id=map_id, description=description, sequence_number=seq_num, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationMapEntryDef( + expected_def = policy_defs.CommunicationMapDef( domain_id=domain_id, - map_id=mock.ANY, + map_id=map_id, name=name, description=description, - sequence_number=seq_num, - profile_id=profile_id, - source_groups=[source_group], - dest_groups=[dest_group], + category=policy_constants.CATEGORY_DEFAULT, + precedence=0, tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) + expected_def = policy_defs.CommunicationMapEntryDef( + domain_id=domain_id, + map_id=map_id, + entry_id=map_id, + name=name, + description=description, + sequence_number=seq_num, + service_id=service_id, + source_groups=[source_group], + dest_groups=[dest_group], + tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def, call_num=1) + def test_create_first_seqnum(self): domain_id = '111' + map_id = '222' name = 'cm1' description = 'desc' source_group = 'g1' dest_group = 'g2' - profile_id = 'c1' + service_id = 'c1' + category = 'Emergency' + get_return_value = {'communication_entries': []} with mock.patch.object(self.policy_api, "create_or_update") as api_call, \ - mock.patch.object(self.resourceApi, "list", return_value=[]): + mock.patch.object(self.resourceApi, "get", + return_value=get_return_value): self.resourceApi.create_or_overwrite(name, domain_id, + map_id=map_id, description=description, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], + category=category, tenant=TEST_TENANT) + expected_def = policy_defs.CommunicationMapDef( + domain_id=domain_id, + map_id=map_id, + name=name, + description=description, + category=category, + precedence=0, + tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def) + expected_def = policy_defs.CommunicationMapEntryDef( domain_id=domain_id, - map_id=mock.ANY, + map_id=map_id, + entry_id=map_id, name=name, description=description, sequence_number=1, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], tenant=TEST_TENANT) - - self.assert_called_with_def(api_call, expected_def) + self.assert_called_with_def(api_call, expected_def, call_num=1) def test_create_without_seqnum(self): domain_id = '111' @@ -922,29 +703,33 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): description = 'desc' source_group = 'g1' dest_group = 'g2' - profile_id = 'c1' + service_id = 'c1' with mock.patch.object(self.policy_api, - "create_with_parent") as api_call, \ - mock.patch.object(self.resourceApi, "_get_last_seq_num", - return_value=-1): + "create_with_parent") as api_call: self.resourceApi.create_or_overwrite(name, domain_id, description=description, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], tenant=TEST_TENANT) expected_map_def = policy_defs.CommunicationMapDef( domain_id=domain_id, + map_id=mock.ANY, + name=name, + description=description, + category=policy_constants.CATEGORY_DEFAULT, + precedence=0, tenant=TEST_TENANT) expected_entry_def = policy_defs.CommunicationMapEntryDef( domain_id=domain_id, map_id=mock.ANY, + entry_id=mock.ANY, name=name, description=description, sequence_number=1, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], tenant=TEST_TENANT) @@ -958,7 +743,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): id = '222' with mock.patch.object(self.policy_api, "delete") as api_call: self.resourceApi.delete(domain_id, id, tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationMapEntryDef( + expected_def = policy_defs.CommunicationMapDef( domain_id=domain_id, map_id=id, tenant=TEST_TENANT) @@ -969,7 +754,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): id = '222' with mock.patch.object(self.policy_api, "get") as api_call: self.resourceApi.get(domain_id, id, tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationMapEntryDef( + expected_def = policy_defs.CommunicationMapDef( domain_id=domain_id, map_id=id, tenant=TEST_TENANT) @@ -984,8 +769,8 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): obj = self.resourceApi.get_by_name(domain_id, name, tenant=TEST_TENANT) self.assertIsNotNone(obj) - expected_def = policy_defs.CommunicationMapEntryDef( - domain_id, + expected_def = policy_defs.CommunicationMapDef( + domain_id=domain_id, tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) @@ -993,61 +778,53 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): domain_id = '111' with mock.patch.object(self.policy_api, "list") as api_call: self.resourceApi.list(domain_id, tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationMapEntryDef( + expected_def = policy_defs.CommunicationMapDef( domain_id=domain_id, tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) def test_update(self): domain_id = '111' - id = '222' + map_id = '222' name = 'new name' description = 'new desc' source_group = 'ng1' dest_group = 'ng2' - profile_id = 'nc1' + service_id = 'nc1' with mock.patch.object(self.policy_api, "get", return_value={}) as get_call,\ mock.patch.object(self.policy_api, "create_or_update") as update_call: - self.resourceApi.update(domain_id, id, + self.resourceApi.update(domain_id, map_id, name=name, description=description, - profile_id=profile_id, + service_id=service_id, source_groups=[source_group], dest_groups=[dest_group], tenant=TEST_TENANT) - expected_def = policy_defs.CommunicationMapEntryDef( + expected_map_def = policy_defs.CommunicationMapDef( domain_id=domain_id, - map_id=id, + map_id=map_id, tenant=TEST_TENANT) - sgroup_path = "/%s/domains/%s/groups/%s" % ( - TEST_TENANT, domain_id, source_group) - dgroup_path = "/%s/domains/%s/groups/%s" % ( - TEST_TENANT, domain_id, dest_group) - profile_path = "/%s/communication-profiles/%s" % ( - TEST_TENANT, profile_id) - expected_dict = {'display_name': name, - 'description': description, - 'communication_profile_path': profile_path, - 'source_groups': [sgroup_path], - 'destination_groups': [dgroup_path]} - self.assert_called_with_def(get_call, expected_def) + expected_map_dict = {'display_name': name, + 'description': description} + self.assert_called_with_def(get_call, expected_map_def) self.assert_called_with_def_and_dict( - update_call, expected_def, expected_dict) + update_call, expected_map_def, expected_map_dict) def test_get_realized(self): domain_id = 'd1' + map_id = '111' ep_id = 'ef1' result = {'state': policy_constants.STATE_REALIZED} with mock.patch.object( self.policy_api, "get_by_path", return_value=result) as api_get: state = self.resourceApi.get_realized_state( - domain_id, ep_id, tenant=TEST_TENANT) + domain_id, map_id, ep_id, tenant=TEST_TENANT) self.assertEqual(policy_constants.STATE_REALIZED, state) expected_path = policy_defs.REALIZED_STATE_COMM_MAP % ( - TEST_TENANT, ep_id, domain_id) + TEST_TENANT, ep_id, domain_id, map_id) api_get.assert_called_once_with(expected_path) @@ -1063,11 +840,13 @@ class TestPolicyEnforcementPoint(NsxPolicyLibTestCase): ip_address = '1.1.1.1' username = 'admin' password = 'zzz' + thumbprint = 'abc' with mock.patch.object(self.policy_api, "create_or_update") as api_call: self.resourceApi.create_or_overwrite( name, description=description, ip_address=ip_address, + thumbprint=thumbprint, username=username, password=password, tenant=TEST_TENANT) @@ -1078,6 +857,7 @@ class TestPolicyEnforcementPoint(NsxPolicyLibTestCase): description=description, ip_address=ip_address, username=username, + thumbprint=thumbprint, password=password, tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) @@ -1119,18 +899,29 @@ class TestPolicyEnforcementPoint(NsxPolicyLibTestCase): name = 'new name' username = 'admin' password = 'zzz' + ip_address = '1.1.1.1' + thumbprint = 'abc' with mock.patch.object(self.policy_api, - "create_or_update") as update_call: + "create_or_update") as update_call,\ + mock.patch.object(self.policy_api, "get", return_value={'id': id}): self.resourceApi.update(id, name=name, username=username, password=password, + ip_address=ip_address, + thumbprint=thumbprint, tenant=TEST_TENANT) expected_def = policy_defs.EnforcementPointDef(ep_id=id, tenant=TEST_TENANT) - expected_dict = {'display_name': name, - 'username': username, - 'password': password} + expected_dict = {'id': id, + 'display_name': name, + 'resource_type': 'EnforcementPoint', + 'connection_info': { + 'username': username, + 'password': password, + 'thumbprint': thumbprint, + 'enforcement_point_address': ip_address, + 'resource_type': 'NSXTConnectionInfo'}} self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) @@ -1235,7 +1026,7 @@ class TestPolicyDeploymentMap(NsxPolicyLibTestCase): ep_path = ("/%s/deployment-zones/default/" "enforcement-points/%s" % (TEST_TENANT, ep_id)) expected_dict = {'display_name': name, - 'enforcement_point_paths': [ep_path], - 'domain_path': domain_path} + 'enforcement_point_path': ep_path, + 'parent_path': domain_path} self.assert_called_with_def_and_dict( update_call, expected_def, expected_dict) diff --git a/vmware_nsxlib/v3/__init__.py b/vmware_nsxlib/v3/__init__.py index 239eac99..be2befca 100644 --- a/vmware_nsxlib/v3/__init__.py +++ b/vmware_nsxlib/v3/__init__.py @@ -352,8 +352,6 @@ class NsxPolicyLib(NsxLibBase): self.service = policy_resources.NsxPolicyL4ServiceApi(self.policy_api) self.icmp_service = policy_resources.NsxPolicyIcmpServiceApi( self.policy_api) - self.comm_profile = policy_resources.NsxPolicyCommunicationProfileApi( - self.policy_api) self.comm_map = policy_resources.NsxPolicyCommunicationMapApi( self.policy_api) self.enforcement_point = policy_resources.NsxPolicyEnforcementPointApi( diff --git a/vmware_nsxlib/v3/policy_constants.py b/vmware_nsxlib/v3/policy_constants.py index 9c3081bd..a6104777 100644 --- a/vmware_nsxlib/v3/policy_constants.py +++ b/vmware_nsxlib/v3/policy_constants.py @@ -34,3 +34,9 @@ DEFAULT_THUMBPRINT = 'abc' STATE_REALIZED = 'REALIZED' STATE_UNREALIZED = 'UNREALIZED' + +CATEGORY_EMERGENCY = 'Emergency' +CATEGORY_INFRASTRUCTURE = 'Infrastructure' +CATEGORY_ENVIRONMENTAL = 'Environmental' +CATEGORY_APPLICATION = 'Application' +CATEGORY_DEFAULT = 'Default' diff --git a/vmware_nsxlib/v3/policy_defs.py b/vmware_nsxlib/v3/policy_defs.py index cfb0a3cb..4e1b6766 100644 --- a/vmware_nsxlib/v3/policy_defs.py +++ b/vmware_nsxlib/v3/policy_defs.py @@ -22,12 +22,12 @@ from vmware_nsxlib.v3 import policy_constants TENANTS_PATH_PATTERN = "%s/" DOMAINS_PATH_PATTERN = TENANTS_PATH_PATTERN + "domains/" -COMM_PROF_PATH_PATTERN = TENANTS_PATH_PATTERN + "communication-profiles/" SERVICES_PATH_PATTERN = TENANTS_PATH_PATTERN + "services/" REALIZED_STATE_EF = (TENANTS_PATH_PATTERN + "realized-state/enforcement-points/%s/") REALIZED_STATE_GROUP = REALIZED_STATE_EF + "groups/nsgroups/%s-%s" -REALIZED_STATE_COMM_MAP = REALIZED_STATE_EF + "firewalls/firewall-sections/%s" +REALIZED_STATE_COMM_MAP = (REALIZED_STATE_EF + + "firewalls/firewall-sections/%s.%s") REALIZED_STATE_SERVICE = REALIZED_STATE_EF + "services/nsservices/services:%s" @@ -324,124 +324,77 @@ class IcmpServiceEntryDef(ServiceEntryDef): body=body, **kwargs) -class CommunicationProfileDef(ResourceDef): - def __init__(self, - profile_id=None, - name=None, - description=None, - tenant=policy_constants.POLICY_INFRA_TENANT): - super(CommunicationProfileDef, self).__init__() - self.tenant = tenant - self.id = profile_id - self.name = name - self.description = description - self.parent_ids = (tenant) - - @property - def path_pattern(self): - return COMM_PROF_PATH_PATTERN - - def get_obj_dict(self): - body = super(CommunicationProfileDef, self).get_obj_dict() - body['communication_profile_entries'] = [] - return body - - @staticmethod - def sub_entries_path(): - entryDef = CommunicationProfileEntryDef() - return entryDef.get_last_section_dict_key - - def update_attributes_in_body(self, **kwargs): - super(CommunicationProfileDef, self).update_attributes_in_body( - **kwargs) - # make sure entries are there - entries_path = self.sub_entries_path() - if entries_path not in self.body: - self.body[entries_path] = [] - - -class CommunicationProfileEntryDef(ResourceDef): - def __init__(self, - profile_id=None, - profile_entry_id=None, - name=None, - description=None, - services=None, - action=policy_constants.ACTION_ALLOW, - tenant=policy_constants.POLICY_INFRA_TENANT): - super(CommunicationProfileEntryDef, self).__init__() - self.tenant = tenant - self.id = profile_entry_id - self.name = name - self.description = description - self.services = services - self.action = action.upper() - self.parent_ids = (tenant, profile_id) - - @property - def path_pattern(self): - return COMM_PROF_PATH_PATTERN + "%s/communication-profile-entries/" - - def get_obj_dict(self): - body = super(CommunicationProfileEntryDef, self).get_obj_dict() - body['services'] = self.services - body['action'] = self.action - return body - - def update_attributes_in_body(self, **kwargs): - body = self._get_body_from_kwargs(**kwargs) - if 'body' in kwargs: - del kwargs['body'] - if kwargs.get('action') is not None: - body['action'] = kwargs['action'].upper() - del kwargs['action'] - super(CommunicationProfileEntryDef, self).update_attributes_in_body( - body=body, **kwargs) - - class CommunicationMapDef(ResourceDef): def __init__(self, + map_id=None, domain_id=None, + category=policy_constants.CATEGORY_DEFAULT, + name=None, + precedence=0, + description=None, tenant=policy_constants.POLICY_INFRA_TENANT): super(CommunicationMapDef, self).__init__() + self.id = map_id + self.category = category + self.precedence = precedence + self.name = name + self.description = description self.tenant = tenant self.domain_id = domain_id self.parent_ids = (tenant, domain_id) @property def path_pattern(self): - return (DOMAINS_PATH_PATTERN + "%s/communication-map/") + return (DOMAINS_PATH_PATTERN + "%s/communication-maps/") def get_realized_state_path(self, ep_id): - return REALIZED_STATE_COMM_MAP % (self.tenant, ep_id, self.domain_id) + return REALIZED_STATE_COMM_MAP % (self.tenant, ep_id, self.domain_id, + self.id) + + def get_obj_dict(self): + body = super(CommunicationMapDef, self).get_obj_dict() + if self.category: + body['category'] = self.category + if self.precedence: + body['precedence'] = self.precedence + return body + + @staticmethod + def sub_entries_path(): + return CommunicationMapEntryDef().get_last_section_dict_key class CommunicationMapEntryDef(ResourceDef): def __init__(self, domain_id=None, map_id=None, + entry_id=None, sequence_number=None, source_groups=None, dest_groups=None, - profile_id=None, + service_id=None, + action=policy_constants.ACTION_ALLOW, + scope="ANY", name=None, description=None, tenant=policy_constants.POLICY_INFRA_TENANT): super(CommunicationMapEntryDef, self).__init__() self.tenant = tenant self.domain_id = domain_id - self.id = map_id + self.map_id = map_id, + self.id = entry_id self.name = name self.description = description self.sequence_number = sequence_number - + self.action = action + self.scope = scope self.source_groups = self.get_groups_path(domain_id, source_groups) self.dest_groups = self.get_groups_path(domain_id, dest_groups) - self.profile_path = self.get_profile_path( - profile_id) if profile_id else None - self.parent_ids = (tenant, domain_id) + self.service_path = self.get_service_path( + service_id) if service_id else None + self.parent_ids = (tenant, domain_id, map_id) - # convert groups and communication profile to full path + # convert groups and services to full path def get_groups_path(self, domain_id, group_ids): if not group_ids: return [policy_constants.ANY_GROUP] @@ -450,22 +403,24 @@ class CommunicationMapEntryDef(ResourceDef): tenant=self.tenant).get_resource_full_path() for group_id in group_ids] - def get_profile_path(self, profile_id): - return CommunicationProfileDef( - profile_id, + def get_service_path(self, service_id): + return ServiceDef( + service_id, tenant=self.tenant).get_resource_full_path() @property def path_pattern(self): return (DOMAINS_PATH_PATTERN + - "%s/communication-map/communication-entries/") + "%s/communication-maps/%s/communication-entries/") def get_obj_dict(self): body = super(CommunicationMapEntryDef, self).get_obj_dict() body['source_groups'] = self.source_groups body['destination_groups'] = self.dest_groups body['sequence_number'] = self.sequence_number - body['communication_profile_path'] = self.profile_path + body['services'] = [self.service_path] + body['scope'] = [self.scope] + body['action'] = self.action return body def update_attributes_in_body(self, **kwargs): @@ -473,10 +428,10 @@ class CommunicationMapEntryDef(ResourceDef): if 'body' in kwargs: del kwargs['body'] # Fix params that need special conversions - if kwargs.get('profile_id') is not None: - profile_path = self.get_profile_path(kwargs['profile_id']) - body['communication_profile_path'] = profile_path - del kwargs['profile_id'] + if kwargs.get('service_id') is not None: + service_path = self.get_service_path(kwargs['service_id']) + body['services'] = [service_path] + del kwargs['service_id'] if kwargs.get('dest_groups') is not None: groups = self.get_groups_path( @@ -490,6 +445,10 @@ class CommunicationMapEntryDef(ResourceDef): body['source_groups'] = groups del kwargs['source_groups'] + if kwargs.get('scope') is not None: + body['scope'] = [kwargs['scope']] + del kwargs['scope'] + super(CommunicationMapEntryDef, self).update_attributes_in_body( body=body, **kwargs) @@ -538,13 +497,18 @@ class EnforcementPointDef(ResourceDef): if 'body' in kwargs: del kwargs['body'] # Fix params that need special conversions - if body.get('connection_info'): - body['connection_info'][0]['resource_type'] = 'NSXTConnectionInfo' + if not body.get('connection_info'): + body['connection_info'] = {} + body['connection_info']['resource_type'] = 'NSXTConnectionInfo' + body['resource_type'] = 'EnforcementPoint' - for attr in ('username', 'password', 'ip_address', 'thumbprint'): - if kwargs.get(attr) is not None: - body['connection_info'][0][attr] = kwargs[attr] - del kwargs[attr] + for attr in ('username', 'password', 'ip_address', 'thumbprint'): + if kwargs.get(attr) is not None: + body_attr = attr + if attr == 'ip_address': + body_attr = 'enforcement_point_address' + body['connection_info'][body_attr] = kwargs[attr] + del kwargs[attr] super(EnforcementPointDef, self).update_attributes_in_body( body=body, **kwargs) @@ -592,14 +556,14 @@ class DeploymentMapDef(ResourceDef): domain_id = kwargs.get('domain_id') domain_path = DomainDef( domain_id, tenant=self.tenant).get_resource_full_path() - body['domain_path'] = domain_path + body['parent_path'] = domain_path del kwargs['domain_id'] if kwargs.get('ep_id') is not None: ep_id = kwargs.get('ep_id') ep_path = EnforcementPointDef( ep_id, tenant=self.tenant).get_resource_full_path() - body['enforcement_point_paths'] = [ep_path] + body['enforcement_point_path'] = ep_path del kwargs['ep_id'] super(DeploymentMapDef, self).update_attributes_in_body( @@ -616,13 +580,14 @@ class NsxPolicyApi(object): This api will update an existing object, or create a new one if it doesn't exist. - The policy API supports POST for update too + The policy API supports PATCH for create/update operations """ path = resource_def.get_resource_path() body = resource_def.body if not body: body = resource_def.get_obj_dict() - return self.client.create(path, body) + self.client.patch(path, body) + return self.client.get(path) def create_with_parent(self, parent_def, resource_def): path = parent_def.get_resource_path() @@ -633,7 +598,8 @@ class NsxPolicyApi(object): else: child_dict_key = resource_def.get_last_section_dict_key body[child_dict_key] = [resource_def.get_obj_dict()] - return self.client.create(path, body) + self.client.patch(path, body) + return self.client.get(path) def delete(self, resource_def): path = resource_def.get_resource_path() diff --git a/vmware_nsxlib/v3/policy_resources.py b/vmware_nsxlib/v3/policy_resources.py index bf08084c..b3f0d1bd 100644 --- a/vmware_nsxlib/v3/policy_resources.py +++ b/vmware_nsxlib/v3/policy_resources.py @@ -27,9 +27,6 @@ from vmware_nsxlib.v3 import policy_defs LOG = logging.getLogger(__name__) -# TODO(asarfaty): support retries? -# TODO(asarfaty): In future versions PATCH may be supported for update - @six.add_metaclass(abc.ABCMeta) class NsxPolicyResourceBase(object): @@ -68,6 +65,10 @@ class NsxPolicyResourceBase(object): obj_uuid = str(uuid.uuid4()) return obj_uuid + def _canonize_name(self, name): + # remove spaces and slashes from objects names + return name.replace(' ', '_').replace('/', '_') + def get_by_name(self, name, *args, **kwargs): # Return first match by name resources_list = self.list(*args, **kwargs) @@ -271,6 +272,47 @@ class NsxPolicyServiceBase(NsxPolicyResourceBase): path = service_def.get_realized_state_path(ep_id) return self._get_realized_state(path) + # TODO(asarfaty) currently service update doesn't work + def update(self, service_id, name=None, description=None, + tenant=policy_constants.POLICY_INFRA_TENANT, + **kwargs): + # service name cannot contain spaces or slashes + if name: + name = self._canonize_name(name) + + # Get the current data of service & its' service entry + service = self.get(service_id, tenant=tenant) + # update the relevant data service itself: + # TODO(asarfaty): currently updating the service itself doesn't work + if name is not None: + service['display_name'] = name + if description is not None: + service['description'] = description + + if (service.get('service_entries') and + len(service['service_entries']) == 1): + # update the service entry body + self._update_service_entry( + service_id, service['service_entries'][0], + name=name, description=description, **kwargs) + else: + LOG.error("Cannot update service %s - expected 1 service " + "entry", service_id) + + # update the backend + service_def = policy_defs.ServiceDef(service_id=service_id, + tenant=tenant) + service_def.body = service + self.policy_api.create_or_update(service_def) + # return the updated service + return self.get(service_id, tenant=tenant) + + def get_by_name(self, name, *args, **kwargs): + # service name cannot contain spaces or slashes + name = self._canonize_name(name) + return super(NsxPolicyServiceBase, self).get_by_name( + name, *args, **kwargs) + @property def entry_def(self): pass @@ -290,6 +332,8 @@ class NsxPolicyL4ServiceApi(NsxPolicyServiceBase): protocol=policy_constants.TCP, dest_ports=None, tenant=policy_constants.POLICY_INFRA_TENANT): service_id = self._init_obj_uuid(service_id) + # service name cannot contain spaces or slashes + name = self._canonize_name(name) service_def = policy_defs.ServiceDef(service_id=service_id, name=name, description=description, @@ -320,43 +364,6 @@ class NsxPolicyL4ServiceApi(NsxPolicyServiceBase): protocol=protocol, dest_ports=dest_ports) - self.policy_api.create_or_update(entry_def) - - def update(self, service_id, name=None, description=None, - protocol=None, dest_ports=None, - tenant=policy_constants.POLICY_INFRA_TENANT): - # Get the current data of service & its' service entry - service = self.get(service_id, tenant=tenant) - - # update the service itself: - if name is not None or description is not None: - # update the service itself - service_def = policy_defs.ServiceDef(service_id=service_id, - tenant=tenant) - service_def.update_attributes_in_body(name=name, - description=description) - - # update the backend - updated_service = self.policy_api.create_or_update(service_def) - else: - updated_service = service - - # update the service entry if it exists - service_entry = policy_defs.ServiceDef.get_single_entry(service) - if not service_entry: - LOG.error("Cannot update service %s - expected 1 service " - "entry", service_id) - return updated_service - - self._update_service_entry( - service_id, service_entry, - name=name, description=description, - protocol=protocol, dest_ports=dest_ports, - tenant=tenant) - - # re-read the service from the backend to return the current data - return self.get(service_id, tenant=tenant) - class NsxPolicyIcmpServiceApi(NsxPolicyServiceBase): """NSX Policy Service with a single ICMP service entry. @@ -372,6 +379,8 @@ class NsxPolicyIcmpServiceApi(NsxPolicyServiceBase): version=4, icmp_type=None, icmp_code=None, tenant=policy_constants.POLICY_INFRA_TENANT): service_id = self._init_obj_uuid(service_id) + # service name cannot contain spaces or slashes + name = self._canonize_name(name) service_def = policy_defs.ServiceDef(service_id=service_id, name=name, description=description, @@ -404,164 +413,16 @@ class NsxPolicyIcmpServiceApi(NsxPolicyServiceBase): icmp_type=icmp_type, icmp_code=icmp_code) - self.policy_api.create_or_update(entry_def) - - def update(self, service_id, name=None, description=None, - version=None, icmp_type=None, icmp_code=None, - tenant=policy_constants.POLICY_INFRA_TENANT): - # Get the current data of service & its' service entry - service = self.get(service_id, tenant=tenant) - - # update the service itself: - if name is not None or description is not None: - # update the service itself - service_def = policy_defs.ServiceDef(service_id=service_id, - tenant=tenant) - service_def.update_attributes_in_body(name=name, - description=description) - - # update the backend - updated_service = self.policy_api.create_or_update(service_def) - else: - updated_service = service - - # update the service entry if it exists - service_entry = policy_defs.ServiceDef.get_single_entry(service) - if not service_entry: - LOG.error("Cannot update service %s - expected 1 service " - "entry", service_id) - return updated_service - - self._update_service_entry( - service_id, service_entry, - name=name, description=description, - version=version, icmp_type=icmp_type, icmp_code=icmp_code, - tenant=tenant) - - # re-read the service from the backend to return the current data - return self.get(service_id, tenant=tenant) - - -class NsxPolicyCommunicationProfileApi(NsxPolicyResourceBase): - """NSX Policy Communication profile (with a single entry). - - Note the nsx-policy backend supports multiple entries per communication - profile. - At this point this is not supported here. - """ - def create_or_overwrite(self, name, profile_id=None, description=None, - services=None, - action=policy_constants.ACTION_ALLOW, - tenant=policy_constants.POLICY_INFRA_TENANT): - """Create a Communication profile with a single entry. - - Services should be a list of service ids - """ - profile_id = self._init_obj_uuid(profile_id) - profile_def = policy_defs.CommunicationProfileDef( - profile_id=profile_id, - name=name, - description=description, - tenant=tenant) - # NOTE(asarfaty) We set the profile entry display name (which is also - # used as the id) to be the same as the profile name. In case we - # support multiple entries, we need the name to be unique. - entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=profile_id, - name=name, - description=description, - services=services, - action=action, - tenant=tenant) - - return self.policy_api.create_with_parent(profile_def, entry_def) - - def delete(self, profile_id, - tenant=policy_constants.POLICY_INFRA_TENANT): - """Delete the Communication profile with all the entries""" - # first delete the entries, or else the profile deletion will fail - profile_def = policy_defs.CommunicationProfileDef( - profile_id=profile_id, tenant=tenant) - prof = self.policy_api.get(profile_def) - if 'communication_profile_entries' in prof: - for entry in prof['communication_profile_entries']: - entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=profile_id, - profile_entry_id=entry['id'], - tenant=tenant) - self.policy_api.delete(entry_def) - self.policy_api.delete(profile_def) - - def get(self, profile_id, - tenant=policy_constants.POLICY_INFRA_TENANT): - profile_def = policy_defs.CommunicationProfileDef( - profile_id=profile_id, tenant=tenant) - return self.policy_api.get(profile_def) - - def list(self, tenant=policy_constants.POLICY_INFRA_TENANT): - profile_def = policy_defs.CommunicationProfileDef(tenant=tenant) - return self.policy_api.list(profile_def)['results'] - - def _update_profile_entry(self, profile_id, profile_entry, - name=None, description=None, - services=None, action=None, - tenant=policy_constants.POLICY_INFRA_TENANT): - entry_id = profile_entry['id'] - entry_def = policy_defs.CommunicationProfileEntryDef( - profile_id=profile_id, - profile_entry_id=entry_id, - tenant=tenant) - entry_def.update_attributes_in_body(body=profile_entry, - name=name, - description=description, - services=services, - action=action) - - self.policy_api.create_or_update(entry_def) - - def update(self, profile_id, name=None, description=None, - services=None, action=None, - tenant=policy_constants.POLICY_INFRA_TENANT): - # Get the current data of the profile & its' entry - profile = self.get(profile_id, tenant=tenant) - - if name is not None or description is not None: - # update the profile itself - profile_def = policy_defs.CommunicationProfileDef( - profile_id=profile_id, tenant=tenant) - profile_def.update_attributes_in_body(name=name, - description=description) - - # update the backend - updated_profile = self.policy_api.create_or_update(profile_def) - else: - updated_profile = profile - - # update the profile entry if it exists - profile_entry = policy_defs.CommunicationProfileDef.get_single_entry( - profile) - if not profile_entry: - LOG.error("Cannot update communication profile %s - expected 1 " - "profile entry", profile_id) - return updated_profile - - self._update_profile_entry( - profile_id, profile_entry, - name=name, description=description, - services=services, action=action, - tenant=tenant) - - # re-read the profile from the backend to return the current data - return self.get(profile_id, tenant=tenant) - class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase): """NSX Policy CommunicationMap (Under a Domain).""" - def _get_last_seq_num(self, domain_id, + def _get_last_seq_num(self, domain_id, map_id, tenant=policy_constants.POLICY_INFRA_TENANT): # get the current entries, and choose the next unused sequence number + # between the entries under the same communication map try: - com_entries = self.list(domain_id, tenant=tenant) + com_map = self.get(domain_id, map_id, tenant=tenant) + com_entries = com_map.get('communication_entries') except exceptions.ResourceNotFound: return -1 if not com_entries: @@ -571,11 +432,13 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase): return seq_nums[-1] def create_or_overwrite(self, name, domain_id, map_id=None, - description=None, sequence_number=None, - profile_id=None, + description=None, precedence=0, + category=policy_constants.CATEGORY_DEFAULT, + sequence_number=None, service_id=None, + action=policy_constants.ACTION_ALLOW, source_groups=None, dest_groups=None, tenant=policy_constants.POLICY_INFRA_TENANT): - """Create CommunicationMapEntry. + """Create CommunicationMap & Entry. source_groups/dest_groups should be a list of group ids belonging to the domain. @@ -584,44 +447,56 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase): end up with same sequence number. """ # Validate and convert inputs - map_id = self._init_obj_uuid(map_id) - if not profile_id: - # profile-id must be provided + if not service_id: + # service-id must be provided err_msg = (_("Cannot create a communication map %(name)s without " - "communication profile id") % {'name': name}) + "service id") % {'name': name}) raise exceptions.ManagerError(details=err_msg) + if map_id: + # get the next available sequence number + last_sequence = self._get_last_seq_num(domain_id, map_id, + tenant=tenant) + else: + map_id = self._init_obj_uuid(map_id) + last_sequence = -1 - # get the next available sequence number - last_sequence = self._get_last_seq_num(domain_id, tenant=tenant) if not sequence_number: if last_sequence < 0: sequence_number = 1 else: sequence_number = last_sequence + 1 + # Build the communication entry. Since we currently support only one + # it will have the same id as its parent entry_def = policy_defs.CommunicationMapEntryDef( domain_id=domain_id, map_id=map_id, + entry_id=map_id, name=name, description=description, sequence_number=sequence_number, source_groups=source_groups, dest_groups=dest_groups, - profile_id=profile_id, + service_id=service_id, + action=action, tenant=tenant) + map_def = policy_defs.CommunicationMapDef( + domain_id=domain_id, map_id=map_id, + tenant=tenant, name=name, description=description, + precedence=precedence, category=category) if last_sequence < 0: # if communication map is absent, we need to create it - map_def = policy_defs.CommunicationMapDef(domain_id, tenant) - map_result = self.policy_api.create_with_parent(map_def, entry_def) - # return the created entry - return map_result['communication_entries'][0] + return self.policy_api.create_with_parent(map_def, entry_def) - return self.policy_api.create_or_update(entry_def) + # TODO(asarfaty) combine both calls together + self.policy_api.create_or_update(map_def) + self.policy_api.create_or_update(entry_def) + return self.get(domain_id, map_id, tenant=tenant) def delete(self, domain_id, map_id, tenant=policy_constants.POLICY_INFRA_TENANT): - map_def = policy_defs.CommunicationMapEntryDef( + map_def = policy_defs.CommunicationMapDef( domain_id=domain_id, map_id=map_id, tenant=tenant) @@ -629,7 +504,7 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase): def get(self, domain_id, map_id, tenant=policy_constants.POLICY_INFRA_TENANT): - map_def = policy_defs.CommunicationMapEntryDef( + map_def = policy_defs.CommunicationMapDef( domain_id=domain_id, map_id=map_id, tenant=tenant) @@ -644,44 +519,60 @@ class NsxPolicyCommunicationMapApi(NsxPolicyResourceBase): def list(self, domain_id, tenant=policy_constants.POLICY_INFRA_TENANT): """List all the map entries of a specific domain.""" - map_def = policy_defs.CommunicationMapEntryDef( + map_def = policy_defs.CommunicationMapDef( domain_id=domain_id, tenant=tenant) return self.policy_api.list(map_def)['results'] def update(self, domain_id, map_id, name=None, description=None, - sequence_number=None, profile_id=None, - source_groups=None, dest_groups=None, + sequence_number=None, service_id=None, action=None, + source_groups=None, dest_groups=None, precedence=None, + category=None, tenant=policy_constants.POLICY_INFRA_TENANT): - map_def = policy_defs.CommunicationMapEntryDef( - domain_id=domain_id, - map_id=map_id, - tenant=tenant) + # Get the current data of communication map & its' entry + comm_map = self.get(domain_id, map_id, tenant=tenant) + # update the communication map itself: + comm_def = policy_defs.CommunicationMapDef( + domain_id=domain_id, map_id=map_id, tenant=tenant) + if name is not None: + comm_map['display_name'] = name + if description is not None: + comm_map['description'] = description + if category is not None: + comm_map['category'] = category + if precedence is not None: + comm_map['precedence'] = precedence - # Get the current data, and update it with the new values - try: - comm_map = self.get(domain_id, map_id, tenant=tenant) - except exceptions.ResourceNotFound: - return self.create_or_overwrite(name, domain_id, map_id, - description, sequence_number, - profile_id, source_groups, - dest_groups, tenant) + if (comm_map.get('communication_entries') and + len(comm_map['communication_entries']) == 1): + # update the entry body + comm_entry = comm_map['communication_entries'][0] + entry_id = comm_entry['id'] + entry_def = policy_defs.CommunicationMapEntryDef( + domain_id=domain_id, map_id=map_id, entry_id=entry_id, + tenant=tenant) + entry_def.update_attributes_in_body( + body=comm_entry, name=name, + description=description, + service_id=service_id, + source_groups=source_groups, + dest_groups=dest_groups, + sequence_number=sequence_number, + action=action) + else: + LOG.error("Cannot update communication map %s - expected 1 entry", + map_id) - map_def.update_attributes_in_body( - body=comm_map, - name=name, - description=description, - sequence_number=sequence_number, - profile_id=profile_id, - source_groups=source_groups, - dest_groups=dest_groups) + comm_def.body = comm_map + self.policy_api.create_or_update(comm_def) - # update the backend - return self.policy_api.create_or_update(map_def) + # re-read the map from the backend to return the current data + return self.get(domain_id, map_id, tenant=tenant) - def get_realized_state(self, domain_id, ep_id, + def get_realized_state(self, domain_id, map_id, ep_id, tenant=policy_constants.POLICY_INFRA_TENANT): - map_def = policy_defs.CommunicationMapDef(domain_id, tenant) + map_def = policy_defs.CommunicationMapDef(map_id, domain_id, + tenant=tenant) path = map_def.get_realized_state_path(ep_id) return self._get_realized_state(path) @@ -734,13 +625,15 @@ class NsxPolicyEnforcementPointApi(NsxPolicyResourceBase): username & password must be defined """ if not username or password is None: - # profile-id must be provided + # username/password must be provided err_msg = (_("Cannot update an enforcement point without " "username and password")) raise exceptions.ManagerError(details=err_msg) - + # Get the original body because ip & thumbprint are mandatory + body = self.get(ep_id) ep_def = policy_defs.EnforcementPointDef(ep_id=ep_id, tenant=tenant) - ep_def.update_attributes_in_body(name=name, + ep_def.update_attributes_in_body(body=body, + name=name, description=description, ip_address=ip_address, username=username, @@ -807,7 +700,7 @@ class NsxPolicyDeploymentMapApi(NsxPolicyResourceBase): ep_id=None, domain_id=None, tenant=policy_constants.POLICY_INFRA_TENANT): map_def = policy_defs.DeploymentMapDef( - map_id=map_id, tenant=tenant) + map_id=map_id, domain_id=domain_id, tenant=tenant) map_def.update_attributes_in_body(name=name, description=description, ep_id=ep_id,