NSX|V add edge_ha per availability zone
Support different edge_ha flag per availability zone Change-Id: Iff1b9d76a62d23d600b57ad83d868c4de2b04ee9
This commit is contained in:
parent
2b7210313c
commit
2f2d770b9b
@ -27,7 +27,7 @@ NSXV_PASSWORD # NSXv password.
|
|||||||
NSXV_CLUSTER_MOID # clusters ids containing OpenStack hosts.
|
NSXV_CLUSTER_MOID # clusters ids containing OpenStack hosts.
|
||||||
NSXV_DATACENTER_MOID # datacenter id for edge deployment.
|
NSXV_DATACENTER_MOID # datacenter id for edge deployment.
|
||||||
NSXV_RESOURCE_POOL_ID # resource-pool 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_DATASTORE_ID # datastore id for edge deployment.
|
||||||
NSXV_EXTERNAL_NETWORK # id of logic switch for physical network connectivity.
|
NSXV_EXTERNAL_NETWORK # id of logic switch for physical network connectivity.
|
||||||
NSXV_VDN_SCOPE_ID # network scope id for VXLAN virtual-wires.
|
NSXV_VDN_SCOPE_ID # network scope id for VXLAN virtual-wires.
|
||||||
|
@ -431,8 +431,8 @@ nsxv_opts = [
|
|||||||
default=[],
|
default=[],
|
||||||
help=_('Optional parameter defining the availability zones '
|
help=_('Optional parameter defining the availability zones '
|
||||||
'for deploying NSX Edges with the format: <zone name>:'
|
'for deploying NSX Edges with the format: <zone name>:'
|
||||||
'<resource pool id]:<datastore id>:<(optional)HA '
|
'<resource pool id]:<datastore id>:<edge_ha True/False>'
|
||||||
'datastore id>.')),
|
'<(optional)HA datastore id>.')),
|
||||||
cfg.StrOpt('datastore_id',
|
cfg.StrOpt('datastore_id',
|
||||||
deprecated_group="vcns",
|
deprecated_group="vcns",
|
||||||
help=_('Optional parameter identifying the ID of datastore to '
|
help=_('Optional parameter identifying the ID of datastore to '
|
||||||
|
@ -188,3 +188,8 @@ class NsxTaaSDriverException(NsxPluginException):
|
|||||||
|
|
||||||
class NsxPortMirrorSessionMappingNotFound(n_exc.NotFound):
|
class NsxPortMirrorSessionMappingNotFound(n_exc.NotFound):
|
||||||
message = _("Unable to find mapping for Tap Flow: %(tf)s")
|
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")
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutron_lib import exceptions as n_exc
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from vmware_nsx._i18n import _
|
from vmware_nsx._i18n import _
|
||||||
|
from vmware_nsx.common import exceptions as nsx_exc
|
||||||
|
|
||||||
DEFAULT_NAME = 'default'
|
DEFAULT_NAME = 'default'
|
||||||
|
|
||||||
@ -26,23 +26,49 @@ class ConfiguredAvailabilityZone(object):
|
|||||||
def __init__(self, config_line):
|
def __init__(self, config_line):
|
||||||
if config_line:
|
if config_line:
|
||||||
values = config_line.split(':')
|
values = config_line.split(':')
|
||||||
if len(values) < 3 or len(values) > 4:
|
if len(values) < 4 or len(values) > 5:
|
||||||
raise n_exc.Invalid(_("Invalid availability zones format"))
|
raise nsx_exc.NsxInvalidConfiguration(
|
||||||
|
opt_name="availability_zones",
|
||||||
|
opt_value=config_line,
|
||||||
|
reason=_("Expected 4 or 5 values per zone"))
|
||||||
|
|
||||||
self.name = values[0]
|
self.name = values[0]
|
||||||
# field name limit in the DB
|
# field name size in the DB is 36
|
||||||
if len(self.name) > 36:
|
if len(self.name) > 36:
|
||||||
raise n_exc.Invalid(_("Invalid availability zone name %s: "
|
raise nsx_exc.NsxInvalidConfiguration(
|
||||||
"max name length is 36"), self.name)
|
opt_name="availability_zones",
|
||||||
|
opt_value=config_line,
|
||||||
|
reason=_("Maximum name length is 36"))
|
||||||
|
|
||||||
self.resource_pool = values[1]
|
self.resource_pool = values[1]
|
||||||
self.datastore_id = values[2]
|
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:
|
else:
|
||||||
# use the default configuration
|
# use the default configuration
|
||||||
self.name = DEFAULT_NAME
|
self.name = DEFAULT_NAME
|
||||||
self.resource_pool = cfg.CONF.nsxv.resource_pool_id
|
self.resource_pool = cfg.CONF.nsxv.resource_pool_id
|
||||||
self.datastore_id = cfg.CONF.nsxv.datastore_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
|
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:
|
for az in cfg.CONF.nsxv.availability_zones:
|
||||||
obj = ConfiguredAvailabilityZone(az)
|
obj = ConfiguredAvailabilityZone(az)
|
||||||
self.availability_zones[obj.name] = obj
|
self.availability_zones[obj.name] = obj
|
||||||
# DEBUG ADIT - name max len 36 (DB)
|
|
||||||
|
|
||||||
# add a default entry
|
# add a default entry
|
||||||
obj = ConfiguredAvailabilityZone(None)
|
obj = ConfiguredAvailabilityZone(None)
|
||||||
|
@ -46,7 +46,8 @@ class EdgeApplianceDriver(object):
|
|||||||
def _assemble_edge(self, name, appliance_size="compact",
|
def _assemble_edge(self, name, appliance_size="compact",
|
||||||
deployment_container_id=None, datacenter_moid=None,
|
deployment_container_id=None, datacenter_moid=None,
|
||||||
enable_aesni=True, dist=False,
|
enable_aesni=True, dist=False,
|
||||||
enable_fips=False, remote_access=False):
|
enable_fips=False, remote_access=False,
|
||||||
|
edge_ha=False):
|
||||||
edge = {
|
edge = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'fqdn': None,
|
'fqdn': None,
|
||||||
@ -86,7 +87,7 @@ class EdgeApplianceDriver(object):
|
|||||||
if datacenter_moid:
|
if datacenter_moid:
|
||||||
edge['datacenterMoid'] = 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)
|
self._enable_high_availability(edge)
|
||||||
|
|
||||||
return edge
|
return edge
|
||||||
@ -97,7 +98,7 @@ class EdgeApplianceDriver(object):
|
|||||||
appliances.append(self._assemble_edge_appliance(
|
appliances.append(self._assemble_edge_appliance(
|
||||||
availability_zone.resource_pool,
|
availability_zone.resource_pool,
|
||||||
availability_zone.datastore_id))
|
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(
|
appliances.append(self._assemble_edge_appliance(
|
||||||
availability_zone.resource_pool,
|
availability_zone.resource_pool,
|
||||||
availability_zone.ha_datastore_id))
|
availability_zone.ha_datastore_id))
|
||||||
@ -522,7 +523,8 @@ class EdgeApplianceDriver(object):
|
|||||||
edge = self._assemble_edge(
|
edge = self._assemble_edge(
|
||||||
edge_name, datacenter_moid=self.datacenter_moid,
|
edge_name, datacenter_moid=self.datacenter_moid,
|
||||||
deployment_container_id=self.deployment_container_id,
|
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)
|
appliances = self._assemble_edge_appliances(availability_zone)
|
||||||
if appliances:
|
if appliances:
|
||||||
edge['appliances']['appliances'] = appliances
|
edge['appliances']['appliances'] = appliances
|
||||||
@ -610,7 +612,8 @@ class EdgeApplianceDriver(object):
|
|||||||
edge = self._assemble_edge(
|
edge = self._assemble_edge(
|
||||||
edge_name, datacenter_moid=self.datacenter_moid,
|
edge_name, datacenter_moid=self.datacenter_moid,
|
||||||
deployment_container_id=self.deployment_container_id,
|
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
|
edge['id'] = edge_id
|
||||||
appliances = self._assemble_edge_appliances(availability_zone)
|
appliances = self._assemble_edge_appliances(availability_zone)
|
||||||
if appliances:
|
if appliances:
|
||||||
|
@ -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 networking_l2gw.services.l2gateway import exceptions as l2gw_exc
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
from neutron_lib import exceptions as n_exc
|
from neutron_lib import exceptions as n_exc
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
@ -119,7 +118,7 @@ class NsxvL2GatewayDriver(l2gateway_db.L2GatewayMixin):
|
|||||||
if not edge_binding:
|
if not edge_binding:
|
||||||
raise nsx_exc.NsxL2GWDeviceNotFound()
|
raise nsx_exc.NsxL2GWDeviceNotFound()
|
||||||
# Enable edge HA on the DLR
|
# Enable edge HA on the DLR
|
||||||
if cfg.CONF.nsxv.edge_ha:
|
if availability_zone.edge_ha:
|
||||||
edge_id = edge_binding['edge_id']
|
edge_id = edge_binding['edge_id']
|
||||||
self._edge_manager.nsxv_manager.update_edge_ha(edge_id)
|
self._edge_manager.nsxv_manager.update_edge_ha(edge_id)
|
||||||
return edge_binding['edge_id']
|
return edge_binding['edge_id']
|
||||||
|
@ -25,7 +25,6 @@ import vmware_nsx.shell.resources as shell
|
|||||||
|
|
||||||
from neutron.callbacks import registry
|
from neutron.callbacks import registry
|
||||||
from neutron_lib import exceptions
|
from neutron_lib import exceptions
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from vmware_nsx._i18n import _LE, _LI
|
from vmware_nsx._i18n import _LE, _LI
|
||||||
from vmware_nsx.common import nsxv_constants
|
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
|
configuration is updated, or when the configuration of a specific
|
||||||
availability zone was updated.
|
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
|
# 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_name, size = _get_edge_az_and_size(edge_id)
|
||||||
az = nsx_az.ConfiguredAvailabilityZones().get_availability_zone(az_name)
|
az = nsx_az.ConfiguredAvailabilityZones().get_availability_zone(az_name)
|
||||||
appliances = [{'resourcePoolId': az.resource_pool,
|
appliances = [{'resourcePoolId': az.resource_pool,
|
||||||
'datastoreId': az.datastore_id}]
|
'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,
|
appliances.append({'resourcePoolId': az.resource_pool,
|
||||||
'datastoreId': az.ha_datastore_id})
|
'datastoreId': az.ha_datastore_id})
|
||||||
request = {'appliances': appliances, 'applianceSize': size}
|
request = {'appliances': appliances, 'applianceSize': size}
|
||||||
@ -227,7 +225,7 @@ def change_edge_appliance(edge_id):
|
|||||||
LOG.error(_LE("%s"), str(e))
|
LOG.error(_LE("%s"), str(e))
|
||||||
else:
|
else:
|
||||||
# also update the edge_ha of the edge
|
# 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
|
@admin_utils.output_header
|
||||||
|
79
vmware_nsx/tests/unit/nsx_v/test_availability_zones.py
Normal file
79
vmware_nsx/tests/unit/nsx_v/test_availability_zones.py
Normal file
@ -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")
|
@ -632,7 +632,7 @@ class TestNetworksV2(test_plugin.TestNetworksV2, NsxVPluginV2TestCase):
|
|||||||
|
|
||||||
def test_create_network_with_az_hint(self):
|
def test_create_network_with_az_hint(self):
|
||||||
az_name = 'az7'
|
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")
|
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
||||||
p = manager.NeutronManager.get_plugin()
|
p = manager.NeutronManager.get_plugin()
|
||||||
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
||||||
@ -3085,7 +3085,7 @@ class TestExclusiveRouterTestCase(L3NatTest, L3NatTestCaseBase,
|
|||||||
|
|
||||||
def test_create_router_with_az_hint(self):
|
def test_create_router_with_az_hint(self):
|
||||||
az_name = 'az7'
|
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")
|
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
||||||
p = manager.NeutronManager.get_plugin()
|
p = manager.NeutronManager.get_plugin()
|
||||||
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
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):
|
def _test_create_rotuer_with_az_hint(self, with_hint):
|
||||||
# init the availability zones in the plugin
|
# init the availability zones in the plugin
|
||||||
az_name = 'az7'
|
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")
|
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
||||||
p = manager.NeutronManager.get_plugin()
|
p = manager.NeutronManager.get_plugin()
|
||||||
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
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):
|
def _test_create_rotuer_with_az_hint(self, with_hint):
|
||||||
# init the availability zones in the plugin
|
# init the availability zones in the plugin
|
||||||
az_name = 'az7'
|
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")
|
cfg.CONF.set_override('availability_zones', [az_config], group="nsxv")
|
||||||
p = manager.NeutronManager.get_plugin()
|
p = manager.NeutronManager.get_plugin()
|
||||||
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
p._availability_zones_data = nsx_az.ConfiguredAvailabilityZones()
|
||||||
|
Loading…
Reference in New Issue
Block a user