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(