Add availability_zone support base
This patch adds the availability_zone attribute to agents and supports availability_zone API. Availability_zone support for resources (network/router) and the schedulers are included in subsequent patches. APIImpact DocImpact Co-Authored-By: IWAMOTO Toshihiro <iwamoto@valinux.co.jp> Change-Id: Id7a62000ab0484412b3970199df8c374568fe70d Partially-implements: blueprint add-availability-zone
This commit is contained in:
parent
afabab7fd1
commit
9c2c3021a6
|
@ -706,6 +706,9 @@
|
|||
# exit - Exits the agent
|
||||
# check_child_processes_action = respawn
|
||||
|
||||
# Availability zone of this node.
|
||||
# availability_zone = nova
|
||||
|
||||
# =========== items for agent management extension =============
|
||||
# seconds between nodes reporting state to server; should be less than
|
||||
# agent_down_time, best if it is half or less than agent_down_time
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import os
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_config import types
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.common import config
|
||||
|
@ -75,6 +76,37 @@ PROCESS_MONITOR_OPTS = [
|
|||
]
|
||||
|
||||
|
||||
# TODO(hichihara): Remove these two classes, once oslo fixes types.string
|
||||
# and cfg.StrOpt.
|
||||
class LengthString(types.String):
|
||||
def __init__(self, maxlen=None):
|
||||
super(LengthString, self).__init__()
|
||||
self.maxlen = maxlen
|
||||
|
||||
def __call__(self, value):
|
||||
value = super(LengthString, self).__call__(value)
|
||||
if self.maxlen and len(value) > self.maxlen:
|
||||
raise ValueError(_("String value '%(value)s' exceeds max length "
|
||||
"%(len)d") % {'value': value,
|
||||
'len': self.maxlen})
|
||||
return value
|
||||
|
||||
|
||||
class LengthStrOpt(cfg.Opt):
|
||||
def __init__(self, name, maxlen=None, **kwargs):
|
||||
super(LengthStrOpt, self).__init__(name,
|
||||
type=LengthString(maxlen=maxlen),
|
||||
**kwargs)
|
||||
|
||||
|
||||
AVAILABILITY_ZONE_OPTS = [
|
||||
# The default AZ name "nova" is selected to match the default
|
||||
# AZ name in Nova and Cinder.
|
||||
LengthStrOpt('availability_zone', maxlen=255, default='nova',
|
||||
help=_("Availability zone of this node")),
|
||||
]
|
||||
|
||||
|
||||
def get_log_args(conf, log_file_name, **kwargs):
|
||||
cmd_args = []
|
||||
if conf.debug:
|
||||
|
@ -128,6 +160,10 @@ def register_process_monitor_opts(conf):
|
|||
conf.register_opts(PROCESS_MONITOR_OPTS, 'AGENT')
|
||||
|
||||
|
||||
def register_availability_zone_opts_helper(conf):
|
||||
conf.register_opts(AVAILABILITY_ZONE_OPTS, 'AGENT')
|
||||
|
||||
|
||||
def get_root_helper(conf):
|
||||
return conf.AGENT.root_helper
|
||||
|
||||
|
|
|
@ -548,6 +548,7 @@ class DhcpAgentWithStateReport(DhcpAgent):
|
|||
self.agent_state = {
|
||||
'binary': 'neutron-dhcp-agent',
|
||||
'host': host,
|
||||
'availability_zone': self.conf.AGENT.availability_zone,
|
||||
'topic': topics.DHCP_AGENT,
|
||||
'configurations': {
|
||||
'dhcp_driver': self.conf.dhcp_driver,
|
||||
|
|
|
@ -32,6 +32,7 @@ def register_options(conf):
|
|||
config.register_interface_driver_opts_helper(conf)
|
||||
config.register_use_namespaces_opts_helper(conf)
|
||||
config.register_agent_state_opts_helper(conf)
|
||||
config.register_availability_zone_opts_helper(conf)
|
||||
conf.register_opts(dhcp_config.DHCP_AGENT_OPTS)
|
||||
conf.register_opts(dhcp_config.DHCP_OPTS)
|
||||
conf.register_opts(dhcp_config.DNSMASQ_OPTS)
|
||||
|
|
|
@ -615,6 +615,7 @@ class L3NATAgentWithStateReport(L3NATAgent):
|
|||
self.agent_state = {
|
||||
'binary': 'neutron-l3-agent',
|
||||
'host': host,
|
||||
'availability_zone': self.conf.AGENT.availability_zone,
|
||||
'topic': topics.L3_AGENT,
|
||||
'configurations': {
|
||||
'agent_mode': self.conf.agent_mode,
|
||||
|
|
|
@ -40,6 +40,7 @@ def register_opts(conf):
|
|||
config.register_agent_state_opts_helper(conf)
|
||||
conf.register_opts(interface.OPTS)
|
||||
conf.register_opts(external_process.OPTS)
|
||||
config.register_availability_zone_opts_helper(conf)
|
||||
|
||||
|
||||
def main(manager='neutron.agent.l3.agent.L3NATAgentWithStateReport'):
|
||||
|
|
|
@ -20,6 +20,7 @@ from oslo_log import log as logging
|
|||
import oslo_messaging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import exc
|
||||
from sqlalchemy import sql
|
||||
|
@ -29,6 +30,7 @@ from neutron.common import constants
|
|||
from neutron.db import model_base
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import agent as ext_agent
|
||||
from neutron.extensions import availability_zone as az_ext
|
||||
from neutron.i18n import _LE, _LI, _LW
|
||||
from neutron import manager
|
||||
|
||||
|
@ -81,6 +83,7 @@ class Agent(model_base.BASEV2, models_v2.HasId):
|
|||
topic = sa.Column(sa.String(255), nullable=False)
|
||||
# TOPIC.host is a target topic
|
||||
host = sa.Column(sa.String(255), nullable=False)
|
||||
availability_zone = sa.Column(sa.String(255))
|
||||
admin_state_up = sa.Column(sa.Boolean, default=True,
|
||||
server_default=sql.true(), nullable=False)
|
||||
# the time when first report came from agents
|
||||
|
@ -101,7 +104,60 @@ class Agent(model_base.BASEV2, models_v2.HasId):
|
|||
return not AgentDbMixin.is_agent_down(self.heartbeat_timestamp)
|
||||
|
||||
|
||||
class AgentDbMixin(ext_agent.AgentPluginBase):
|
||||
class AgentAvailabilityZoneMixin(az_ext.AvailabilityZonePluginBase):
|
||||
"""Mixin class to add availability_zone extension to AgentDbMixin."""
|
||||
|
||||
def _list_availability_zones(self, context, filters=None):
|
||||
result = {}
|
||||
query = self._get_collection_query(context, Agent, filters=filters)
|
||||
for agent in query.group_by(Agent.admin_state_up,
|
||||
Agent.availability_zone,
|
||||
Agent.agent_type):
|
||||
if not agent.availability_zone:
|
||||
continue
|
||||
if agent.agent_type == constants.AGENT_TYPE_DHCP:
|
||||
resource = 'network'
|
||||
elif agent.agent_type == constants.AGENT_TYPE_L3:
|
||||
resource = 'router'
|
||||
else:
|
||||
continue
|
||||
key = (agent.availability_zone, resource)
|
||||
result[key] = agent.admin_state_up or result.get(key, False)
|
||||
return result
|
||||
|
||||
def get_availability_zones(self, context, filters=None, fields=None,
|
||||
sorts=None, limit=None, marker=None,
|
||||
page_reverse=False):
|
||||
"""Return a list of availability zones."""
|
||||
# NOTE(hichihara): 'tenant_id' is dummy for policy check.
|
||||
# it is not visible via API.
|
||||
return [{'state': 'available' if v else 'unavailable',
|
||||
'name': k[0], 'resource': k[1],
|
||||
'tenant_id': context.tenant_id}
|
||||
for k, v in six.iteritems(self._list_availability_zones(
|
||||
context, filters))]
|
||||
|
||||
def validate_availability_zones(self, context, resource_type,
|
||||
availability_zones):
|
||||
"""Verify that the availability zones exist."""
|
||||
if not availability_zones:
|
||||
return
|
||||
if resource_type == 'network':
|
||||
agent_type = constants.AGENT_TYPE_DHCP
|
||||
elif resource_type == 'router':
|
||||
agent_type = constants.AGENT_TYPE_L3
|
||||
else:
|
||||
return
|
||||
query = context.session.query(Agent.availability_zone).filter_by(
|
||||
agent_type=agent_type).group_by(Agent.availability_zone)
|
||||
query = query.filter(Agent.availability_zone.in_(availability_zones))
|
||||
azs = [item[0] for item in query]
|
||||
diff = set(availability_zones) - set(azs)
|
||||
if diff:
|
||||
raise az_ext.AvailabilityZoneNotFound(availability_zone=diff.pop())
|
||||
|
||||
|
||||
class AgentDbMixin(ext_agent.AgentPluginBase, AgentAvailabilityZoneMixin):
|
||||
"""Mixin class to add agent extension to db_base_plugin_v2."""
|
||||
|
||||
def _get_agent(self, context, id):
|
||||
|
@ -162,6 +218,7 @@ class AgentDbMixin(ext_agent.AgentPluginBase):
|
|||
res['alive'] = not AgentDbMixin.is_agent_down(
|
||||
res['heartbeat_timestamp'])
|
||||
res['configurations'] = self.get_configuration_dict(agent)
|
||||
res['availability_zone'] = agent['availability_zone']
|
||||
return self._fields(res, fields)
|
||||
|
||||
def delete_agent(self, context, id):
|
||||
|
@ -222,7 +279,8 @@ class AgentDbMixin(ext_agent.AgentPluginBase):
|
|||
with context.session.begin(subtransactions=True):
|
||||
res_keys = ['agent_type', 'binary', 'host', 'topic']
|
||||
res = dict((k, agent_state[k]) for k in res_keys)
|
||||
|
||||
if 'availability_zone' in agent_state:
|
||||
res['availability_zone'] = agent_state['availability_zone']
|
||||
configurations_dict = agent_state.get('configurations', {})
|
||||
res['configurations'] = jsonutils.dumps(configurations_dict)
|
||||
res['load'] = self._get_agent_load(agent_state)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Add availability zone
|
||||
|
||||
Revision ID: 59cb5b6cf4d
|
||||
Revises: 34af2b5c5a59
|
||||
Create Date: 2015-01-20 14:38:47.156574
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '59cb5b6cf4d'
|
||||
down_revision = '34af2b5c5a59'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('agents',
|
||||
sa.Column('availability_zone', sa.String(length=255)))
|
|
@ -108,6 +108,10 @@ class Agent(extensions.ExtensionDescriptor):
|
|||
|
||||
return [ex]
|
||||
|
||||
def update_attributes_map(self, attributes):
|
||||
super(Agent, self).update_attributes_map(
|
||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return RESOURCE_ATTRIBUTE_MAP
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
#
|
||||
# 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 attributes as attr
|
||||
from neutron.api.v2 import base
|
||||
from neutron.common import exceptions
|
||||
from neutron import manager
|
||||
|
||||
|
||||
# Attribute Map
|
||||
RESOURCE_NAME = 'availability_zone'
|
||||
AVAILABILITY_ZONES = 'availability_zones'
|
||||
# name: name of availability zone (string)
|
||||
# resource: type of resource: 'network' or 'router'
|
||||
# state: state of availability zone: 'available' or 'unavailable'
|
||||
# It means whether users can use the availability zone.
|
||||
RESOURCE_ATTRIBUTE_MAP = {
|
||||
AVAILABILITY_ZONES: {
|
||||
'name': {'is_visible': True},
|
||||
'resource': {'is_visible': True},
|
||||
'state': {'is_visible': True}
|
||||
}
|
||||
}
|
||||
|
||||
EXTENDED_ATTRIBUTES_2_0 = {
|
||||
'agents': {
|
||||
RESOURCE_NAME: {'allow_post': False, 'allow_put': False,
|
||||
'is_visible': True}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AvailabilityZoneNotFound(exceptions.NotFound):
|
||||
message = _("AvailabilityZone %(availability_zone)s could not be found.")
|
||||
|
||||
|
||||
class Availability_zone(extensions.ExtensionDescriptor):
|
||||
"""Availability zone extension."""
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "Availability Zone"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
return "availability_zone"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
return "The availability zone extension."
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
return "2015-01-01T10:00:00-00:00"
|
||||
|
||||
def get_required_extensions(self):
|
||||
return ["agent"]
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
"""Returns Ext Resources."""
|
||||
my_plurals = [(key, key[:-1]) for key in RESOURCE_ATTRIBUTE_MAP.keys()]
|
||||
attr.PLURALS.update(dict(my_plurals))
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
params = RESOURCE_ATTRIBUTE_MAP.get(AVAILABILITY_ZONES)
|
||||
controller = base.create_resource(AVAILABILITY_ZONES,
|
||||
RESOURCE_NAME, plugin, params)
|
||||
|
||||
ex = extensions.ResourceExtension(AVAILABILITY_ZONES, controller)
|
||||
|
||||
return [ex]
|
||||
|
||||
def get_extended_resources(self, version):
|
||||
if version == "2.0":
|
||||
return dict(list(EXTENDED_ATTRIBUTES_2_0.items()) +
|
||||
list(RESOURCE_ATTRIBUTE_MAP.items()))
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
class AvailabilityZonePluginBase(object):
|
||||
"""REST API to operate the Availability Zone."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_availability_zones(self, context, filters=None, fields=None,
|
||||
sorts=None, limit=None, marker=None,
|
||||
page_reverse=False):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def validate_availability_zones(self, context, resource_type,
|
||||
availability_zones):
|
||||
pass
|
|
@ -118,7 +118,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||
"multi-provider", "allowed-address-pairs",
|
||||
"extra_dhcp_opt", "subnet_allocation",
|
||||
"net-mtu", "vlan-transparent",
|
||||
"address-scope", "dns-integration"]
|
||||
"address-scope", "dns-integration",
|
||||
"availability_zone"]
|
||||
|
||||
@property
|
||||
def supported_extension_aliases(self):
|
||||
|
|
|
@ -25,6 +25,7 @@ from neutron.db import agents_db
|
|||
from neutron.db import common_db_mixin
|
||||
|
||||
HOST = 'localhost'
|
||||
DEFAULT_AZ = 'nova'
|
||||
|
||||
|
||||
def find_file(filename, path):
|
||||
|
@ -47,12 +48,14 @@ class FakePlugin(common_db_mixin.CommonDbMixin,
|
|||
|
||||
|
||||
def _get_l3_agent_dict(host, agent_mode, internal_only=True,
|
||||
ext_net_id='', ext_bridge='', router_id=None):
|
||||
ext_net_id='', ext_bridge='', router_id=None,
|
||||
az=DEFAULT_AZ):
|
||||
return {
|
||||
'agent_type': constants.AGENT_TYPE_L3,
|
||||
'binary': 'neutron-l3-agent',
|
||||
'host': host,
|
||||
'topic': topics.L3_AGENT,
|
||||
'availability_zone': az,
|
||||
'configurations': {'agent_mode': agent_mode,
|
||||
'handle_internal_only_routers': internal_only,
|
||||
'external_network_bridge': ext_bridge,
|
||||
|
@ -71,18 +74,19 @@ def _register_agent(agent):
|
|||
|
||||
def register_l3_agent(host=HOST, agent_mode=constants.L3_AGENT_MODE_LEGACY,
|
||||
internal_only=True, ext_net_id='', ext_bridge='',
|
||||
router_id=None):
|
||||
router_id=None, az=DEFAULT_AZ):
|
||||
agent = _get_l3_agent_dict(host, agent_mode, internal_only, ext_net_id,
|
||||
ext_bridge, router_id)
|
||||
ext_bridge, router_id, az)
|
||||
return _register_agent(agent)
|
||||
|
||||
|
||||
def _get_dhcp_agent_dict(host, networks=0):
|
||||
def _get_dhcp_agent_dict(host, networks=0, az=DEFAULT_AZ):
|
||||
agent = {
|
||||
'binary': 'neutron-dhcp-agent',
|
||||
'host': host,
|
||||
'topic': topics.DHCP_AGENT,
|
||||
'agent_type': constants.AGENT_TYPE_DHCP,
|
||||
'availability_zone': az,
|
||||
'configurations': {'dhcp_driver': 'dhcp_driver',
|
||||
'use_namespaces': True,
|
||||
'networks': networks}}
|
||||
|
@ -90,9 +94,9 @@ def _get_dhcp_agent_dict(host, networks=0):
|
|||
|
||||
|
||||
def register_dhcp_agent(host=HOST, networks=0, admin_state_up=True,
|
||||
alive=True):
|
||||
alive=True, az=DEFAULT_AZ):
|
||||
agent = _register_agent(
|
||||
_get_dhcp_agent_dict(host, networks))
|
||||
_get_dhcp_agent_dict(host, networks, az=az))
|
||||
|
||||
if not admin_state_up:
|
||||
set_agent_admin_state(agent['id'])
|
||||
|
|
|
@ -70,6 +70,7 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
|
|||
agent_config.register_interface_driver_opts_helper(self.conf)
|
||||
agent_config.register_use_namespaces_opts_helper(self.conf)
|
||||
agent_config.register_process_monitor_opts(self.conf)
|
||||
agent_config.register_availability_zone_opts_helper(self.conf)
|
||||
self.conf.register_opts(interface.OPTS)
|
||||
self.conf.register_opts(external_process.OPTS)
|
||||
self.conf.set_override('router_id', 'fake_id')
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
#
|
||||
# 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 oslo_log import log as logging
|
||||
|
||||
from neutron import context
|
||||
from neutron.db import agents_db
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron.extensions import agent
|
||||
from neutron.extensions import availability_zone as az_ext
|
||||
from neutron.tests.common import helpers
|
||||
from neutron.tests.unit.db import test_db_base_plugin_v2
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AZExtensionManager(object):
|
||||
|
||||
def get_resources(self):
|
||||
agent.RESOURCE_ATTRIBUTE_MAP['agents'].update(
|
||||
az_ext.EXTENDED_ATTRIBUTES_2_0['agents'])
|
||||
return (az_ext.Availability_zone.get_resources() +
|
||||
agent.Agent.get_resources())
|
||||
|
||||
def get_actions(self):
|
||||
return []
|
||||
|
||||
def get_request_extensions(self):
|
||||
return []
|
||||
|
||||
|
||||
# This plugin class is just for testing
|
||||
class AZTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
agents_db.AgentDbMixin):
|
||||
supported_extension_aliases = ["agent", "availability_zone"]
|
||||
|
||||
|
||||
class AZTestCommon(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||
def _register_azs(self):
|
||||
self.agent1 = helpers.register_dhcp_agent(host='host1', az='nova1')
|
||||
self.agent2 = helpers.register_dhcp_agent(host='host2', az='nova2')
|
||||
self.agent3 = helpers.register_l3_agent(host='host2', az='nova2')
|
||||
self.agent4 = helpers.register_l3_agent(host='host3', az='nova3')
|
||||
self.agent5 = helpers.register_l3_agent(host='host4', az='nova2')
|
||||
|
||||
|
||||
class TestAZAgentCase(AZTestCommon):
|
||||
def setUp(self):
|
||||
plugin = ('neutron.tests.unit.extensions.'
|
||||
'test_availability_zone.AZTestPlugin')
|
||||
ext_mgr = AZExtensionManager()
|
||||
super(TestAZAgentCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr)
|
||||
|
||||
def test_list_availability_zones(self):
|
||||
self._register_azs()
|
||||
helpers.set_agent_admin_state(self.agent3['id'], admin_state_up=False)
|
||||
helpers.set_agent_admin_state(self.agent4['id'], admin_state_up=False)
|
||||
expected = [
|
||||
{'name': 'nova1', 'resource': 'network', 'state': 'available'},
|
||||
{'name': 'nova2', 'resource': 'network', 'state': 'available'},
|
||||
{'name': 'nova2', 'resource': 'router', 'state': 'available'},
|
||||
{'name': 'nova3', 'resource': 'router', 'state': 'unavailable'}]
|
||||
res = self._list('availability_zones')
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected, azs)
|
||||
# not admin case
|
||||
ctx = context.Context('', 'noadmin')
|
||||
res = self._list('availability_zones', neutron_context=ctx)
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected, azs)
|
||||
|
||||
def test_list_agent_with_az(self):
|
||||
helpers.register_dhcp_agent(host='host1', az='nova1')
|
||||
res = self._list('agents')
|
||||
self.assertEqual('nova1',
|
||||
res['agents'][0]['availability_zone'])
|
||||
|
||||
def test_validate_availability_zones(self):
|
||||
self._register_azs()
|
||||
ctx = context.Context('', 'tenant_id')
|
||||
self.plugin.validate_availability_zones(ctx, 'network',
|
||||
['nova1', 'nova2'])
|
||||
self.plugin.validate_availability_zones(ctx, 'router',
|
||||
['nova2', 'nova3'])
|
||||
self.assertRaises(az_ext.AvailabilityZoneNotFound,
|
||||
self.plugin.validate_availability_zones,
|
||||
ctx, 'router', ['nova1'])
|
Loading…
Reference in New Issue