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 8b489be3..fd7aba27 100644 --- a/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py +++ b/vmware_nsxlib/tests/unit/v3/policy/test_lb_resources.py @@ -628,6 +628,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, @@ -635,14 +636,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) @@ -662,6 +666,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: @@ -708,21 +731,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): diff --git a/vmware_nsxlib/tests/unit/v3/test_constants.py b/vmware_nsxlib/tests/unit/v3/test_constants.py index 27776ca2..337d89d1 100644 --- a/vmware_nsxlib/tests/unit/v3/test_constants.py +++ b/vmware_nsxlib/tests/unit/v3/test_constants.py @@ -348,7 +348,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 9fe6aced..0af239f4 100644 --- a/vmware_nsxlib/tests/unit/v3/test_load_balancer.py +++ b/vmware_nsxlib/tests/unit/v3/test_load_balancer.py @@ -498,13 +498,17 @@ 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: + 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']) + attachment=body['attachment'], + relax_scale_validation=body['relax_scale_validation']) create.assert_called_with('loadbalancer/services', body) diff --git a/vmware_nsxlib/v3/__init__.py b/vmware_nsxlib/v3/__init__.py index f450a780..be41e0b4 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_2_5_1)): + # features available since 2.5.1 + 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/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 b2dfe0f3..bbfdecaf 100644 --- a/vmware_nsxlib/v3/nsx_constants.py +++ b/vmware_nsxlib/v3/nsx_constants.py @@ -146,6 +146,7 @@ NSX_VERSION_2_2_0 = '2.2.0' NSX_VERSION_2_3_0 = '2.3.0' NSX_VERSION_2_4_0 = '2.4.0' NSX_VERSION_2_5_0 = '2.5.0' +NSX_VERSION_2_5_1 = '2.5.1' NSX_VERSION_3_0_0 = '3.0.0' # Features available depending on the NSX Manager backend version @@ -169,6 +170,7 @@ 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' diff --git a/vmware_nsxlib/v3/policy/__init__.py b/vmware_nsxlib/v3/policy/__init__.py index 4e352ffa..1a0ab3cc 100644 --- a/vmware_nsxlib/v3/policy/__init__.py +++ b/vmware_nsxlib/v3/policy/__init__.py @@ -161,8 +161,15 @@ class NsxPolicyLib(lib.NsxLibBase): if (feature == nsx_constants.FEATURE_ENS_WITH_QOS): return True + if (version.LooseVersion(self.get_version()) >= + version.LooseVersion(nsx_constants.NSX_VERSION_2_5_1)): + # features available since 2.5.1 + if (feature == nsx_constants.FEATURE_RELAX_SCALE_VALIDATION): + return True + 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 diff --git a/vmware_nsxlib/v3/policy/core_defs.py b/vmware_nsxlib/v3/policy/core_defs.py index f4930f49..e601cee4 100644 --- a/vmware_nsxlib/v3/policy/core_defs.py +++ b/vmware_nsxlib/v3/policy/core_defs.py @@ -64,9 +64,13 @@ 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() @@ -191,6 +195,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. diff --git a/vmware_nsxlib/v3/policy/core_resources.py b/vmware_nsxlib/v3/policy/core_resources.py index d4156580..c1b35661 100644 --- a/vmware_nsxlib/v3/policy/core_resources.py +++ b/vmware_nsxlib/v3/policy/core_resources.py @@ -125,7 +125,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""" @@ -135,7 +135,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) diff --git a/vmware_nsxlib/v3/policy/lb_defs.py b/vmware_nsxlib/v3/policy/lb_defs.py index c00b4222..83cb9699 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_2_5_1)): + 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_2_5_1) + 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 de6d1c28..a8fb77b0 100644 --- a/vmware_nsxlib/v3/policy/lb_resources.py +++ b/vmware_nsxlib/v3/policy/lb_resources.py @@ -577,6 +577,7 @@ class NsxPolicyLoadBalancerPoolApi(NsxPolicyResourceBase): class NsxPolicyLoadBalancerServiceApi(NsxPolicyResourceBase): """NSX Policy LBService.""" + @property def entry_def(self): return lb_defs.LBServiceDef @@ -586,6 +587,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( @@ -595,6 +597,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) @@ -618,14 +621,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):