Add support for relax_scale_validation in LbService

Starting from NSX version 2.5.1, LBService will have a new property
'relax_scale_validation'. If this flag is set to true, scale
validation for vs, pool, pool member and rule will be relaxed for lbs

Checks are added to prevent including this flag in API calls on older
versions of NSX backend. feature_supported for this new flag is added
in NsxLib for MP. For Policy, ResourceDef and LBServiceDef are
responsible for the checks.

For MP, checks are added in _build_args() in load_balancer.py >
Service class so that the new flag is only passed when NSX meets
the version requirement.

For Policy, ResourceDef now has a new attribute nsx_version and two
new methods: _set_attr_if_supported() and _attr_supported().
get_obj_dict() will use them to check if a certain attr meets
version requirement, and only include it in body if true.

Change-Id: I4f9c3bfd7995bca85a51a6971fc9d784e574301a
This commit is contained in:
Shawn Wang 2019-08-20 13:15:37 -07:00
parent e252900cc0
commit 093d510988
No known key found for this signature in database
GPG Key ID: C98A86CC967E89A7
11 changed files with 164 additions and 26 deletions

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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):

View File

@ -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):