From 3ac633ec6845e02fc15c8a8c13d42d83b7b96e8f Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Fri, 27 Jan 2017 22:23:06 +0200 Subject: [PATCH] NSXv: Edge random placement Support randomly selecting which will be the primary datastore and which will be the secondary one when deplying an edge, in order to balance the load. This new option is available globally as well as per availability_zone via a new configuration parameter edge_placement_random which will be False by default. Change-Id: I5bf8f8999100c4c6da4645bda6e74165575c3818 --- ...dge-random-placement-9534371967edec8f.yaml | 9 ++++++ vmware_nsx/common/config.py | 12 ++++++++ .../plugins/nsx_v/availability_zones.py | 9 ++++++ .../nsx_v/vshield/edge_appliance_driver.py | 30 +++++++++++++++---- .../unit/nsx_v/test_availability_zones.py | 24 ++++++++++++++- 5 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/nsxv-edge-random-placement-9534371967edec8f.yaml diff --git a/releasenotes/notes/nsxv-edge-random-placement-9534371967edec8f.yaml b/releasenotes/notes/nsxv-edge-random-placement-9534371967edec8f.yaml new file mode 100644 index 0000000000..206c9c2d3a --- /dev/null +++ b/releasenotes/notes/nsxv-edge-random-placement-9534371967edec8f.yaml @@ -0,0 +1,9 @@ +--- +prelude: > + Support randomly selecting which will be the primary datastore and which + will be the secondary one when deplying an edge, in order to balance the load. + This new option is available globally as well as per availability_zone. +features: + - | + Support randomly selecting which will be the primary datastore and which + will be the secondary one when deplying an edge, in order to balance the load. diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index c531e58e5e..cc43c47742 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -461,6 +461,12 @@ nsxv_opts = [ help=_('Optional parameter identifying the ID of datastore to ' 'deploy NSX Edges in addition to data_store_id in case' 'edge_ha is True')), + cfg.BoolOpt('ha_placement_random', + default=False, + help=_('When True and in case edge_ha is True, half of the ' + 'edges will be placed in the primary datastore as ' + 'active and the other half will be placed in the ' + 'ha_datastore')), cfg.StrOpt('external_network', deprecated_group="vcns", help=_('(Required) Network ID for physical network ' @@ -663,6 +669,12 @@ nsxv_az_opts = [ help=_('Optional parameter identifying the ID of datastore to ' 'deploy NSX Edges in addition to data_store_id in case' 'edge_ha is True')), + cfg.BoolOpt('ha_placement_random', + help=_('When True and in case edge_ha is True, half of the ' + 'edges will be placed in the primary datastore as ' + 'active and the other half will be placed in the ' + 'ha_datastore. If this value is not set, the global ' + 'one will be used')), ] # Register the configuration options diff --git a/vmware_nsx/plugins/nsx_v/availability_zones.py b/vmware_nsx/plugins/nsx_v/availability_zones.py index 675f402fed..941e1bf119 100644 --- a/vmware_nsx/plugins/nsx_v/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v/availability_zones.py @@ -66,6 +66,8 @@ class ConfiguredAvailabilityZone(object): "enabled")) self.ha_datastore_id = values[4] if len(values) == 5 else None + # Use the global configuration for ha_placement_random + self.ha_placement_random = cfg.CONF.nsxv.ha_placement_random elif config_line: # Newer configuration - the name of the availability zone can be # used to get the rest of the configuration for this AZ @@ -89,6 +91,12 @@ class ConfiguredAvailabilityZone(object): # The HA datastore can be empty self.ha_datastore_id = (az_info.get('ha_datastore_id') if self.edge_ha else None) + + # Use the global config for ha_placement_random if not set + self.ha_placement_random = az_info.get('ha_placement_random') + if self.ha_placement_random is None: + self.ha_placement_random = ( + cfg.CONF.nsxv.ha_placement_random) else: # use the default configuration self.name = DEFAULT_NAME @@ -96,6 +104,7 @@ class ConfiguredAvailabilityZone(object): 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 + self.ha_placement_random = cfg.CONF.nsxv.ha_placement_random class ConfiguredAvailabilityZones(object): 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 f5c120257c..ec8dfd1f65 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py +++ b/vmware_nsx/plugins/nsx_v/vshield/edge_appliance_driver.py @@ -13,6 +13,7 @@ # under the License. from distutils import version +import random import time from neutron.plugins.common import constants as plugin_const @@ -45,6 +46,7 @@ class EdgeApplianceDriver(object): 'nat': {}, 'route': {}, } + random.seed() def _assemble_edge(self, name, appliance_size="compact", deployment_container_id=None, datacenter_moid=None, @@ -95,16 +97,34 @@ class EdgeApplianceDriver(object): return edge + def _select_datastores(self, availability_zone): + primary_ds = availability_zone.datastore_id + secondary_ds = availability_zone.ha_datastore_id + if availability_zone.ha_placement_random: + # we want to switch primary and secondary datastores + # half of the times, to balance it + if random.random() > 0.5: + primary_ds = availability_zone.ha_datastore_id + secondary_ds = availability_zone.datastore_id + return primary_ds, secondary_ds + def _assemble_edge_appliances(self, availability_zone): appliances = [] - if availability_zone.datastore_id: + if availability_zone.ha_datastore_id and availability_zone.edge_ha: + # create appliance with HA + primary_ds, secondary_ds = self._select_datastores( + availability_zone) + appliances.append(self._assemble_edge_appliance( + availability_zone.resource_pool, + primary_ds)) + appliances.append(self._assemble_edge_appliance( + availability_zone.resource_pool, + secondary_ds)) + elif availability_zone.datastore_id: + # Single datastore appliances.append(self._assemble_edge_appliance( availability_zone.resource_pool, availability_zone.datastore_id)) - 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)) return appliances def _assemble_edge_appliance(self, resource_pool_id, datastore_id): diff --git a/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py b/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py index 24a2221de8..622c21b405 100644 --- a/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py +++ b/vmware_nsx/tests/unit/nsx_v/test_availability_zones.py @@ -29,9 +29,11 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): self.az_name = 'zone1' self.group_name = 'az:%s' % self.az_name config.register_nsxv_azs(cfg.CONF, [self.az_name]) + cfg.CONF.set_override("ha_placement_random", True, group="nsxv") def _config_az(self, resource_pool_id="respool", datastore_id="datastore", - edge_ha=True, ha_datastore_id="hastore"): + edge_ha=True, ha_datastore_id="hastore", + ha_placement_random=False): cfg.CONF.set_override("resource_pool_id", resource_pool_id, group=self.group_name) cfg.CONF.set_override("datastore_id", datastore_id, @@ -41,6 +43,10 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): group=self.group_name) cfg.CONF.set_override("ha_datastore_id", ha_datastore_id, group=self.group_name) + if ha_placement_random is not None: + cfg.CONF.set_override("ha_placement_random", + ha_placement_random, + group=self.group_name) def test_simple_availability_zone(self): self._config_az() @@ -50,6 +56,7 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): self.assertEqual("datastore", az.datastore_id) self.assertEqual(True, az.edge_ha) self.assertEqual("hastore", az.ha_datastore_id) + self.assertEqual(False, az.ha_placement_random) def test_availability_zone_no_edge_ha(self): self._config_az(edge_ha=False) @@ -59,6 +66,7 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): self.assertEqual("datastore", az.datastore_id) self.assertEqual(False, az.edge_ha) self.assertEqual(None, az.ha_datastore_id) + self.assertEqual(False, az.ha_placement_random) def test_availability_zone_no_ha_datastore(self): self._config_az(ha_datastore_id=None) @@ -68,6 +76,7 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): self.assertEqual("datastore", az.datastore_id) self.assertEqual(True, az.edge_ha) self.assertEqual(None, az.ha_datastore_id) + self.assertEqual(False, az.ha_placement_random) def test_missing_group_section(self): self.assertRaises( @@ -97,6 +106,18 @@ class NsxvAvailabilityZonesTestCase(base.BaseTestCase): self.assertEqual("datastore", az.datastore_id) self.assertEqual(False, az.edge_ha) self.assertEqual(None, az.ha_datastore_id) + self.assertEqual(False, az.ha_placement_random) + + def test_availability_zone_missing_edge_placement(self): + self._config_az(ha_placement_random=None) + az = nsx_az.ConfiguredAvailabilityZone(self.az_name) + self.assertEqual(self.az_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) + # ha_placement_random should have the global value + self.assertEqual(True, az.ha_placement_random) class NsxvAvailabilityZonesOldTestCase(base.BaseTestCase): @@ -113,6 +134,7 @@ class NsxvAvailabilityZonesOldTestCase(base.BaseTestCase): self.assertEqual("datastore", az.datastore_id) self.assertEqual(True, az.edge_ha) self.assertEqual("hastore", az.ha_datastore_id) + self.assertEqual(False, az.ha_placement_random) def test_availability_zone_without_ha_datastore(self): az = nsx_az.ConfiguredAvailabilityZone(