diff --git a/vmware_nsxlib/tests/unit/v3/test_policy_resources.py b/vmware_nsxlib/tests/unit/v3/test_policy_resources.py index 61b1ad07..895dbe24 100644 --- a/vmware_nsxlib/tests/unit/v3/test_policy_resources.py +++ b/vmware_nsxlib/tests/unit/v3/test_policy_resources.py @@ -635,6 +635,126 @@ class TestPolicyIcmpService(NsxPolicyLibTestCase): update_call, expected_def, expected_dict) +class TestPolicyIPProtocolService(NsxPolicyLibTestCase): + + def setUp(self, *args, **kwargs): + super(TestPolicyIPProtocolService, self).setUp() + self.resourceApi = self.policy_lib.ip_protocol_service + + def test_create(self): + name = 's1' + description = 'desc' + protocol_number = 2 + with mock.patch.object(self.policy_api, + "create_with_parent") as api_call: + self.resourceApi.create_or_overwrite( + name, + description=description, + protocol_number=protocol_number, + tenant=TEST_TENANT) + exp_srv_def = policy_defs.ServiceDef(service_id=mock.ANY, + name=name, + description=description, + tenant=TEST_TENANT) + exp_entry_def = policy_defs.IPProtocolServiceEntryDef( + service_id=mock.ANY, + name=name, + description=description, + protocol_number=protocol_number, + tenant=TEST_TENANT) + self.assert_called_with_defs( + api_call, [exp_srv_def, exp_entry_def]) + + def test_delete(self): + id = '111' + with mock.patch.object(self.policy_api, "delete") as api_call,\ + mock.patch.object(self.policy_api, "get") as get_call: + self.resourceApi.delete(id, tenant=TEST_TENANT) + expected_def = policy_defs.ServiceDef(service_id=id, + tenant=TEST_TENANT) + self.assert_called_with_def(get_call, expected_def) + self.assert_called_with_def(api_call, expected_def) + + def test_get(self): + id = '111' + with mock.patch.object(self.policy_api, "get") as api_call: + self.resourceApi.get(id, tenant=TEST_TENANT) + expected_def = policy_defs.ServiceDef(service_id=id, + tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def) + + def test_get_by_name(self): + name = 's1' + with mock.patch.object( + self.policy_api, "list", + return_value={'results': [{'display_name': name}]}) as api_call: + obj = self.resourceApi.get_by_name(name, tenant=TEST_TENANT) + self.assertIsNotNone(obj) + expected_def = policy_defs.ServiceDef(tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def) + + def test_list(self): + with mock.patch.object(self.policy_api, "list") as api_call: + self.resourceApi.list(tenant=TEST_TENANT) + expected_def = policy_defs.ServiceDef(tenant=TEST_TENANT) + self.assert_called_with_def(api_call, expected_def) + + def test_update(self): + id = '111' + name = 'new_name' + description = 'new desc' + with mock.patch.object(self.policy_api, "get", + return_value={}) as get_call,\ + mock.patch.object(self.policy_api, + "create_or_update") as update_call: + self.resourceApi.update(id, + name=name, + description=description, + tenant=TEST_TENANT) + expected_def = policy_defs.ServiceDef(service_id=id, + tenant=TEST_TENANT) + expected_dict = {'display_name': name, + 'description': description} + self.assert_called_with_def(get_call, expected_def) + self.assert_called_with_def_and_dict( + update_call, expected_def, expected_dict) + + def test_update_all(self): + id = '111' + name = 'newName' + description = 'new desc' + protocol_number = 3 + service_entry_id = '222' + service_entry = {'id': service_entry_id} + + with mock.patch.object( + self.policy_api, "get", + return_value={'service_entries': [service_entry]}) as get_call,\ + mock.patch.object(self.policy_api, + "create_or_update") as update_call,\ + mock.patch.object(self.policy_api, "list", + return_value={'results': []}): + self.resourceApi.update(id, + name=name, + description=description, + protocol_number=protocol_number, + tenant=TEST_TENANT) + # get will be called for the entire service + expected_def = policy_defs.ServiceDef(service_id=id, + tenant=TEST_TENANT) + self.assert_called_with_def(get_call, expected_def) + + expected_dict = {'display_name': name, + 'description': description, + 'service_entries': [{ + 'id': service_entry_id, + 'display_name': name, + 'description': description, + 'protocol_number': protocol_number}]} + self.assert_called_with_def_and_dict( + update_call, expected_def, expected_dict) + + class TestPolicyCommunicationMap(NsxPolicyLibTestCase): def setUp(self, *args, **kwargs): diff --git a/vmware_nsxlib/v3/__init__.py b/vmware_nsxlib/v3/__init__.py index 7e2b1a1f..ec7ca66b 100644 --- a/vmware_nsxlib/v3/__init__.py +++ b/vmware_nsxlib/v3/__init__.py @@ -383,6 +383,9 @@ class NsxPolicyLib(NsxLibBase): self.service = policy_resources.NsxPolicyL4ServiceApi(self.policy_api) self.icmp_service = policy_resources.NsxPolicyIcmpServiceApi( self.policy_api) + self.ip_protocol_service = ( + policy_resources.NsxPolicyIPProtocolServiceApi( + self.policy_api)) self.network = policy_resources.NsxPolicyNetworkApi(self.policy_api) self.segment = policy_resources.NsxPolicySegmentApi(self.policy_api) self.comm_map = policy_resources.NsxPolicyCommunicationMapApi( diff --git a/vmware_nsxlib/v3/policy_defs.py b/vmware_nsxlib/v3/policy_defs.py index 308d2e16..f7b49627 100644 --- a/vmware_nsxlib/v3/policy_defs.py +++ b/vmware_nsxlib/v3/policy_defs.py @@ -441,6 +441,38 @@ class IcmpServiceEntryDef(ServiceEntryDef): body=body, **kwargs) +class IPProtocolServiceEntryDef(ServiceEntryDef): + def __init__(self, + service_id=None, + service_entry_id=None, + name=None, + description=None, + protocol_number=None, + tenant=policy_constants.POLICY_INFRA_TENANT): + super(IPProtocolServiceEntryDef, self).__init__() + self.tenant = tenant + self.id = service_entry_id + self.name = name + self.description = description + self.protocol_number = protocol_number + self.parent_ids = (tenant, service_id) + + def get_obj_dict(self): + body = super(IPProtocolServiceEntryDef, self).get_obj_dict() + body['resource_type'] = 'IPProtocolServiceEntry' + body['protocol_number'] = self.protocol_number + return body + + def update_attributes_in_body(self, **kwargs): + # Fix params that need special conversions + body = self._get_body_from_kwargs(**kwargs) + if 'body' in kwargs: + del kwargs['body'] + + super(IPProtocolServiceEntryDef, self).update_attributes_in_body( + body=body, **kwargs) + + class CommunicationMapDef(ResourceDef): def __init__(self, map_id=None, diff --git a/vmware_nsxlib/v3/policy_resources.py b/vmware_nsxlib/v3/policy_resources.py index c33ecff2..5c3fa0ab 100644 --- a/vmware_nsxlib/v3/policy_resources.py +++ b/vmware_nsxlib/v3/policy_resources.py @@ -435,6 +435,52 @@ class NsxPolicyIcmpServiceApi(NsxPolicyServiceBase): icmp_code=icmp_code) +class NsxPolicyIPProtocolServiceApi(NsxPolicyServiceBase): + """NSX Policy Service with a single IPProtocol service entry. + + Note the nsx-policy backend supports multiple service entries per service. + At this point this is not supported here. + """ + @property + def entry_def(self): + return policy_defs.IPProtocolServiceEntryDef + + def create_or_overwrite(self, name, service_id=None, description=None, + protocol_number=None, + tenant=policy_constants.POLICY_INFRA_TENANT): + service_id = self._init_obj_uuid(service_id) + # service name cannot contain spaces or slashes + name = self._canonize_name(name) + service_def = policy_defs.ServiceDef(service_id=service_id, + name=name, + description=description, + tenant=tenant) + # NOTE(asarfaty) We set the service entry display name (which is also + # used as the id) to be the same as the service name. In case we + # support multiple service entries, we need the name to be unique. + entry_def = policy_defs.IPProtocolServiceEntryDef( + service_id=service_id, + name=name, + description=description, + protocol_number=protocol_number, + tenant=tenant) + + return self.policy_api.create_with_parent(service_def, entry_def) + + def _update_service_entry(self, service_id, srv_entry, + name=None, description=None, + protocol_number=None, + tenant=policy_constants.POLICY_INFRA_TENANT): + entry_id = srv_entry['id'] + entry_def = policy_defs.IPProtocolServiceEntryDef( + service_id=service_id, + service_entry_id=entry_id, + tenant=tenant) + entry_def.update_attributes_in_body(body=srv_entry, name=name, + description=description, + protocol_number=protocol_number) + + class NsxPolicyNetworkApi(NsxPolicyResourceBase): """NSX Network API """ @property diff --git a/vmware_nsxlib/v3/security.py b/vmware_nsxlib/v3/security.py index f67555ea..4575057c 100644 --- a/vmware_nsxlib/v3/security.py +++ b/vmware_nsxlib/v3/security.py @@ -24,7 +24,6 @@ from oslo_log import log from oslo_log import versionutils from oslo_utils import excutils -from vmware_nsxlib.v3 import constants from vmware_nsxlib.v3 import exceptions from vmware_nsxlib.v3 import nsx_constants as consts from vmware_nsxlib.v3 import utils @@ -291,34 +290,13 @@ class NsxLibFirewallSection(utils.NsxLibApiBase): else consts.OUT ) - def _get_l4_protocol_name(self, protocol_number): - if protocol_number is None: - return - protocol_number = constants.IP_PROTOCOL_MAP.get(protocol_number, - protocol_number) - try: - protocol_number = int(protocol_number) - except ValueError: - raise exceptions.InvalidInput( - operation='create_rule', - arg_val=protocol_number, - arg_name='protocol') - if protocol_number == 6: - return consts.TCP - elif protocol_number == 17: - return consts.UDP - elif protocol_number == 1: - return consts.ICMPV4 - else: - return protocol_number - def get_nsservice(self, resource_type, **properties): service = {'resource_type': resource_type} service.update(properties) return {'service': service} def _decide_service(self, sg_rule): - l4_protocol = self._get_l4_protocol_name(sg_rule['protocol']) + l4_protocol = utils.get_l4_protocol_name(sg_rule['protocol']) if l4_protocol in [consts.TCP, consts.UDP]: # If port_range_min is not specified then we assume all ports are @@ -343,30 +321,8 @@ class NsxLibFirewallSection(utils.NsxLibApiBase): icmp_code = sg_rule['port_range_max'] icmp_strict = self.nsxlib.feature_supported( consts.FEATURE_ICMP_STRICT) - if icmp_type: - if (icmp_strict and icmp_type not in - constants.IPV4_ICMP_STRICT_TYPES): - raise exceptions.InvalidInput( - operation='create_rule', - arg_val=icmp_type, - arg_name='icmp_type') - if icmp_type not in constants.IPV4_ICMP_TYPES: - raise exceptions.InvalidInput( - operation='create_rule', - arg_val=icmp_type, - arg_name='icmp_type') - if (icmp_code and icmp_strict and icmp_code not in constants. - IPV4_ICMP_STRICT_TYPES[icmp_type]): - raise exceptions.InvalidInput( - operation='create_rule', - arg_val=icmp_code, - arg_name='icmp_code for this icmp_type') - if (icmp_code and icmp_code not in - constants.IPV4_ICMP_TYPES[icmp_type]): - raise exceptions.InvalidInput( - operation='create_rule', - arg_val=icmp_code, - arg_name='icmp_code for this icmp_type') + utils.validate_dhcp_params(icmp_type, icmp_code, icmp_version=4, + strict=icmp_strict) return self.get_nsservice( consts.ICMP_TYPE_NSSERVICE, diff --git a/vmware_nsxlib/v3/utils.py b/vmware_nsxlib/v3/utils.py index 3da6c05a..ea89230a 100644 --- a/vmware_nsxlib/v3/utils.py +++ b/vmware_nsxlib/v3/utils.py @@ -24,7 +24,9 @@ import tenacity from tenacity import _utils as tenacity_utils from vmware_nsxlib._i18n import _ +from vmware_nsxlib.v3 import constants from vmware_nsxlib.v3 import exceptions as nsxlib_exceptions +from vmware_nsxlib.v3 import nsx_constants LOG = log.getLogger(__name__) @@ -521,3 +523,57 @@ class NsxLibApiBase(object): 'tag': project_name[:MAX_TAG_LEN]}, {'scope': 'os-api-version', 'tag': self.nsxlib_config.plugin_ver}] + + +# Some utilities for services translations & validations +# both for the nsx manager & policy manager +def validate_dhcp_params(icmp_type, icmp_code, icmp_version=4, strict=False): + if icmp_version != 4: + # ICMPv6 is currently not supported + return + if icmp_type: + if (strict and icmp_type not in + constants.IPV4_ICMP_STRICT_TYPES): + raise nsxlib_exceptions.InvalidInput( + operation='create_rule', + arg_val=icmp_type, + arg_name='icmp_type') + if icmp_type not in constants.IPV4_ICMP_TYPES: + raise nsxlib_exceptions.InvalidInput( + operation='create_rule', + arg_val=icmp_type, + arg_name='icmp_type') + if (icmp_code and strict and icmp_code not in + constants.IPV4_ICMP_STRICT_TYPES[icmp_type]): + raise nsxlib_exceptions.InvalidInput( + operation='create_rule', + arg_val=icmp_code, + arg_name='icmp_code for this icmp_type') + if (icmp_code and icmp_code not in + constants.IPV4_ICMP_TYPES[icmp_type]): + raise nsxlib_exceptions.InvalidInput( + operation='create_rule', + arg_val=icmp_code, + arg_name='icmp_code for this icmp_type') + + +def get_l4_protocol_name(protocol_number): + if protocol_number is None: + return + protocol_number = constants.IP_PROTOCOL_MAP.get(protocol_number, + protocol_number) + try: + protocol_number = int(protocol_number) + except ValueError: + raise nsxlib_exceptions.InvalidInput( + operation='create_rule', + arg_val=protocol_number, + arg_name='protocol') + if protocol_number == 6: + return nsx_constants.TCP + elif protocol_number == 17: + return nsx_constants.UDP + elif protocol_number == 1: + return nsx_constants.ICMPV4 + else: + return protocol_number