diff --git a/vmware_nsx/extensions/nsxpolicy.py b/vmware_nsx/extensions/nsxpolicy.py new file mode 100644 index 0000000000..d0fed28af7 --- /dev/null +++ b/vmware_nsx/extensions/nsxpolicy.py @@ -0,0 +1,107 @@ +# Copyright 2016 VMware. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import abc + +from neutron.api import extensions +from neutron.api.v2 import resource_helper +from neutron_lib import exceptions as nexception + +from vmware_nsx._i18n import _ + +POLICY_RESOURCE_NAME = "nsx_policy" +# Use dash for alias and collection name +EXT_ALIAS = POLICY_RESOURCE_NAME.replace('_', '-') +NSX_POLICIES = "nsx_policies" + +# The nsx-policies table is read only +RESOURCE_ATTRIBUTE_MAP = { + NSX_POLICIES: { + 'id': { + 'allow_post': False, 'allow_put': False, 'is_visible': True}, + 'name': { + 'allow_post': False, 'allow_put': False, 'is_visible': True}, + 'description': { + 'allow_post': False, 'allow_put': False, 'is_visible': True}, + } +} + + +class Nsxpolicy(extensions.ExtensionDescriptor): + """API extension for NSX policies.""" + + @classmethod + def get_name(cls): + return "NSX Policy" + + @classmethod + def get_alias(cls): + return EXT_ALIAS + + @classmethod + def get_description(cls): + return "NSX security policies." + + @classmethod + def get_updated(cls): + return "2016-11-20T00:00:00-00:00" + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + plural_mappings = resource_helper.build_plural_mappings( + {}, RESOURCE_ATTRIBUTE_MAP) + member_actions = {} + return resource_helper.build_resource_info(plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + None, + action_map=member_actions, + register_quota=True, + translate_name=True) + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + +class NsxPolicyReadOnly(nexception.NotAuthorized): + message = _("NSX policies are read-only.") + + +class NsxPolicyPluginBase(object): + + @abc.abstractmethod + def create_nsx_policy(self, context, nsx_policy): + raise NsxPolicyReadOnly() + + @abc.abstractmethod + def update_nsx_policy(self, context, id, nsx_policy): + raise NsxPolicyReadOnly() + + @abc.abstractmethod + def get_nsx_policy(self, context, id, fields=None): + pass + + @abc.abstractmethod + def delete_nsx_policy(self, context, id): + raise NsxPolicyReadOnly() + + @abc.abstractmethod + def get_nsx_policies(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + pass diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 2e6a6e093a..3765cd8bb1 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -20,6 +20,7 @@ import uuid import netaddr from neutron_lib.api import validators from neutron_lib import constants +from neutron_lib.db import constants as db_const from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from oslo_config import cfg @@ -98,6 +99,7 @@ from vmware_nsx.extensions import ( vnicindex as ext_vnic_idx) from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain +from vmware_nsx.extensions import nsxpolicy from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import routersize from vmware_nsx.extensions import secgroup_rule_local_ip_prefix @@ -136,7 +138,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, securitygroups_db.SecurityGroupDbMixin, extended_secgroup.ExtendedSecurityGroupPropertiesMixin, vnic_index_db.VnicIndexDbMixin, - dns_db.DNSDbMixin): + dns_db.DNSDbMixin, nsxpolicy.NsxPolicyPluginBase): supported_extension_aliases = ["agent", "allowed-address-pairs", @@ -227,6 +229,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self._use_nsx_policies = True # enable the extension self.supported_extension_aliases.append("security-group-policy") + self.supported_extension_aliases.append("nsx-policy") self.sg_container_id = self._create_security_group_container() self.default_section = self._create_cluster_default_fw_section() @@ -3119,9 +3122,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, 'group %s') % id) raise n_exc.InvalidInput(error_message=msg) - # validate that the new policy exists - if new_policy and not self.nsx_v.vcns.validate_inventory( - new_policy): + # validate that the new policy exists (and not hidden) by using the + # plugin getter that raises an exception if it fails. + try: + new_policy = self.get_nsx_policy(context, new_policy) + except n_exc.ObjectNotFound: msg = _('Policy %s was not found on the NSX') % new_policy raise n_exc.InvalidInput(error_message=msg) @@ -3133,9 +3138,11 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Use the NSX policy description as the description of this # security group if the description was not set by the user # and the security group is new or policy was updated + # if the nsx policy has not description - use its name if new_policy and not security_group.get('description'): security_group['description'] = ( - self.nsx_sg_utils.get_nsx_policy_description(new_policy)) + new_policy.get('description') or + new_policy.get('name'))[:db_const.DESCRIPTION_FIELD_SIZE] else: # must not have a policy: if security_group.get(sg_policy.POLICY): @@ -3580,6 +3587,34 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, def get_default_az(self): return self._availability_zones_data.get_default_availability_zone() + def _nsx_policy_is_hidden(self, policy): + for attrib in policy.get('extendedAttributes', []): + if (attrib['name'].lower() == 'ishidden' and + attrib['value'].lower() == 'true'): + return True + return False + + def _nsx_policy_to_dict(self, policy): + return {'id': policy['objectId'], + 'name': policy.get('name'), + 'description': policy.get('description')} + + def get_nsx_policy(self, context, id, fields=None): + policy = self.nsx_v.vcns.get_security_policy(id, return_xml=False) + if self._nsx_policy_is_hidden(policy): + raise n_exc.ObjectNotFound(id=id) + return self._nsx_policy_to_dict(policy) + + def get_nsx_policies(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + policies = self.nsx_v.vcns.get_security_policies() + results = [] + for policy in policies.get('policies', []): + if not self._nsx_policy_is_hidden(policy): + results.append(self._nsx_policy_to_dict(policy)) + return results + # Register the callback def _validate_network_has_subnet(resource, event, trigger, **kwargs): diff --git a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py index 63fd6a1955..894251fbd0 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py +++ b/vmware_nsx/plugins/nsx_v/vshield/securitygroup_utils.py @@ -15,7 +15,6 @@ import xml.etree.ElementTree as et -from neutron_lib.db import constants as db_const from oslo_log import log as logging from vmware_nsx.common import utils @@ -203,16 +202,3 @@ class NsxSecurityGroupUtils(object): return self.nsxv_manager.vcns.update_security_policy( policy_id, et.tostring(policy)) - - def get_nsx_policy_description(self, policy_id): - if not policy_id: - return - # Get the policy configuration - policy = self.nsxv_manager.vcns.get_security_policy(policy_id) - policy = utils.normalize_xml(policy) - # If no description - use the name instead - description = policy.find('description').text - if not description: - description = policy.find('name').text - # use only the allowed length - return description[:db_const.DESCRIPTION_FIELD_SIZE] diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py index 05196b8646..2bc2d8d4d2 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py @@ -1001,10 +1001,17 @@ class Vcns(object): 'ipaddresses', ip_addr) return self.do_request(HTTP_DELETE, uri) - def get_security_policy(self, policy_id): - # get the policy configuration as an xml string + def get_security_policy(self, policy_id, return_xml=True): + # get the policy configuration as an xml string / dictionary uri = '%s/%s' % (SECURITY_POLICY_PREFIX, policy_id) - h, policy = self.do_request(HTTP_GET, uri, format='xml', decode=False) + if return_xml: + format = 'xml' + decode = False + else: + format = 'json' + decode = True + h, policy = self.do_request(HTTP_GET, uri, format=format, + decode=decode) return policy def update_security_policy(self, policy_id, request): @@ -1013,3 +1020,9 @@ class Vcns(object): return self.do_request(HTTP_PUT, uri, request, format='xml', decode=False, encode=True) + + def get_security_policies(self): + # get the policies configuration dictionary + uri = '%s/all' % (SECURITY_POLICY_PREFIX) + h, policies = self.do_request(HTTP_GET, uri, decode=True) + return policies diff --git a/vmware_nsx/tests/unit/extensions/test_security_group_policy.py b/vmware_nsx/tests/unit/extensions/test_security_group_policy.py index 3f4f0c407e..8cb483bbb4 100644 --- a/vmware_nsx/tests/unit/extensions/test_security_group_policy.py +++ b/vmware_nsx/tests/unit/extensions/test_security_group_policy.py @@ -18,9 +18,12 @@ import webob.exc from neutron.api.v2 import attributes as attr from neutron import context +from neutron.tests.unit.api import test_extensions from neutron.tests.unit.extensions import test_securitygroup from neutron_lib import constants +from neutron_lib import exceptions as n_exc +from vmware_nsx.extensions import nsxpolicy from vmware_nsx.extensions import securitygrouplogging as ext_logging from vmware_nsx.extensions import securitygrouppolicy as ext_policy from vmware_nsx.tests.unit.nsx_v import test_plugin @@ -89,10 +92,9 @@ class SecGroupPolicyExtensionTestCase( self.assertEqual(400, res.status_int) def test_secgroup_create_with_illegal_policy(self): - with mock.patch.object(fake_vcns.FakeVcns, - 'validate_inventory', - return_value=False): - policy_id = 'bad-policy' + policy_id = 'bad-policy' + with mock.patch(PLUGIN_NAME + '.get_nsx_policy', + side_effect=n_exc.ObjectNotFound(id=policy_id)): res = self._create_secgroup_with_policy(policy_id) self.assertEqual(400, res.status_int) @@ -211,3 +213,41 @@ class SecGroupPolicyExtensionTestCaseWithRules( self.assertEqual( sg['security_group']['id'], rule_data['security_group_rule']['security_group_id']) + + +class NsxPolExtensionManager(object): + + def get_resources(self): + return nsxpolicy.Nsxpolicy.get_resources() + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + +class TestNsxPolicies(test_plugin.NsxVPluginV2TestCase): + + def setUp(self, plugin=None): + super(TestNsxPolicies, self).setUp() + ext_mgr = NsxPolExtensionManager() + self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr) + + def test_get_policy(self): + id = 'policy-1' + req = self.new_show_request('nsx-policies', id) + res = self.deserialize( + self.fmt, req.get_response(self.ext_api) + ) + policy = res['nsx_policy'] + self.assertEqual(id, policy['id']) + + def test_list_policies(self): + req = self.new_list_request('nsx-policies') + res = self.deserialize( + self.fmt, req.get_response(self.ext_api) + ) + self.assertIn('nsx_policies', res) + # the fake_vcns api returns 3 policies + self.assertEqual(3, len(res['nsx_policies'])) diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py index fddfa14c43..daf459e00f 100644 --- a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py +++ b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py @@ -1366,14 +1366,28 @@ class FakeVcns(object): msg, 120054, 'core-services') return self.return_helper(header, response) - def get_security_policy(self, policy_id): - response_text = ( - "" - "%s" - "pol1" - "dummy" - "") % policy_id - return response_text + def get_security_policy(self, policy_id, return_xml=True): + name = 'pol1' + description = 'dummy' + if return_xml: + response_text = ( + "" + "%(id)s" + "%(name)s" + "%(desc)s" + "") % {'id': policy_id, 'name': name, + 'desc': description} + return response_text + else: + return {'objectId': policy_id, + 'name': name, + 'description': description} def update_security_policy(self, policy_id, request): pass + + def get_security_policies(self): + policies = [] + for id in ['policy-1', 'policy-2', 'policy-3']: + policies.append(self.get_security_policy(id, return_xml=False)) + return {'policies': policies}