From 8985a92978a094e7a3f88cc38c6ff64ddc16b4ed Mon Sep 17 00:00:00 2001 From: Shawn Wang Date: Tue, 20 Aug 2019 13:15:37 -0700 Subject: [PATCH] Cherry pick NSX 3 related patches to Stein commit: I4f9c3bfd7995bca85a51a6971fc9d784e574301a commit: Ib8d8a15f23146e09fd6ed839c626724aa4e36952 commit: If1e86a3f0a0049b3022b9645f738440200e337d9 commit: I45b7c50a50b1a69e37b79f684b324bb17496a9df commit: I59ef2e27f6a2f23a44edcd37da88bdc70fda944d commit: 27d4662f90008790da824942a835c4f861973e61 commit: I26ce4b1af9e9b6c19a3245675a3db2c027361acb commit: I8f8f928d6a7d40a909772b949ea7b49315f69ee3 commit: If51c51fb503dcb5465ad2eda5a3fb0cd24b463b8 commit: Ia34e42a94c10bd3f12ebc658939ed826af53658c commit: Ibf1c1fc918998f6002e445ad53e32b2c5c54ac1c Change-Id: Ibf1c1fc918998f6002e445ad53e32b2c5c54ac1c --- .../tests/unit/v3/policy/test_lb_resources.py | 123 +++++++- .../tests/unit/v3/policy/test_resources.py | 274 +++++++++++++++++- .../tests/unit/v3/policy/test_transaction.py | 188 ++++++++++++ vmware_nsxlib/tests/unit/v3/test_constants.py | 3 +- .../tests/unit/v3/test_load_balancer.py | 18 +- vmware_nsxlib/tests/unit/v3/test_resources.py | 52 +++- vmware_nsxlib/v3/__init__.py | 14 +- vmware_nsxlib/v3/lib.py | 16 +- vmware_nsxlib/v3/load_balancer.py | 26 +- vmware_nsxlib/v3/nsx_constants.py | 2 + vmware_nsxlib/v3/policy/__init__.py | 33 +++ vmware_nsxlib/v3/policy/core_defs.py | 92 +++++- vmware_nsxlib/v3/policy/core_resources.py | 145 +++++++-- vmware_nsxlib/v3/policy/lb_defs.py | 22 ++ vmware_nsxlib/v3/policy/lb_resources.py | 39 ++- vmware_nsxlib/v3/policy/transaction.py | 19 +- 16 files changed, 990 insertions(+), 76 deletions(-) diff --git a/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py b/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py index ab978456..20e5b535 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py @@ -17,6 +17,8 @@ import mock from vmware_nsxlib.tests.unit.v3.policy import test_resources +from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3.policy import constants from vmware_nsxlib.v3.policy import lb_defs TEST_TENANT = 'test' @@ -603,6 +605,7 @@ class TestPolicyLBService(test_resources.NsxPolicyLibTestCase): obj_id = '111' size = 'SMALL' connectivity_path = 'path' + relax_scale_validation = True with self.mock_create_update() as api_call: result = self.resourceApi.create_or_overwrite( name, @@ -610,14 +613,17 @@ class TestPolicyLBService(test_resources.NsxPolicyLibTestCase): description=description, size=size, connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation, tenant=TEST_TENANT) expected_def = ( lb_defs.LBServiceDef( + nsx_version=self.policy_lib.get_version(), lb_service_id=obj_id, name=name, description=description, size=size, connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation, tenant=TEST_TENANT)) self.assert_called_with_def(api_call, expected_def) self.assertEqual(obj_id, result) @@ -637,6 +643,25 @@ class TestPolicyLBService(test_resources.NsxPolicyLibTestCase): self.assert_called_with_def(api_call, expected_def) self.assertIsNotNone(result) + def test_create_with_unsupported_attribute(self): + name = 'd1' + description = 'desc' + relax_scale_validation = True + + with self.mock_create_update() as api_call, \ + mock.patch.object(self.resourceApi, 'version', '0.0.0'): + result = self.resourceApi.create_or_overwrite( + name, description=description, + relax_scale_validation=relax_scale_validation, + tenant=TEST_TENANT) + expected_def = ( + lb_defs.LBServiceDef(lb_service_id=mock.ANY, + name=name, + description=description, + tenant=TEST_TENANT)) + self.assert_called_with_def(api_call, expected_def) + self.assertIsNotNone(result) + def test_delete(self): obj_id = '111' with mock.patch.object(self.policy_api, "delete") as api_call: @@ -683,21 +708,26 @@ class TestPolicyLBService(test_resources.NsxPolicyLibTestCase): description = 'new desc' size = 'SMALL' connectivity_path = 'path' + relax_scale_validation = True with self.mock_get(obj_id, name), \ self.mock_create_update() as update_call: - self.resourceApi.update(obj_id, - name=name, - description=description, - tenant=TEST_TENANT, - size=size, - connectivity_path=connectivity_path) + self.resourceApi.update( + obj_id, + name=name, + description=description, + tenant=TEST_TENANT, + size=size, + connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation) expected_def = lb_defs.LBServiceDef( + nsx_version=self.policy_lib.get_version(), lb_service_id=obj_id, name=name, description=description, tenant=TEST_TENANT, size=size, - connectivity_path=connectivity_path) + connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation) self.assert_called_with_def(update_call, expected_def) def test_get_status(self): @@ -729,6 +759,62 @@ class TestPolicyLBService(test_resources.NsxPolicyLibTestCase): tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) + def test_wait_until_realized_fail(self): + lbs_id = 'test_lbs' + info = {'state': constants.STATE_UNREALIZED, + 'realization_specific_identifier': lbs_id, + 'entity_type': 'LbServiceDto'} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + self.assertRaises(nsxlib_exc.RealizationTimeoutError, + self.resourceApi.wait_until_realized, + lbs_id, max_attempts=5, sleep=0.1, + tenant=TEST_TENANT) + + def test_wait_until_realized_error(self): + lbs_id = 'test_lbs' + error_code = 23500 + related_error_code = 23707 + error_msg = 'Found errors in the request.' + related_error_msg = 'Exceed maximum number of load balancer.' + info = {'state': constants.STATE_ERROR, + 'realization_specific_identifier': lbs_id, + 'entity_type': 'LbServiceDto', + 'alarms': [{ + 'message': error_msg, + 'error_details': { + 'related_errors': [{ + 'error_code': related_error_code, + 'module_name': 'LOAD-BALANCER', + 'error_message': related_error_msg + }], + 'error_code': error_code, + 'module_name': 'LOAD-BALANCER', + 'error_message': error_msg + } + }]} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + with self.assertRaises(nsxlib_exc.RealizationErrorStateError) as e: + self.resourceApi.wait_until_realized( + lbs_id, tenant=TEST_TENANT) + error_msg_tail = "%s: %s" % (error_msg, related_error_msg) + self.assertTrue(e.exception.msg.endswith(error_msg_tail)) + self.assertEqual(e.exception.error_code, error_code) + self.assertEqual(e.exception.related_error_codes, + [related_error_code]) + + def test_wait_until_realized_succeed(self): + lbs_id = 'test_lbs' + info = {'state': constants.STATE_REALIZED, + 'realization_specific_identifier': lbs_id, + 'entity_type': 'LbServiceDto'} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + actual_info = self.resourceApi.wait_until_realized( + lbs_id, max_attempts=5, sleep=0.1, tenant=TEST_TENANT) + self.assertEqual(info, actual_info) + class TestPolicyLBVirtualServer(test_resources.NsxPolicyLibTestCase): @@ -1015,6 +1101,29 @@ class TestPolicyLBVirtualServer(test_resources.NsxPolicyLibTestCase): rules=[{'display_name': 'yy'}]) self.assert_called_with_def(update_call, expected_def) + def test_wait_until_realized_fail(self): + vs_id = 'test_vs' + info = {'state': constants.STATE_UNREALIZED, + 'realization_specific_identifier': vs_id} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + self.assertRaises(nsxlib_exc.RealizationTimeoutError, + self.resourceApi.wait_until_realized, + vs_id, max_attempts=5, sleep=0.1, + tenant=TEST_TENANT) + + def test_wait_until_realized_succeed(self): + vs_id = 'test_vs' + info = {'state': constants.STATE_REALIZED, + 'realization_specific_identifier': vs_id, + 'entity_type': 'LbVirtualServerDto'} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + actual_info = self.resourceApi.wait_until_realized( + vs_id, entity_type='LbVirtualServerDto', max_attempts=5, + sleep=0.1, tenant=TEST_TENANT) + self.assertEqual(info, actual_info) + class TestPolicyLBPoolApi(test_resources.NsxPolicyLibTestCase): diff --git a/vmware_nsxlib/tests/unit/v3/policy/test_resources.py b/vmware_nsxlib/tests/unit/v3/policy/test_resources.py index cf200924..2c3e6621 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_resources.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_resources.py @@ -1931,7 +1931,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): 'display_name': 'map_name', 'rules': [ {'id': entry1_id, 'resource_type': 'Rule', - 'dsiplay_name': 'name1', 'scope': ['scope1']}, + 'display_name': 'name1', 'scope': ['scope1']}, {'id': entry2_id, 'resource_type': 'Rule', 'display_name': 'name2', 'scope': ['scope2']}, {'id': entry3_id, 'resource_type': 'Rule', @@ -1943,7 +1943,7 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): 'display_name': 'new_map_name', 'rules': [ {'id': entry1_id, 'resource_type': 'Rule', - 'dsiplay_name': 'name1', 'scope': ['new_scope1']}, + 'display_name': 'name1', 'scope': ['new_scope1']}, {'id': entry2_id, 'resource_type': 'Rule', 'display_name': 'name2', 'scope': ['scope2']}]} map_def = self.mapDef( @@ -1960,6 +1960,51 @@ class TestPolicyCommunicationMap(NsxPolicyLibTestCase): update_call.assert_called_once_with( map_def.get_resource_path(), updated_map) + def test_update_with_entries_for_IGNORE_entries(self): + domain_id = '111' + map_id = '222' + entry1_id = 'entry1' + entry2_id = 'entry2' + entry3_id = 'entry3' + original_map = { + 'id': map_id, + 'resource_type': self.resource_type, + 'category': constants.CATEGORY_APPLICATION, + 'display_name': 'map_name', + 'rules': [ + {'id': entry1_id, 'resource_type': 'Rule', + 'display_name': 'name1', 'scope': ['scope1'], + '_created_time': 1}, + {'id': entry2_id, 'resource_type': 'Rule', + 'display_name': 'name2', 'scope': ['scope2']}, + {'id': entry3_id, 'resource_type': 'Rule', + 'display_name': 'name3', 'scope': ['scope3']}]} + updated_map = { + 'id': map_id, + 'resource_type': self.resource_type, + 'category': constants.CATEGORY_APPLICATION, + 'display_name': 'new_map_name', + 'rules': [ + {'id': entry1_id, 'resource_type': 'Rule', + 'display_name': 'name1', 'scope': ['scope1'], + '_created_time': 1}, + {'id': entry2_id, 'resource_type': 'Rule', + 'display_name': 'name2', 'scope': ['scope2']}, + {'id': entry3_id, 'resource_type': 'Rule', + 'display_name': 'name3', 'scope': ['scope3']}]} + map_def = self.mapDef( + domain_id=domain_id, + map_id=map_id, + tenant=TEST_TENANT) + with mock.patch.object(self.policy_api, "get", + return_value=original_map),\ + mock.patch.object(self.policy_api.client, + "update") as update_call: + self.resourceApi.update_with_entries( + domain_id, map_id, name='new_map_name', tenant=TEST_TENANT) + update_call.assert_called_once_with( + map_def.get_resource_path(), updated_map) + def test_unset(self): name = 'hello' domain_id = 'test' @@ -3050,6 +3095,7 @@ class TestPolicyTier0NatRule(NsxPolicyLibTestCase): tier0_id = '111' nat_rule_id = 'rule1' action = constants.NAT_ACTION_SNAT + firewall_match = constants.NAT_FIREWALL_MATCH_INTERNAL cidr1 = '1.1.1.1/32' cidr2 = '2.2.2.0/24' enabled = True @@ -3063,6 +3109,7 @@ class TestPolicyTier0NatRule(NsxPolicyLibTestCase): action=action, translated_network=cidr1, source_network=cidr2, + firewall_match=firewall_match, tenant=TEST_TENANT, enabled=enabled) expected_def = core_defs.Tier0NatRule( @@ -3074,6 +3121,7 @@ class TestPolicyTier0NatRule(NsxPolicyLibTestCase): action=action, translated_network=cidr1, source_network=cidr2, + firewall_match=firewall_match, tenant=TEST_TENANT, enabled=enabled) self.assert_called_with_def(api_call, expected_def) @@ -3109,6 +3157,44 @@ class TestPolicyTier0NatRule(NsxPolicyLibTestCase): self.assert_called_with_def(api_call, expected_def) self.assertEqual(mock_t0_nat_rule, result) + def test_update(self): + name = 'test' + description = 'desc' + tier0_id = '111' + nat_rule_id = 'rule1' + action = constants.NAT_ACTION_SNAT + firewall_match = constants.NAT_FIREWALL_MATCH_EXTERNAL + cidr1 = '1.1.1.1/32' + cidr2 = '2.2.2.0/24' + enabled = True + + with mock.patch.object(self.policy_api, + "create_or_update") as api_call: + self.resourceApi.update( + tier0_id, nat_rule_id, + name=name, + description=description, + action=action, + translated_network=cidr1, + firewall_match=firewall_match, + source_network=cidr2, + tenant=TEST_TENANT, + enabled=enabled) + + expected_def = core_defs.Tier0NatRule( + tier0_id=tier0_id, + nat_rule_id=nat_rule_id, + nat_id=self.resourceApi.DEFAULT_NAT_ID, + name=name, + description=description, + action=action, + translated_network=cidr1, + firewall_match=firewall_match, + source_network=cidr2, + tenant=TEST_TENANT, + enabled=enabled) + self.assert_called_with_def(api_call, expected_def) + class TestPolicyTier1NatRule(NsxPolicyLibTestCase): @@ -3122,6 +3208,7 @@ class TestPolicyTier1NatRule(NsxPolicyLibTestCase): tier1_id = '111' nat_rule_id = 'rule1' action = constants.NAT_ACTION_SNAT + firewall_match = constants.NAT_FIREWALL_MATCH_INTERNAL cidr1 = '1.1.1.1/32' cidr2 = '2.2.2.0/24' enabled = True @@ -3134,6 +3221,7 @@ class TestPolicyTier1NatRule(NsxPolicyLibTestCase): description=description, action=action, translated_network=cidr1, + firewall_match=firewall_match, source_network=cidr2, tenant=TEST_TENANT, enabled=enabled) @@ -3146,6 +3234,7 @@ class TestPolicyTier1NatRule(NsxPolicyLibTestCase): description=description, action=action, translated_network=cidr1, + firewall_match=firewall_match, source_network=cidr2, tenant=TEST_TENANT, enabled=enabled) @@ -3167,6 +3256,44 @@ class TestPolicyTier1NatRule(NsxPolicyLibTestCase): tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) + def test_update(self): + name = 'test' + description = 'desc' + tier1_id = '111' + nat_rule_id = 'rule1' + action = constants.NAT_ACTION_SNAT + firewall_match = constants.NAT_FIREWALL_MATCH_INTERNAL + cidr1 = '1.1.1.1/32' + cidr2 = '2.2.2.0/24' + enabled = True + + with mock.patch.object(self.policy_api, + "create_or_update") as api_call: + self.resourceApi.update( + tier1_id, nat_rule_id, + name=name, + description=description, + action=action, + translated_network=cidr1, + firewall_match=firewall_match, + source_network=cidr2, + tenant=TEST_TENANT, + enabled=enabled) + + expected_def = core_defs.Tier1NatRule( + tier1_id=tier1_id, + nat_rule_id=nat_rule_id, + nat_id=self.resourceApi.DEFAULT_NAT_ID, + name=name, + description=description, + action=action, + translated_network=cidr1, + firewall_match=firewall_match, + source_network=cidr2, + tenant=TEST_TENANT, + enabled=enabled) + self.assert_called_with_def(api_call, expected_def) + class TestPolicyTier1StaticRoute(NsxPolicyLibTestCase): @@ -3846,6 +3973,17 @@ class TestPolicyIpPool(NsxPolicyLibTestCase): tenant=TEST_TENANT) self.assert_called_with_def(api_call, expected_def) + def test_get_realization_info(self): + ip_pool_id = '111' + with mock.patch.object( + self.resourceApi, "_get_realization_info") as api_call: + self.resourceApi.get_realization_info( + ip_pool_id, tenant=TEST_TENANT) + expected_def = core_defs.IpPoolDef( + ip_pool_id=ip_pool_id, + tenant=TEST_TENANT) + self.assert_called_with_def_and_dict(api_call, expected_def, {}) + def test_get_static_subnet_realization_info(self): ip_pool_id = 'ip-pool-id' ip_subnet_id = 'static-subnet-id' @@ -3870,6 +4008,53 @@ class TestPolicyIpPool(NsxPolicyLibTestCase): wait=True, subnet_type=constants.IPPOOL_STATIC_SUBNET) api_get.assert_called_once() + def test_wait_until_realized_fail(self): + ip_pool_id = 'p1' + info = {'state': constants.STATE_UNREALIZED, + 'realization_specific_identifier': ip_pool_id, + 'entity_type': 'IpPool'} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + self.assertRaises(nsxlib_exc.RealizationTimeoutError, + self.resourceApi.wait_until_realized, + ip_pool_id, max_attempts=5, sleep=0.1, + tenant=TEST_TENANT) + + def test_wait_until_realized_error(self): + ip_alloc_id = 'ip_alloc_1' + error_code = 5109 + error_msg = 'Insufficient free IP addresses.' + info = {'state': constants.STATE_ERROR, + 'realization_specific_identifier': ip_alloc_id, + 'entity_type': 'AllocationIpAddress', + 'alarms': [{ + 'message': error_msg, + 'error_details': { + 'error_code': error_code, + 'module_name': 'id-allocation service', + 'error_message': error_msg + } + }]} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + with self.assertRaises(nsxlib_exc.RealizationErrorStateError) as e: + self.resourceApi.wait_until_realized( + ip_alloc_id, tenant=TEST_TENANT) + self.assertTrue(e.exception.msg.endswith(error_msg)) + self.assertEqual(e.exception.error_code, error_code) + self.assertEqual(e.exception.related_error_codes, []) + + def test_wait_until_realized_succeed(self): + ip_pool_id = 'p1' + info = {'state': constants.STATE_REALIZED, + 'realization_specific_identifier': ip_pool_id, + 'entity_type': 'IpPool'} + with mock.patch.object(self.resourceApi, "_get_realization_info", + return_value=info): + actual_info = self.resourceApi.wait_until_realized( + ip_pool_id, max_attempts=5, sleep=0.1, tenant=TEST_TENANT) + self.assertEqual(info, actual_info) + class TestPolicySegmentPort(NsxPolicyLibTestCase): @@ -4747,3 +4932,88 @@ class TestPolicyExcludeList(NsxPolicyLibTestCase): def test_update(self): self.skipTest("The action is not supported by this resource") + + +class TestNsxSearch(NsxPolicyLibTestCase): + + def setUp(self): + super(TestNsxSearch, self).setUp() + self.search_path = 'search/query?query=%s' + + def test_nsx_search_by_realization(self): + """Test search of resources with the specified tag.""" + with mock.patch.object(self.policy_lib.client, 'url_get') as search: + realized_id = 'xxx' + realized_type = 'RealizedLogicalSwitch' + query = ('resource_type:GenericPolicyRealizedResource AND ' + 'realization_specific_identifier:%s AND ' + 'entity_type:%s' % (realized_id, realized_type)) + self.policy_lib.search_resource_by_realized_id( + realized_id, realized_type) + search.assert_called_with(self.search_path % query) + + +class TestPolicyGlobalConfig(NsxPolicyLibTestCase): + + def setUp(self, *args, **kwargs): + super(TestPolicyGlobalConfig, self).setUp() + self.resourceApi = self.policy_lib.global_config + + def test_create_or_overwrite(self): + self.skipTest("The action is not supported by this resource") + + def test_delete(self): + self.skipTest("The action is not supported by this resource") + + def test_get(self): + with mock.patch.object(self.policy_api, "get") as api_call: + self.resourceApi.get(tenant=TEST_TENANT) + expected_def = core_defs.GlobalConfigDef( + tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def) + + def test_list(self): + self.skipTest("The action is not supported by this resource") + + def test_update(self): + self.skipTest("The action is not supported by this resource") + + def test_enable_ipv6(self): + current_config = {'l3_forwarding_mode': 'IPV4_ONLY'} + with mock.patch.object(self.policy_api, "get", + return_value=current_config) as api_get,\ + mock.patch.object(self.policy_api.client, "update") as api_put: + self.resourceApi.enable_ipv6(tenant=TEST_TENANT) + api_get.assert_called_once() + api_put.assert_called_once_with( + "%s/global-config/" % TEST_TENANT, + {'l3_forwarding_mode': 'IPV4_AND_IPV6'}) + + def test_enable_ipv6_no_call(self): + current_config = {'l3_forwarding_mode': 'IPV4_AND_IPV6'} + with mock.patch.object(self.policy_api, "get", + return_value=current_config) as api_get,\ + mock.patch.object(self.policy_api.client, "update") as api_put: + self.resourceApi.enable_ipv6(tenant=TEST_TENANT) + api_get.assert_called_once() + api_put.assert_not_called() + + def test_disable_ipv6(self): + current_config = {'l3_forwarding_mode': 'IPV4_AND_IPV6'} + with mock.patch.object(self.policy_api, "get", + return_value=current_config) as api_get,\ + mock.patch.object(self.policy_api.client, "update") as api_put: + self.resourceApi.disable_ipv6(tenant=TEST_TENANT) + api_get.assert_called_once() + api_put.assert_called_once_with( + "%s/global-config/" % TEST_TENANT, + {'l3_forwarding_mode': 'IPV4_ONLY'}) + + def test_disable_ipv6_no_call(self): + current_config = {'l3_forwarding_mode': 'IPV4_ONLY'} + with mock.patch.object(self.policy_api, "get", + return_value=current_config) as api_get,\ + mock.patch.object(self.policy_api.client, "update") as api_put: + self.resourceApi.disable_ipv6(tenant=TEST_TENANT) + api_get.assert_called_once() + api_put.assert_not_called() diff --git a/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py b/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py index acf3c951..67fb387b 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_transaction.py @@ -14,6 +14,8 @@ # under the License. # +import copy + import mock from vmware_nsxlib.tests.unit.v3 import nsxlib_testcase @@ -248,3 +250,189 @@ class TestPolicyTransaction(policy_testcase.TestPolicyApi): 'Segment': seg2}]} self.assert_infra_patch_call(expected_body) + + def test_creating_security_policy_and_dfw_rules(self): + dfw_rule = {'id': 'rule_id1', 'action': 'ALLOW', + 'display_name': 'rule1', 'description': None, + 'direction': 'IN_OUT', 'ip_protocol': 'IPV4_IPV6', + 'logged': False, 'destination_groups': ['destination_url'], + 'source_groups': ['src_url'], 'resource_type': 'Rule', + 'scope': None, 'sequence_number': None, 'tag': None, + 'services': ['ANY']} + security_policy = {'id': 'security_policy_id1', + 'display_name': 'security_policy', + 'category': 'Application', + 'resource_type': 'SecurityPolicy'} + domain = {'resource_type': 'Domain', 'id': 'domain1'} + domain_id = domain['id'] + map_id = security_policy['id'] + dfw_rule_entries = [self.policy_lib.comm_map.build_entry( + name=dfw_rule['display_name'], + domain_id=domain_id, + map_id=map_id, + entry_id=dfw_rule['id'], + source_groups=dfw_rule['source_groups'], + dest_groups=dfw_rule['destination_groups'] + )] + with trans.NsxPolicyTransaction(): + self.policy_lib.comm_map.create_with_entries( + name=security_policy['display_name'], + domain_id=domain_id, + map_id=map_id, + entries=dfw_rule_entries + ) + + def get_group_path(group_id, domain_id): + return '/infra/domains/' + domain_id + '/groups/' + group_id + + dfw_rule['destination_groups'] = [get_group_path(group_id, domain_id) + for group_id in + dfw_rule['destination_groups']] + dfw_rule['source_groups'] = [get_group_path(group_id, domain_id) for + group_id in dfw_rule['source_groups']] + child_rules = [{'resource_type': 'ChildRule', 'Rule': dfw_rule}] + security_policy.update({'children': child_rules}) + child_security_policies = [{ + 'resource_type': 'ChildSecurityPolicy', + 'SecurityPolicy': security_policy + }] + domain.update({'children': child_security_policies}) + child_domains = [{'resource_type': 'ChildDomain', + 'Domain': domain}] + expected_body = {'resource_type': 'Infra', + 'children': child_domains} + self.assert_infra_patch_call(expected_body) + + @mock.patch('vmware_nsxlib.v3.policy.core_defs.NsxPolicyApi.get') + def test_updating_security_policy_and_dfw_rules(self, mock_get_api): + dfw_rule1 = {'id': 'rule_id1', 'action': 'ALLOW', + 'display_name': 'rule1', 'description': None, + 'direction': 'IN_OUT', 'ip_protocol': 'IPV4_IPV6', + 'logged': False, + 'destination_groups': ['destination_url'], + 'source_groups': ['src_url'], 'resource_type': 'Rule', + 'scope': None, 'sequence_number': None, 'tag': None, + 'services': ['ANY'], "_create_time": 1} + dfw_rule2 = {'id': 'rule_id2', 'action': 'DROP', + 'display_name': 'rule2', 'description': None, + 'direction': 'IN_OUT', 'ip_protocol': 'IPV4_IPV6', + 'logged': False, + 'destination_groups': ['destination_url'], + 'source_groups': ['src_url'], 'resource_type': 'Rule', + 'scope': None, 'sequence_number': None, 'tag': None, + 'services': ['ANY'], "_create_time": 1} + security_policy = {'id': 'security_policy_id1', + 'display_name': 'security_policy', + 'category': 'Application', + 'resource_type': 'SecurityPolicy'} + domain = {'resource_type': 'Domain', 'id': 'domain1'} + domain_id = domain['id'] + map_id = security_policy['id'] + new_rule_name = 'new_rule1' + new_direction = 'IN' + dfw_rule_entries = [self.policy_lib.comm_map.build_entry( + name=new_rule_name, + domain_id=domain_id, + map_id=map_id, + entry_id=dfw_rule1['id'], + source_groups=dfw_rule1['source_groups'], + dest_groups=dfw_rule1['destination_groups'], + direction=new_direction + )] + + def get_group_path(group_id, domain_id): + return '/infra/domains/' + domain_id + '/groups/' + group_id + + for dfw_rule in [dfw_rule1, dfw_rule2]: + dfw_rule['destination_groups'] = [get_group_path(group_id, + domain_id) + for group_id in + dfw_rule['destination_groups']] + dfw_rule['source_groups'] = [get_group_path(group_id, domain_id) + for group_id in + dfw_rule['source_groups']] + + security_policy_values = copy.deepcopy(security_policy) + security_policy_values.update({'rules': + copy.deepcopy([dfw_rule1, dfw_rule2])}) + mock_get_api.return_value = security_policy_values + + with trans.NsxPolicyTransaction(): + self.policy_lib.comm_map.update_with_entries( + name=security_policy['display_name'], + domain_id=domain_id, + map_id=map_id, + entries=dfw_rule_entries + ) + + dfw_rule1['display_name'] = new_rule_name + dfw_rule1['direction'] = new_direction + child_rules = [{'resource_type': 'ChildRule', 'Rule': dfw_rule1}, + {'resource_type': 'ChildRule', 'Rule': dfw_rule2, + 'marked_for_delete': True}] + security_policy.update({'children': child_rules}) + child_security_policies = [{ + 'resource_type': 'ChildSecurityPolicy', + 'SecurityPolicy': security_policy + }] + domain.update({'children': child_security_policies}) + child_domains = [{ + 'resource_type': 'ChildDomain', + 'Domain': domain + }] + expected_body = {'resource_type': 'Infra', + 'children': child_domains} + self.assert_infra_patch_call(expected_body) + + @mock.patch('vmware_nsxlib.v3.policy.core_defs.NsxPolicyApi.get') + def test_updating_security_policy_with_no_entries_set(self, mock_get_api): + dfw_rule1 = {'id': 'rule_id1', 'action': 'ALLOW', + 'display_name': 'rule1', 'description': None, + 'direction': 'IN_OUT', 'ip_protocol': 'IPV4_IPV6', + 'logged': False, + 'destination_groups': ['destination_url'], + 'source_groups': ['src_url'], 'resource_type': 'Rule', + 'scope': None, 'sequence_number': None, 'tag': None, + 'services': ['ANY'], "_create_time": 1} + security_policy = {'id': 'security_policy_id1', + 'display_name': 'security_policy', + 'category': 'Application', + 'resource_type': 'SecurityPolicy'} + domain = {'resource_type': 'Domain', 'id': 'domain1'} + domain_id = domain['id'] + map_id = security_policy['id'] + + def get_group_path(group_id, domain_id): + return '/infra/domains/' + domain_id + '/groups/' + group_id + + for dfw_rule in [dfw_rule1]: + dfw_rule['destination_groups'] = [get_group_path(group_id, + domain_id) + for group_id in + dfw_rule['destination_groups']] + dfw_rule['source_groups'] = [get_group_path(group_id, domain_id) + for group_id in + dfw_rule['source_groups']] + + security_policy.update({'rules': [dfw_rule1]}) + mock_get_api.return_value = security_policy + + with trans.NsxPolicyTransaction(): + self.policy_lib.comm_map.update_with_entries( + name=security_policy['display_name'], + domain_id=domain_id, + map_id=map_id + ) + + child_security_policies = [{ + 'resource_type': 'ChildSecurityPolicy', + 'SecurityPolicy': security_policy + }] + domain.update({'children': child_security_policies}) + child_domains = [{ + 'resource_type': 'ChildDomain', + 'Domain': domain + }] + expected_body = {'resource_type': 'Infra', + 'children': child_domains} + self.assert_infra_patch_call(expected_body) diff --git a/vmware_nsxlib/tests/unit/v3/test_constants.py b/vmware_nsxlib/tests/unit/v3/test_constants.py index 3558c5f6..6779f2bb 100644 --- a/vmware_nsxlib/tests/unit/v3/test_constants.py +++ b/vmware_nsxlib/tests/unit/v3/test_constants.py @@ -334,7 +334,8 @@ FAKE_SERVICE = { "attachment": { "target_id": FAKE_ROUTER_UUID, "target_type": "LogicalRouter" - } + }, + "relax_scale_validation": False } FAKE_TZ_UUID = uuidutils.generate_uuid() diff --git a/vmware_nsxlib/tests/unit/v3/test_load_balancer.py b/vmware_nsxlib/tests/unit/v3/test_load_balancer.py index 2ad6d282..4be82e47 100644 --- a/vmware_nsxlib/tests/unit/v3/test_load_balancer.py +++ b/vmware_nsxlib/tests/unit/v3/test_load_balancer.py @@ -500,15 +500,19 @@ class TestService(nsxlib_testcase.NsxClientTestCase): 'description': fake_service['description'], 'enabled': fake_service['enabled'], 'attachment': fake_service['attachment'], + 'relax_scale_validation': fake_service['relax_scale_validation'], 'tags': consts.FAKE_TAGS } - with mock.patch.object(self.nsxlib.client, 'create') as create: - self.nsxlib.load_balancer.service.create( - body['display_name'], body['description'], - consts.FAKE_TAGS, enabled=body['enabled'], - attachment=body['attachment']) - create.assert_called_with('loadbalancer/services', - body) + with mock.patch.object(self.nsxlib.client, 'create') as create, \ + mock.patch.object(self.nsxlib, 'feature_supported') as support: + support.return_value = True + self.nsxlib.load_balancer.service.create( + body['display_name'], body['description'], + consts.FAKE_TAGS, enabled=body['enabled'], + attachment=body['attachment'], + relax_scale_validation=body['relax_scale_validation']) + create.assert_called_with('loadbalancer/services', + body) def test_list_services(self): with mock.patch.object(self.nsxlib.client, 'list') as list_call: diff --git a/vmware_nsxlib/tests/unit/v3/test_resources.py b/vmware_nsxlib/tests/unit/v3/test_resources.py index 24325000..b537b0b9 100644 --- a/vmware_nsxlib/tests/unit/v3/test_resources.py +++ b/vmware_nsxlib/tests/unit/v3/test_resources.py @@ -1565,13 +1565,27 @@ class IpPoolTestCase(BaseTestResource): class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): + def setUp(self): + super(TestNsxSearch, self).setUp() + self.search_path = 'search?query=%s' + self.mock = mock.patch("vmware_nsxlib.v3.NsxLib.get_version", + return_value=self.get_nsxlib_version()) + self.mock.start() + + def tearDown(self): + self.mock.stop() + + @staticmethod + def get_nsxlib_version(): + return '2.5.0' + def test_nsx_search_tags(self): """Test search of resources with the specified tag.""" with mock.patch.object(self.nsxlib.client, 'url_get') as search: user_tags = [{'scope': 'user', 'tag': 'k8s'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags) - search.assert_called_with('search?query=%s' % query) + search.assert_called_with(self.search_path % query) def test_nsx_search_tags_scope_only(self): """Test search of resources with the specified tag.""" @@ -1579,7 +1593,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): user_tags = [{'scope': 'user'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags) - search.assert_called_with('search?query=%s' % query) + search.assert_called_with(self.search_path % query) def test_nsx_search_tags_tag_only(self): """Test search of resources with the specified tag.""" @@ -1587,7 +1601,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): user_tags = [{'tag': 'k8s'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags) - search.assert_called_with('search?query=%s' % query) + search.assert_called_with(self.search_path % query) def test_nsx_search_by_resouce_type_and_attributes(self): with mock.patch.object(self.nsxlib.client, 'url_get') as search: @@ -1598,7 +1612,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): exp_query = 'resource_type:%s AND color:%s' % ( resource_type, attributes['color']) search.assert_called_with( - 'search?query=%s' % exp_query) + self.search_path % exp_query) def test_nsx_search_by_resouce_type_only(self): with mock.patch.object(self.nsxlib.client, 'url_get') as search: @@ -1606,7 +1620,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): self.nsxlib.search_resource_by_attributes(resource_type) exp_query = 'resource_type:%s' % resource_type search.assert_called_with( - 'search?query=%s' % exp_query) + self.search_path % exp_query) def test_nsx_search_no_resource_type_fails(self): self.assertRaises(exceptions.NsxSearchInvalidQuery, @@ -1622,7 +1636,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): exp_query = 'resource_type:%s AND color:%s' % ( resource_type, attributes['color']) search.assert_called_with( - 'search?query=%s&cursor=50&page_size=100' % exp_query) + (self.search_path + '&cursor=50&page_size=100') % exp_query) def test_nsx_search_tags_tag_and_scope(self): """Test search of resources with the specified tag.""" @@ -1630,7 +1644,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): user_tags = [{'tag': 'k8s'}, {'scope': 'user'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags) - search.assert_called_with('search?query=%s' % query) + search.assert_called_with(self.search_path % query) def test_nsx_search_tags_and_resource_type(self): """Test search of specified resource with the specified tag.""" @@ -1641,7 +1655,7 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): # Add resource_type to the query query = "resource_type:%s AND %s" % (res_type, query) self.nsxlib.search_by_tags(tags=user_tags, resource_type=res_type) - search.assert_called_with('search?query=%s' % query) + search.assert_called_with(self.search_path % query) def test_nsx_search_tags_and_cursor(self): """Test search of resources with the specified tag and cursor.""" @@ -1649,7 +1663,8 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): user_tags = [{'scope': 'user', 'tag': 'k8s'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags, cursor=50) - search.assert_called_with('search?query=%s&cursor=50' % query) + search.assert_called_with( + (self.search_path + '&cursor=50') % query) def test_nsx_search_tags_and_page_size(self): """Test search of resources with the specified tag and page size.""" @@ -1657,7 +1672,8 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): user_tags = [{'scope': 'user', 'tag': 'k8s'}] query = self.nsxlib._build_query(tags=user_tags) self.nsxlib.search_by_tags(tags=user_tags, page_size=100) - search.assert_called_with('search?query=%s&page_size=100' % query) + search.assert_called_with( + (self.search_path + '&page_size=100') % query) def test_nsx_search_invalid_query_fail(self): """Test search query failure for missing tag argument.""" @@ -1687,8 +1703,8 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): query = self.nsxlib._build_query(tags=user_tags) results = self.nsxlib.search_all_by_tags(tags=user_tags) search.assert_has_calls([ - mock.call('search?query=%s' % query), - mock.call('search?query=%s&cursor=2' % query)]) + mock.call(self.search_path % query), + mock.call((self.search_path + '&cursor=2') % query)]) self.assertEqual(3, len(results)) def test_get_id_by_resource_and_tag(self): @@ -1726,6 +1742,18 @@ class TestNsxSearch(nsxlib_testcase.NsxClientTestCase): res_type, scope, tag, alert_multiple=True) +class TestNsxSearchNew(TestNsxSearch): + + def setUp(self): + + super(TestNsxSearchNew, self).setUp() + self.search_path = 'search/query?query=%s' + + @staticmethod + def get_nsxlib_version(): + return '3.0.0' + + class TransportZone(BaseTestResource): def setUp(self): diff --git a/vmware_nsxlib/v3/__init__.py b/vmware_nsxlib/v3/__init__.py index 47b2952e..cf29a237 100644 --- a/vmware_nsxlib/v3/__init__.py +++ b/vmware_nsxlib/v3/__init__.py @@ -91,7 +91,7 @@ class NsxLib(lib.NsxLibBase): self.ip_pool = resources.IpPool( self.client, self.nsxlib_config, nsxlib=self) self.load_balancer = load_balancer.LoadBalancer( - self.client, self.nsxlib_config) + self.client, self.nsxlib_config, nsxlib=self) self.trust_management = trust_management.NsxLibTrustManagement( self.client, self.nsxlib_config) self.router = router.RouterLib( @@ -168,7 +168,13 @@ class NsxLib(lib.NsxLibBase): def feature_supported(self, feature): if (version.LooseVersion(self.get_version()) >= - version.LooseVersion(nsx_constants.NSX_VERSION_2_5_0)): + version.LooseVersion(nsx_constants.NSX_VERSION_3_0_0)): + # features available since 3.0.0 + if (feature == nsx_constants.FEATURE_RELAX_SCALE_VALIDATION): + return True + + if (version.LooseVersion(self.get_version()) >= + version.LooseVersion(nsx_constants.NSX_VERSION_2_5_0)): # features available since 2.5 if (feature == nsx_constants.FEATURE_CONTAINER_CLUSTER_INVENTORY): return True @@ -178,7 +184,7 @@ class NsxLib(lib.NsxLibBase): return True if (version.LooseVersion(self.get_version()) >= - version.LooseVersion(nsx_constants.NSX_VERSION_2_4_0)): + version.LooseVersion(nsx_constants.NSX_VERSION_2_4_0)): # Features available since 2.4 if (feature == nsx_constants.FEATURE_ENS_WITH_SEC): return True @@ -188,7 +194,7 @@ class NsxLib(lib.NsxLibBase): return True if (version.LooseVersion(self.get_version()) >= - version.LooseVersion(nsx_constants.NSX_VERSION_2_3_0)): + version.LooseVersion(nsx_constants.NSX_VERSION_2_3_0)): # Features available since 2.3 if (feature == nsx_constants.FEATURE_ROUTER_ALLOCATION_PROFILE): return True diff --git a/vmware_nsxlib/v3/lib.py b/vmware_nsxlib/v3/lib.py index 7b4a1808..7fca7c59 100644 --- a/vmware_nsxlib/v3/lib.py +++ b/vmware_nsxlib/v3/lib.py @@ -14,6 +14,7 @@ # under the License. import abc +from distutils import version from oslo_log import log import six @@ -22,6 +23,7 @@ from vmware_nsxlib._i18n import _ from vmware_nsxlib.v3 import client from vmware_nsxlib.v3 import cluster from vmware_nsxlib.v3 import exceptions +from vmware_nsxlib.v3 import nsx_constants from vmware_nsxlib.v3 import utils LOG = log.getLogger(__name__) @@ -80,6 +82,10 @@ class NsxLibBase(object): def feature_supported(self, feature): pass + @abc.abstractmethod + def get_version(self): + pass + def build_v3_api_version_tag(self): return self.general_apis.build_v3_api_version_tag() @@ -107,6 +113,12 @@ class NsxLibBase(object): url += "&page_size=%d" % page_size return url + def _get_search_url(self): + if (version.LooseVersion(self.get_version()) >= + version.LooseVersion(nsx_constants.NSX_VERSION_3_0_0)): + return "search/query?query=%s" + return "search?query=%s" + # TODO(abhiraut): Revisit this method to generate complex boolean # queries to search resources. def search_by_tags(self, tags, resource_type=None, cursor=None, @@ -133,7 +145,7 @@ class NsxLibBase(object): query += " AND %s" % query_tags else: query = query_tags - url = self._add_pagination_parameters("search?query=%s" % query, + url = self._add_pagination_parameters(self._get_search_url() % query, cursor, page_size) # Retry the search in case of error @@ -170,7 +182,7 @@ class NsxLibBase(object): in attributes.items()]) query = 'resource_type:%s' % resource_type + ( " AND %s" % attributes_query if attributes_query else "") - url = self._add_pagination_parameters("search?query=%s" % query, + url = self._add_pagination_parameters(self._get_search_url() % query, cursor, page_size) # Retry the search in case of error diff --git a/vmware_nsxlib/v3/load_balancer.py b/vmware_nsxlib/v3/load_balancer.py index 23e6b0bc..1a986ec2 100644 --- a/vmware_nsxlib/v3/load_balancer.py +++ b/vmware_nsxlib/v3/load_balancer.py @@ -16,6 +16,7 @@ from oslo_log import log as logging from vmware_nsxlib.v3 import exceptions as nsxlib_exc +from vmware_nsxlib.v3 import nsx_constants from vmware_nsxlib.v3 import utils LOG = logging.getLogger(__name__) @@ -411,6 +412,27 @@ class VirtualServer(LoadBalancerBase): class Service(LoadBalancerBase): resource = 'loadbalancer/services' + def _build_args(self, body, display_name=None, description=None, + tags=None, resource_type=None, **kwargs): + if display_name: + body['display_name'] = display_name + if description: + body['description'] = description + if tags: + body['tags'] = tags + if resource_type: + body['resource_type'] = resource_type + + if ('relax_scale_validation' in kwargs and + not self.nsxlib.feature_supported( + nsx_constants.FEATURE_RELAX_SCALE_VALIDATION)): + kwargs.pop('relax_scale_validation') + LOG.warning("Ignoring relax_scale_validation for new " + "lb service %s: this feature is not supported.", + display_name) + body.update(kwargs) + return body + def update_service_with_virtual_servers(self, service_id, virtual_server_ids): # Using internal method so we can access max_attempts in the decorator @@ -475,8 +497,8 @@ class Service(LoadBalancerBase): class LoadBalancer(object): """This is the class that have all load balancer resource clients""" - def __init__(self, client, nsxlib_config=None): - self.service = Service(client, nsxlib_config) + def __init__(self, client, nsxlib_config=None, nsxlib=None): + self.service = Service(client, nsxlib_config, nsxlib) self.virtual_server = VirtualServer(client, nsxlib_config) self.pool = Pool(client, nsxlib_config) self.monitor = Monitor(client, nsxlib_config) diff --git a/vmware_nsxlib/v3/nsx_constants.py b/vmware_nsxlib/v3/nsx_constants.py index a7249753..480cbca8 100644 --- a/vmware_nsxlib/v3/nsx_constants.py +++ b/vmware_nsxlib/v3/nsx_constants.py @@ -169,11 +169,13 @@ FEATURE_ICMP_STRICT = 'Strict list of supported ICMP types and codes' FEATURE_ROUTER_ALLOCATION_PROFILE = 'Router Allocation Profile' FEATURE_ENABLE_STANDBY_RELOCATION = 'Router Enable standby relocation' FEATURE_PARTIAL_UPDATES = 'Partial Update with PATCH' +FEATURE_RELAX_SCALE_VALIDATION = 'Relax Scale Validation for LbService' # Features available depending on the Policy Manager backend version FEATURE_NSX_POLICY = 'NSX Policy' FEATURE_NSX_POLICY_NETWORKING = 'NSX Policy Networking' FEATURE_NSX_POLICY_MDPROXY = 'NSX Policy Metadata Proxy' +FEATURE_NSX_POLICY_GLOBAL_CONFIG = 'NSX Policy Global Config' # FEATURE available depending on Inventory service backend version FEATURE_CONTAINER_CLUSTER_INVENTORY = 'Container Cluster Inventory' diff --git a/vmware_nsxlib/v3/policy/__init__.py b/vmware_nsxlib/v3/policy/__init__.py index 2c2157be..0691d82a 100644 --- a/vmware_nsxlib/v3/policy/__init__.py +++ b/vmware_nsxlib/v3/policy/__init__.py @@ -20,8 +20,10 @@ from oslo_log import log from vmware_nsxlib import v3 from vmware_nsxlib.v3 import client +from vmware_nsxlib.v3 import exceptions from vmware_nsxlib.v3 import lib from vmware_nsxlib.v3 import nsx_constants +from vmware_nsxlib.v3 import utils as lib_utils from vmware_nsxlib.v3.policy import core_defs from vmware_nsxlib.v3.policy import core_resources @@ -120,6 +122,7 @@ class NsxPolicyLib(lib.NsxLibBase): self.exclude_list = core_resources.NsxPolicyExcludeListApi(*args) self.load_balancer = lb_resources.NsxPolicyLoadBalancerApi(*args) self.ipsec_vpn = ipsec_vpn_resources.NsxPolicyIpsecVpnApi(*args) + self.global_config = core_resources.NsxPolicyGlobalConfig(*args) @property def keepalive_section(self): @@ -161,10 +164,15 @@ class NsxPolicyLib(lib.NsxLibBase): if (version.LooseVersion(self.get_version()) >= version.LooseVersion(nsx_constants.NSX_VERSION_3_0_0)): + # features available since 3.0.0 if feature == nsx_constants.FEATURE_PARTIAL_UPDATES: return True if feature == nsx_constants.FEATURE_NSX_POLICY_MDPROXY: return True + if (feature == nsx_constants.FEATURE_RELAX_SCALE_VALIDATION): + return True + if (feature == nsx_constants.FEATURE_NSX_POLICY_GLOBAL_CONFIG): + return True return (feature == nsx_constants.FEATURE_NSX_POLICY) @@ -185,3 +193,28 @@ class NsxPolicyLib(lib.NsxLibBase): "value": "0 */%d * * * *" % interval_min} body = {"keyValuePairs": [realization_config]} self.client.patch("system-config", body) + + def search_resource_by_realized_id(self, realized_id, realized_type): + """Search resources by a realized id & type + + :returns: a list of resource pathes matching the realized id and type. + """ + if not realized_type or not realized_id: + raise exceptions.NsxSearchInvalidQuery( + reason=_("Resource type or id was not specified")) + query = ('resource_type:GenericPolicyRealizedResource AND ' + 'realization_specific_identifier:%s AND ' + 'entity_type:%s' % (realized_id, realized_type)) + url = self._get_search_url() % query + + # Retry the search on case of error + @lib_utils.retry_upon_exception(exceptions.NsxSearchError, + max_attempts=self.client.max_attempts) + def do_search(url): + return self.client.url_get(url) + + results = do_search(url) + pathes = [] + for resource in results['results']: + pathes.extend(resource.get('intent_paths', [])) + return pathes diff --git a/vmware_nsxlib/v3/policy/core_defs.py b/vmware_nsxlib/v3/policy/core_defs.py index e7b8bf64..25fb7ea5 100644 --- a/vmware_nsxlib/v3/policy/core_defs.py +++ b/vmware_nsxlib/v3/policy/core_defs.py @@ -31,6 +31,7 @@ PROVIDERS_PATH_PATTERN = TENANTS_PATH_PATTERN + "providers/" TIER0S_PATH_PATTERN = TENANTS_PATH_PATTERN + "tier-0s/" TIER1S_PATH_PATTERN = TENANTS_PATH_PATTERN + "tier-1s/" SERVICES_PATH_PATTERN = TENANTS_PATH_PATTERN + "services/" +GLOBAL_CONFIG_PATH_PATTERN = TENANTS_PATH_PATTERN + "global-config/" ENFORCEMENT_POINT_PATTERN = (TENANTS_PATH_PATTERN + "sites/default/enforcement-points/") TRANSPORT_ZONE_PATTERN = ENFORCEMENT_POINT_PATTERN + "%s/transport-zones/" @@ -65,14 +66,21 @@ TIER1_LOCALE_SERVICES_PATH_PATTERN = (TIER1S_PATH_PATTERN + @six.add_metaclass(abc.ABCMeta) class ResourceDef(object): - def __init__(self, **kwargs): + def __init__(self, nsx_version=None, **kwargs): self.attrs = kwargs + # nsx_version should be passed in on init if the resource has + # version-dependant attributes. Otherwise this is ignored + self.nsx_version = nsx_version + # init default tenant self.attrs['tenant'] = self.get_tenant() self.body = {} + # Whether this entry needs to be deleted + self.delete = False + # As of now, for some defs (ex: services) child entry is required, # meaning parent creation will fail without the child. # Unfortunately in transactional API policy still fails us, even if @@ -82,6 +90,12 @@ class ResourceDef(object): # TODO(annak): remove this if/when policy solves this self.mandatory_child_def = None + def set_delete(self): + self.delete = True + + def get_delete(self): + return self.delete + def get_obj_dict(self): body = self.body if self.body else {} if self.resource_type(): @@ -192,6 +206,41 @@ class ResourceDef(object): for attr in attr_list: self._set_attr_if_specified(body, attr) + # Helper to set attr in body if user specified it + # and current nsx version supports it + # Body name must match attr name + def _set_attr_if_supported(self, body, attr, value=None): + if self.has_attr(attr) and self._version_dependant_attr_supported( + attr): + value = value if value is not None else self.get_attr(attr) + body[attr] = value + + # Helper to set attrs in body if user specified them + # and current nsx version supports it + # Body name must match attr name + def _set_attrs_if_supported(self, body, attr_list): + for attr in attr_list: + self._set_attr_if_supported(body, attr) + + def _version_dependant_attr_supported(self, attr): + """Check if a version dependent attr is supported on current NSX + + For each resource def, there could be some attributes which only exist + on NSX after certain versions. This abstract method provides a skeleton + to define version requirements of version-dependent attributes. + + By design, Devs should use _set_attr_if_supported() to add any attrs + that are only known to NSX after a certain version. This method works + as a registry for _set_attrs_if_supported() to know the baseline + version of each version dependent attr. + + Non-version-dependent attributes should be added to the request body + by using _set_attr_if_specified(). This method defaults to false since + any version dependent attr unknown to this lib should be excluded + for security and safety reasons. + """ + return False + @classmethod def get_single_entry(cls, obj_body): """Return the single sub-entry from the object body. @@ -790,9 +839,11 @@ class SegmentPortDef(ResourceDef): if address_bindings: body['address_bindings'] = [binding.get_obj_dict() for binding in address_bindings] - if self.has_attr('attachment_type') or self.has_attr('vif_id'): + if (self.has_attr('attachment_type') or self.has_attr('vif_id') or + self.has_attr('hyperbus_mode')): if (not self.get_attr('attachment_type') and - not self.get_attr('vif_id')): + not self.get_attr('vif_id') and + not self.get_attr('hyperbus_mode')): # detach operation body['attachment'] = None else: @@ -801,6 +852,8 @@ class SegmentPortDef(ResourceDef): attachment['type'] = self.get_attr('attachment_type') if self.get_attr('vif_id'): attachment['id'] = self.get_attr('vif_id') + if self.get_attr('hyperbus_mode'): + self._set_attr_if_supported(attachment, 'hyperbus_mode') self._set_attrs_if_specified(attachment, ['context_id', @@ -1347,6 +1400,17 @@ class SecurityPolicyRuleBaseDef(ResourceDef): body['services'] = self.get_services_path(service_ids) return body + @classmethod + def adapt_from_rule_dict(cls, rule_dict, domain_id, map_id): + entry_id = rule_dict.pop('id', None) + name = rule_dict.pop('display_name', None) + + rule_def = cls(tenant=constants.POLICY_INFRA_TENANT, + domain_id=domain_id, map_id=map_id, entry_id=entry_id, + name=name) + rule_def.set_obj_dict(rule_dict) + return rule_def + class CommunicationMapEntryDef(SecurityPolicyRuleBaseDef): @@ -1785,6 +1849,28 @@ class CertificateDef(ResourceDef): return body +class GlobalConfigDef(ResourceDef): + + @property + def path_pattern(self): + return GLOBAL_CONFIG_PATH_PATTERN + + @property + def path_ids(self): + # Adding dummy 2nd key to satisfy get_section_path + # This resource has no keys, since it is a single object + return ('tenant', 'dummy') + + @staticmethod + def resource_type(): + return "GlobalConfig" + + def get_obj_dict(self): + body = super(GlobalConfigDef, self).get_obj_dict() + self._set_attrs_if_specified(body, ['l3_forwarding_mode']) + return body + + class ExcludeListDef(ResourceDef): @property diff --git a/vmware_nsxlib/v3/policy/core_resources.py b/vmware_nsxlib/v3/policy/core_resources.py index 8a5946d1..09f4ed06 100644 --- a/vmware_nsxlib/v3/policy/core_resources.py +++ b/vmware_nsxlib/v3/policy/core_resources.py @@ -126,7 +126,7 @@ class NsxPolicyResourceBase(object): def _init_def(self, **kwargs): """Helper for update function - ignore attrs without explicit value""" args = self._get_user_args(**kwargs) - return self.entry_def(**args) + return self.entry_def(nsx_version=self.version, **args) def _init_parent_def(self, **kwargs): """Helper for update function - ignore attrs without explicit value""" @@ -136,7 +136,7 @@ class NsxPolicyResourceBase(object): def _get_and_update_def(self, **kwargs): """Helper for update function - ignore attrs without explicit value""" args = self._get_user_args(**kwargs) - resource_def = self.entry_def(**args) + resource_def = self.entry_def(nsx_version=self.version, **args) body = self.policy_api.get(resource_def) if body: resource_def.set_obj_dict(body) @@ -224,17 +224,23 @@ class NsxPolicyResourceBase(object): realization_info.get('realization_specific_identifier')): return realization_info['realization_specific_identifier'] - def _get_realization_error_message(self, info): + def _get_realization_error_message_and_code(self, info): error_msg = 'unknown' + error_code = None + related_error_codes = [] if info.get('alarms'): alarm = info['alarms'][0] error_msg = alarm.get('message') - if (alarm.get('error_details') and - alarm['error_details'].get('related_errors')): - related = alarm['error_details']['related_errors'][0] - error_msg = '%s: %s' % (error_msg, - related.get('error_message')) - return error_msg + if alarm.get('error_details'): + error_code = alarm['error_details'].get('error_code') + if alarm['error_details'].get('related_errors'): + related = alarm['error_details']['related_errors'] + for err_obj in related: + error_msg = '%s: %s' % (error_msg, + err_obj.get('error_message')) + if err_obj.get('error_code'): + related_error_codes.append(err_obj['error_code']) + return error_msg, error_code, related_error_codes def _wait_until_realized(self, resource_def, entity_type=None, sleep=None, max_attempts=None): @@ -255,11 +261,13 @@ class NsxPolicyResourceBase(object): if info['state'] == constants.STATE_REALIZED: return info if info['state'] == constants.STATE_ERROR: - error_msg = self._get_realization_error_message(info) + error_msg, error_code, related_error_codes = \ + self._get_realization_error_message_and_code(info) raise exceptions.RealizationErrorStateError( resource_type=resource_def.resource_type(), resource_id=resource_def.get_id(), - error=error_msg) + error=error_msg, error_code=error_code, + related_error_codes=related_error_codes) try: return get_info() @@ -303,11 +311,13 @@ class NsxPolicyResourceBase(object): if resource_def and test_num % check_status == (check_status - 1): info = self._get_realization_info(resource_def) if info and info['state'] == constants.STATE_ERROR: - error_msg = self._get_realization_error_message(info) + error_msg, error_code, related_error_codes = \ + self._get_realization_error_message_and_code(info) raise exceptions.RealizationErrorStateError( resource_type=resource_def.resource_type(), resource_id=resource_def.get_id(), - error=error_msg) + error=error_msg, error_code=error_code, + related_error_codes=related_error_codes) if (info and info['state'] == constants.STATE_REALIZED and info.get('realization_specific_identifier')): LOG.warning("Realization ID for %s was not found via " @@ -1527,6 +1537,7 @@ class NsxPolicyTier0NatRuleApi(NsxPolicyResourceBase): source_network=IGNORE, destination_network=IGNORE, translated_network=IGNORE, + firewall_match=IGNORE, action=IGNORE, sequence_number=IGNORE, log=IGNORE, @@ -1541,6 +1552,7 @@ class NsxPolicyTier0NatRuleApi(NsxPolicyResourceBase): source_network=source_network, destination_network=destination_network, translated_network=translated_network, + firewall_match=firewall_match, action=action, sequence_number=sequence_number, log=log, @@ -1615,6 +1627,7 @@ class NsxPolicyTier1NatRuleApi(NsxPolicyResourceBase): source_network=IGNORE, destination_network=IGNORE, translated_network=IGNORE, + firewall_match=IGNORE, action=IGNORE, sequence_number=IGNORE, log=IGNORE, @@ -1629,6 +1642,7 @@ class NsxPolicyTier1NatRuleApi(NsxPolicyResourceBase): source_network=source_network, destination_network=destination_network, translated_network=translated_network, + firewall_match=firewall_match, action=action, sequence_number=sequence_number, log=log, @@ -2680,6 +2694,11 @@ class NsxPolicyIpPoolApi(NsxPolicyResourceBase): tenant=tenant) return self.policy_api.get(ip_subnet_def) + def get_realization_info(self, ip_pool_id, entity_type=None, + tenant=constants.POLICY_INFRA_TENANT): + ip_pool_def = self.entry_def(ip_pool_id=ip_pool_id, tenant=tenant) + return self._get_realization_info(ip_pool_def, entity_type=entity_type) + def get_ip_subnet_realization_info( self, ip_pool_id, ip_subnet_id, entity_type=None, @@ -2737,6 +2756,14 @@ class NsxPolicyIpPoolApi(NsxPolicyResourceBase): except IndexError: return + def wait_until_realized(self, ip_pool_id, entity_type=None, + tenant=constants.POLICY_INFRA_TENANT, + sleep=None, max_attempts=None): + ip_pool_def = self.entry_def(ip_pool_id=ip_pool_id, tenant=tenant) + return self._wait_until_realized(ip_pool_def, entity_type=entity_type, + sleep=sleep, + max_attempts=max_attempts) + class NsxPolicySecurityPolicyBaseApi(NsxPolicyResourceBase): @@ -2889,7 +2916,7 @@ class NsxPolicySecurityPolicyBaseApi(NsxPolicyResourceBase): delay=self.nsxlib_config.realization_wait_sec, max_attempts=self.nsxlib_config.realization_max_attempts) def _do_create_with_retry(): - self.policy_api.create_with_parent(map_def, entries) + self._create_or_store(map_def, entries) _do_create_with_retry() return map_id @@ -3077,34 +3104,63 @@ class NsxPolicySecurityPolicyBaseApi(NsxPolicyResourceBase): map_sequence_number=map_sequence_number) map_path = map_def.get_resource_path() - def _overwrite_entries(old_entries, new_entries): + def _overwrite_entries(old_entries, new_entries, transaction): # Replace old entries with new entries, but copy additional - # attributes from old entries for those kept in new entries. + # attributes from old entries for those kept in new entries + # and marked the unwanted ones in the old entries as deleted + # if it is in the transaction call. old_rules = {entry["id"]: entry for entry in old_entries} - new_rules = [] + replaced_entries = [] for entry in new_entries: rule_id = entry.get_id() new_rule = entry.get_obj_dict() old_rule = old_rules.get(rule_id) if old_rule: + old_rules.pop(rule_id) for key, value in old_rule.items(): if key not in new_rule: new_rule[key] = value - new_rules.append(new_rule) - return new_rules + replaced_entries.append( + self.entry_def.adapt_from_rule_dict( + new_rule, domain_id, map_id)) + + if transaction: + replaced_entries.extend( + _mark_delete_entries(old_rules.values())) + return replaced_entries + + def _mark_delete_entries(delete_rule_dicts): + delete_entries = [] + for delete_rule_dict in delete_rule_dicts: + delete_entry = self.entry_def.adapt_from_rule_dict( + delete_rule_dict, domain_id, map_id) + delete_entry.set_delete() + delete_entries.append(delete_entry) + return delete_entries @utils.retry_upon_exception( exceptions.StaleRevision, max_attempts=self.policy_api.client.max_attempts) def _update(): + transaction = trans.NsxPolicyTransaction.get_current() # Get the current data of communication map & its entries comm_map = self.policy_api.get(map_def) + replaced_entries = None + ignore_entries = (entries == IGNORE) + if not ignore_entries: + replaced_entries = _overwrite_entries(comm_map['rules'], + entries, transaction) + comm_map.pop('rules') map_def.set_obj_dict(comm_map) - body = map_def.get_obj_dict() - if entries != IGNORE: - body['rules'] = _overwrite_entries(comm_map['rules'], entries) # Update the entire map at the NSX - self.policy_api.client.update(map_path, body) + if transaction: + self._create_or_store(map_def, replaced_entries) + else: + body = map_def.get_obj_dict() + if not ignore_entries: + body['rules'] = [rule.get_obj_dict() for rule in + replaced_entries] + self.policy_api.client.update(map_path, body) _update() @@ -3926,3 +3982,46 @@ class NsxPolicyExcludeListApi(NsxPolicyResourceBase): raise exceptions.ManagerError(details=err_msg) # TODO(asarfaty): Add support for add/remove member + + +class NsxPolicyGlobalConfig(NsxPolicyResourceBase): + + @property + def entry_def(self): + return core_defs.GlobalConfigDef + + def create_or_overwrite(self, tenant=constants.POLICY_INFRA_TENANT): + err_msg = (_("This action is not supported")) + raise exceptions.ManagerError(details=err_msg) + + def delete(self, tenant=constants.POLICY_INFRA_TENANT): + err_msg = (_("This action is not supported")) + raise exceptions.ManagerError(details=err_msg) + + def get(self, tenant=constants.POLICY_INFRA_TENANT, silent=False): + global_config_def = self.entry_def(tenant=tenant) + return self.policy_api.get(global_config_def, silent=silent) + + def list(self, tenant=constants.POLICY_INFRA_TENANT): + err_msg = (_("This action is not supported")) + raise exceptions.ManagerError(details=err_msg) + + def update(self, members=IGNORE, + tenant=constants.POLICY_INFRA_TENANT): + err_msg = (_("This action is not supported")) + raise exceptions.ManagerError(details=err_msg) + + def _set_l3_forwarding_mode(self, mode, tenant): + # Using PUT as PATCH is not supported for this API + config = self.get() + if config['l3_forwarding_mode'] != mode: + config['l3_forwarding_mode'] = mode + config_def = self.entry_def(tenant=tenant) + path = config_def.get_resource_path() + self.policy_api.client.update(path, config) + + def enable_ipv6(self, tenant=constants.POLICY_INFRA_TENANT): + return self._set_l3_forwarding_mode('IPV4_AND_IPV6', tenant) + + def disable_ipv6(self, tenant=constants.POLICY_INFRA_TENANT): + return self._set_l3_forwarding_mode('IPV4_ONLY', tenant) diff --git a/vmware_nsxlib/v3/policy/lb_defs.py b/vmware_nsxlib/v3/policy/lb_defs.py index cdd0ecd0..63fb1838 100644 --- a/vmware_nsxlib/v3/policy/lb_defs.py +++ b/vmware_nsxlib/v3/policy/lb_defs.py @@ -14,9 +14,15 @@ # under the License. # +from distutils import version + +from oslo_log import log as logging +from vmware_nsxlib.v3 import nsx_constants from vmware_nsxlib.v3.policy import constants from vmware_nsxlib.v3.policy.core_defs import ResourceDef +LOG = logging.getLogger(__name__) + TENANTS_PATH_PATTERN = "%s/" LB_VIRTUAL_SERVERS_PATH_PATTERN = TENANTS_PATH_PATTERN + "lb-virtual-servers/" LB_SERVICES_PATH_PATTERN = TENANTS_PATH_PATTERN + "lb-services/" @@ -379,8 +385,24 @@ class LBServiceDef(ResourceDef): def get_obj_dict(self): body = super(LBServiceDef, self).get_obj_dict() self._set_attrs_if_specified(body, ['size', 'connectivity_path']) + self._set_attrs_if_supported(body, ['relax_scale_validation']) return body + def _version_dependant_attr_supported(self, attr): + if (version.LooseVersion(self.nsx_version) >= + version.LooseVersion(nsx_constants.NSX_VERSION_3_0_0)): + if attr == 'relax_scale_validation': + return True + else: + LOG.warning( + "Ignoring %s for %s %s: this feature is not supported." + "Current NSX version: %s. Minimum supported version: %s", + attr, self.resource_type, self.attrs.get('name', ''), + self.nsx_version, nsx_constants.NSX_VERSION_3_0_0) + return False + + return False + class LBServiceStatisticsDef(ResourceDef): diff --git a/vmware_nsxlib/v3/policy/lb_resources.py b/vmware_nsxlib/v3/policy/lb_resources.py index f433319e..0b491db0 100644 --- a/vmware_nsxlib/v3/policy/lb_resources.py +++ b/vmware_nsxlib/v3/policy/lb_resources.py @@ -558,6 +558,7 @@ class NsxPolicyLoadBalancerPoolApi(NsxPolicyResourceBase): class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): """NSX Policy LBService.""" + @property def entry_def(self): return lb_defs.LBServiceDef @@ -567,6 +568,7 @@ class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): tags=IGNORE, size=IGNORE, connectivity_path=IGNORE, + relax_scale_validation=IGNORE, tenant=constants.POLICY_INFRA_TENANT): lb_service_id = self._init_obj_uuid(lb_service_id) lb_service_def = self._init_def( @@ -576,6 +578,7 @@ class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): tags=tags, size=size, connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation, tenant=tenant) self._create_or_store(lb_service_def) @@ -599,14 +602,18 @@ class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): def update(self, lb_service_id, name=IGNORE, description=IGNORE, tags=IGNORE, size=IGNORE, connectivity_path=IGNORE, + relax_scale_validation=IGNORE, tenant=constants.POLICY_INFRA_TENANT): - self._update(lb_service_id=lb_service_id, - name=name, - description=description, - tags=tags, - size=size, - connectivity_path=connectivity_path, - tenant=tenant) + + self._update( + lb_service_id=lb_service_id, + name=name, + description=description, + tags=tags, + size=size, + connectivity_path=connectivity_path, + relax_scale_validation=relax_scale_validation, + tenant=tenant) def get_statistics(self, lb_service_id, tenant=constants.POLICY_INFRA_TENANT): @@ -646,6 +653,15 @@ class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): tenant=tenant) return profile_def.get_resource_full_path() + def wait_until_realized(self, lb_service_id, entity_type='LbServiceDto', + tenant=constants.POLICY_INFRA_TENANT, + sleep=None, max_attempts=None): + lb_service_def = self.entry_def( + lb_service_id=lb_service_id, tenant=tenant) + return self._wait_until_realized( + lb_service_def, entity_type=entity_type, + sleep=sleep, max_attempts=max_attempts) + class NsxPolicyLoadBalancerVirtualServerAPI(NsxPolicyResourceBase): """NSX Policy LoadBalancerVirtualServers""" @@ -884,6 +900,15 @@ class NsxPolicyLoadBalancerVirtualServerAPI(NsxPolicyResourceBase): tenant=tenant) return profile_def.get_resource_full_path() + def wait_until_realized(self, virtual_server_id, entity_type=None, + tenant=constants.POLICY_INFRA_TENANT, + sleep=None, max_attempts=None): + lbvs_def = self.entry_def( + virtual_server_id=virtual_server_id, tenant=tenant) + return self._wait_until_realized( + lbvs_def, entity_type=entity_type, + sleep=sleep, max_attempts=max_attempts) + @six.add_metaclass(abc.ABCMeta) class NsxPolicyLBMonitorProfileBase(NsxPolicyResourceBase): diff --git a/vmware_nsxlib/v3/policy/transaction.py b/vmware_nsxlib/v3/policy/transaction.py index 0599d189..03bc6157 100644 --- a/vmware_nsxlib/v3/policy/transaction.py +++ b/vmware_nsxlib/v3/policy/transaction.py @@ -67,7 +67,10 @@ class NsxPolicyTransaction(object): self.client = client # TODO(annak): raise exception for different tenants - self.defs.append(resource_def) + if isinstance(resource_def, list): + self.defs.extend(resource_def) + else: + self.defs.append(resource_def) def _sort_defs(self): sorted_defs = [] @@ -96,9 +99,12 @@ 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 _build_wrapper_dict(self, resource_class, node, delete=False): + wrapper_dict = {'resource_type': 'Child%s' % resource_class, + resource_class: node} + if delete: + wrapper_dict.update({'marked_for_delete': True}) + return wrapper_dict def _find_parent_in_dict(self, d, resource_def, level=1): @@ -179,8 +185,9 @@ class NsxPolicyTransaction(object): child_dict_key = child_def.get_last_section_dict_key node[child_dict_key] = [child_def.get_obj_dict()] parent_dict['children'].append( - self._build_wrapper_dict(resource_class, node)) - + self._build_wrapper_dict(resource_class, + node, + resource_def.get_delete())) if body: headers = {'nsx-enable-partial-patch': 'true'} self.client.patch(url, body, headers=headers)