NSX-V add nsx-policies extension

This extension will list/show nsx security policies, that can be used
in the security groups for the Admin policy feature

In addition, we are using this new api for policy validation in the
security group create/update

Change-Id: I66f75ae24c814c0d644f1fc4c6b9c52b24ddc77c
This commit is contained in:
Adit Sarfaty 2016-11-21 11:45:49 +02:00
parent 22b6a3f0a0
commit c9d44f5031
6 changed files with 229 additions and 34 deletions

View File

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

View File

@ -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()
@ -3115,9 +3118,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)
@ -3129,9 +3134,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):
@ -3576,6 +3583,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):

View File

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

View File

@ -989,10 +989,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):
@ -1001,3 +1008,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

View File

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

View File

@ -1342,14 +1342,28 @@ class FakeVcns(object):
msg, 120054, 'core-services')
return self.return_helper(header, response)
def get_security_policy(self, policy_id):
response_text = (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<securityPolicy><objectId>%s</objectId>"
"<name>pol1</name>"
"<description>dummy</description>"
"</securityPolicy>") % policy_id
return response_text
def get_security_policy(self, policy_id, return_xml=True):
name = 'pol1'
description = 'dummy'
if return_xml:
response_text = (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<securityPolicy><objectId>%(id)s</objectId>"
"<name>%(name)s</name>"
"<description>%(desc)s</description>"
"</securityPolicy>") % {'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}