From 2f2d770b9bafb46d5e1030d2ab150975cce4d470 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Tue, 19 Jul 2016 15:18:48 +0300 Subject: [PATCH] NSX|V add edge_ha per availability zone Support different edge_ha flag per availability zone Change-Id: Iff1b9d76a62d23d600b57ad83d868c4de2b04ee9 --- devstack/README.rst | 2 +- vmware_nsx/common/config.py | 4 +- vmware_nsx/common/exceptions.py | 5 ++ .../plugins/nsx_v/availability_zones.py | 41 ++++++++-- .../nsx_v/vshield/edge_appliance_driver.py | 13 +-- vmware_nsx/services/l2gateway/nsx_v/driver.py | 3 +- .../admin/plugins/nsxv/resources/edges.py | 6 +- .../unit/nsx_v/test_availability_zones.py | 79 +++++++++++++++++++ vmware_nsx/tests/unit/nsx_v/test_plugin.py | 8 +- 9 files changed, 135 insertions(+), 26 deletions(-) create mode 100644 vmware_nsx/tests/unit/nsx_v/test_availability_zones.py diff --git a/devstack/README.rst b/devstack/README.rst index 896a3c25f6..1d9af43c50 100644 --- a/devstack/README.rst +++ b/devstack/README.rst @@ -27,7 +27,7 @@ NSXV_PASSWORD # NSXv password. NSXV_CLUSTER_MOID # clusters ids containing OpenStack hosts. NSXV_DATACENTER_MOID # datacenter id for edge deployment. NSXV_RESOURCE_POOL_ID # resource-pool id for edge deployment. -NSXV_AVAILABILITY_ZONES # alternative resource-pools ids for edge deployment +NSXV_AVAILABILITY_ZONES # alternative resource-pools/data stores ids/edge_ha for edge deployment NSXV_DATASTORE_ID # datastore id for edge deployment. NSXV_EXTERNAL_NETWORK # id of logic switch for physical network connectivity. NSXV_VDN_SCOPE_ID # network scope id for VXLAN virtual-wires. diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index d3043d2343..0801ffec49 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -431,8 +431,8 @@ nsxv_opts = [ default=[], help=_('Optional parameter defining the availability zones ' 'for deploying NSX Edges with the format: :' - ':<(optional)HA ' - 'datastore id>.')), + ':' + '<(optional)HA datastore id>.')), cfg.StrOpt('datastore_id', deprecated_group="vcns", help=_('Optional parameter identifying the ID of datastore to ' diff --git a/vmware_nsx/common/exceptions.py b/vmware_nsx/common/exceptions.py index cab6cb528d..a12fae3838 100644 --- a/vmware_nsx/common/exceptions.py +++ b/vmware_nsx/common/exceptions.py @@ -188,3 +188,8 @@ class NsxTaaSDriverException(NsxPluginException): class NsxPortMirrorSessionMappingNotFound(n_exc.NotFound): message = _("Unable to find mapping for Tap Flow: %(tf)s") + + +class NsxInvalidConfiguration(n_exc.InvalidConfigurationOption): + message = _("An invalid value was provided for %(opt_name)s: " + "%(opt_value)s: %(reason)s") diff --git a/vmware_nsx/plugins/nsx_v/availability_zones.py b/vmware_nsx/plugins/nsx_v/availability_zones.py index e5fce79378..7e8ddc0dfc 100644 --- a/vmware_nsx/plugins/nsx_v/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v/availability_zones.py @@ -13,10 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron_lib import exceptions as n_exc from oslo_config import cfg from vmware_nsx._i18n import _ +from vmware_nsx.common import exceptions as nsx_exc DEFAULT_NAME = 'default' @@ -26,23 +26,49 @@ class ConfiguredAvailabilityZone(object): def __init__(self, config_line): if config_line: values = config_line.split(':') - if len(values) < 3 or len(values) > 4: - raise n_exc.Invalid(_("Invalid availability zones format")) + if len(values) < 4 or len(values) > 5: + raise nsx_exc.NsxInvalidConfiguration( + opt_name="availability_zones", + opt_value=config_line, + reason=_("Expected 4 or 5 values per zone")) self.name = values[0] - # field name limit in the DB + # field name size in the DB is 36 if len(self.name) > 36: - raise n_exc.Invalid(_("Invalid availability zone name %s: " - "max name length is 36"), self.name) + raise nsx_exc.NsxInvalidConfiguration( + opt_name="availability_zones", + opt_value=config_line, + reason=_("Maximum name length is 36")) self.resource_pool = values[1] self.datastore_id = values[2] - self.ha_datastore_id = values[3] if len(values) == 4 else None + + # validate the edge_ha + if values[3].lower() == "true": + self.edge_ha = True + elif values[3].lower() == "false": + self.edge_ha = False + else: + raise nsx_exc.NsxInvalidConfiguration( + opt_name="availability_zones", + opt_value=config_line, + reason=_("Expected the 4th value to be true/false")) + + # HA datastore id is relevant only with edge_ha + if not self.edge_ha and len(values) == 5: + raise nsx_exc.NsxInvalidConfiguration( + opt_name="availability_zones", + opt_value=config_line, + reason=_("Expected HA datastore ID only when edge_ha is " + "enabled")) + + self.ha_datastore_id = values[4] if len(values) == 5 else None else: # use the default configuration self.name = DEFAULT_NAME self.resource_pool = cfg.CONF.nsxv.resource_pool_id self.datastore_id = cfg.CONF.nsxv.datastore_id + self.edge_ha = cfg.CONF.nsxv.edge_ha self.ha_datastore_id = cfg.CONF.nsxv.ha_datastore_id @@ -55,7 +81,6 @@ class ConfiguredAvailabilityZones(object): for az in cfg.CONF.nsxv.availability_zones: obj = ConfiguredAvailabilityZone(az) self.availability_zones[obj.name] = obj - # DEBUG ADIT - name max len 36 (DB) # add a default entry obj = ConfiguredAvailabilityZone(None) diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py b/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py index 40d7afda77..c48cccd161 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py @@ -46,7 +46,8 @@ class EdgeApplianceDriver(object): def _assemble_edge(self, name, appliance_size="compact", deployment_container_id=None, datacenter_moid=None, enable_aesni=True, dist=False, - enable_fips=False, remote_access=False): + enable_fips=False, remote_access=False, + edge_ha=False): edge = { 'name': name, 'fqdn': None, @@ -86,7 +87,7 @@ class EdgeApplianceDriver(object): if datacenter_moid: edge['datacenterMoid'] = datacenter_moid - if not dist and cfg.CONF.nsxv.edge_ha: + if not dist and edge_ha: self._enable_high_availability(edge) return edge @@ -97,7 +98,7 @@ class EdgeApplianceDriver(object): appliances.append(self._assemble_edge_appliance( availability_zone.resource_pool, availability_zone.datastore_id)) - if availability_zone.ha_datastore_id and cfg.CONF.nsxv.edge_ha: + if availability_zone.ha_datastore_id and availability_zone.edge_ha: appliances.append(self._assemble_edge_appliance( availability_zone.resource_pool, availability_zone.ha_datastore_id)) @@ -522,7 +523,8 @@ class EdgeApplianceDriver(object): edge = self._assemble_edge( edge_name, datacenter_moid=self.datacenter_moid, deployment_container_id=self.deployment_container_id, - appliance_size=appliance_size, remote_access=False, dist=dist) + appliance_size=appliance_size, remote_access=False, dist=dist, + edge_ha=availability_zone.edge_ha) appliances = self._assemble_edge_appliances(availability_zone) if appliances: edge['appliances']['appliances'] = appliances @@ -610,7 +612,8 @@ class EdgeApplianceDriver(object): edge = self._assemble_edge( edge_name, datacenter_moid=self.datacenter_moid, deployment_container_id=self.deployment_container_id, - appliance_size=appliance_size, remote_access=False, dist=dist) + appliance_size=appliance_size, remote_access=False, dist=dist, + edge_ha=availability_zone.edge_ha) edge['id'] = edge_id appliances = self._assemble_edge_appliances(availability_zone) if appliances: diff --git a/vmware_nsx/services/l2gateway/nsx_v/driver.py b/vmware_nsx/services/l2gateway/nsx_v/driver.py index 168dc21299..8e5c617dc7 100644 --- a/vmware_nsx/services/l2gateway/nsx_v/driver.py +++ b/vmware_nsx/services/l2gateway/nsx_v/driver.py @@ -19,7 +19,6 @@ from networking_l2gw.services.l2gateway.common import constants as l2gw_const from networking_l2gw.services.l2gateway import exceptions as l2gw_exc from neutron import manager from neutron_lib import exceptions as n_exc -from oslo_config import cfg from oslo_log import log as logging from oslo_utils import uuidutils @@ -119,7 +118,7 @@ class NsxvL2GatewayDriver(l2gateway_db.L2GatewayMixin): if not edge_binding: raise nsx_exc.NsxL2GWDeviceNotFound() # Enable edge HA on the DLR - if cfg.CONF.nsxv.edge_ha: + if availability_zone.edge_ha: edge_id = edge_binding['edge_id'] self._edge_manager.nsxv_manager.update_edge_ha(edge_id) return edge_binding['edge_id'] diff --git a/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py b/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py index d2fe1098ee..7606cdcc34 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py +++ b/vmware_nsx/shell/admin/plugins/nsxv/resources/edges.py @@ -25,7 +25,6 @@ import vmware_nsx.shell.resources as shell from neutron.callbacks import registry from neutron_lib import exceptions -from oslo_config import cfg from vmware_nsx._i18n import _LE, _LI from vmware_nsx.common import nsxv_constants @@ -208,14 +207,13 @@ def change_edge_appliance(edge_id): configuration is updated, or when the configuration of a specific availability zone was updated. """ - edge_ha = cfg.CONF.nsxv.edge_ha # find out what is the current resource pool & size, so we can keep them az_name, size = _get_edge_az_and_size(edge_id) az = nsx_az.ConfiguredAvailabilityZones().get_availability_zone(az_name) appliances = [{'resourcePoolId': az.resource_pool, 'datastoreId': az.datastore_id}] - if az.ha_datastore_id and edge_ha: + if az.ha_datastore_id and az.edge_ha: appliances.append({'resourcePoolId': az.resource_pool, 'datastoreId': az.ha_datastore_id}) request = {'appliances': appliances, 'applianceSize': size} @@ -227,7 +225,7 @@ def change_edge_appliance(edge_id): LOG.error(_LE("%s"), str(e)) else: # also update the edge_ha of the edge - change_edge_ha(edge_ha, edge_id) + change_edge_ha(az.edge_ha, edge_id) @admin_utils.output_header diff --git a/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py b/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py new file mode 100644 index 0000000000..c418656886 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py @@ -0,0 +1,79 @@ +# Copyright 2016 VMware, Inc. +# 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. + +from neutron.tests import base + +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.plugins.nsx_v import availability_zones as nsx_az + + +class NsxvAvailabilityZonesTestCase(base.BaseTestCase): + + def test_simple_availability_zone(self): + az = nsx_az.ConfiguredAvailabilityZone( + "name:respool:datastore:true:hastore") + self.assertEqual("name", az.name) + self.assertEqual("respool", az.resource_pool) + self.assertEqual("datastore", az.datastore_id) + self.assertEqual(True, az.edge_ha) + self.assertEqual("hastore", az.ha_datastore_id) + + def test_availability_zone_without_ha_datastore(self): + az = nsx_az.ConfiguredAvailabilityZone( + "name:respool:datastore:true") + self.assertEqual("name", az.name) + self.assertEqual("respool", az.resource_pool) + self.assertEqual("datastore", az.datastore_id) + self.assertEqual(True, az.edge_ha) + self.assertEqual(None, az.ha_datastore_id) + + def test_availability_zone_without_edge_ha(self): + az = nsx_az.ConfiguredAvailabilityZone( + "name:respool:datastore:FALSE") + self.assertEqual("name", az.name) + self.assertEqual("respool", az.resource_pool) + self.assertEqual("datastore", az.datastore_id) + self.assertEqual(False, az.edge_ha) + self.assertEqual(None, az.ha_datastore_id) + + def test_availability_fail_long_name(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.ConfiguredAvailabilityZone, + "very-very-very-very-very-longest-name:respool:da:true:ha") + + def test_availability_fail_few_args(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.ConfiguredAvailabilityZone, + "name:respool") + + def test_availability_fail_many_args(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.ConfiguredAvailabilityZone, + "name:1:2:3:4:5:6") + + def test_availability_fail_bad_edge_ha(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.ConfiguredAvailabilityZone, + "name:respool:datastore:truex:hastore") + + def test_availability_fail_no_ha_datastore(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.ConfiguredAvailabilityZone, + "name:respool:datastore:false:hastore") diff --git a/vmware_nsx/tests/unit/nsx_v/test_plugin.py b/vmware_nsx/tests/unit/nsx_v/test_plugin.py index 7e76dc2626..2ca5ec15d1 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_v/test_plugin.py @@ -632,7 +632,7 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxVPluginV2TestCase): def test_create_network_with_az_hint(self): az_name = 'az7' - az_config = az_name + ':respool-7:datastore-7' + az_config = az_name + ':respool-7:datastore-7:False' cfg.CONF.set_override('availability_zones', [az_config], group="nsxv") p = manager.NeutronManager.get_plugin() p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones() @@ -3085,7 +3085,7 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase, def test_create_router_with_az_hint(self): az_name = 'az7' - az_config = az_name + ':respool-7:datastore-7' + az_config = az_name + ':respool-7:datastore-7:True' cfg.CONF.set_override('availability_zones', [az_config], group="nsxv") p = manager.NeutronManager.get_plugin() p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones() @@ -3379,7 +3379,7 @@ class TestVdrTestCase(L3NatTest, L3NatTestCaseBase, def _test_create_rotuer_with_az_hint(self, with_hint): # init the availability zones in the plugin az_name = 'az7' - az_config = az_name + ':respool-7:datastore-7' + az_config = az_name + ':respool-7:datastore-7:False' cfg.CONF.set_override('availability_zones', [az_config], group="nsxv") p = manager.NeutronManager.get_plugin() p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones() @@ -4584,7 +4584,7 @@ class TestSharedRouterTestCase(L3NatTest, L3NatTestCaseBase, def _test_create_rotuer_with_az_hint(self, with_hint): # init the availability zones in the plugin az_name = 'az7' - az_config = az_name + ':respool-7:datastore-7' + az_config = az_name + ':respool-7:datastore-7:True' cfg.CONF.set_override('availability_zones', [az_config], group="nsxv") p = manager.NeutronManager.get_plugin() p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()