Adjust filters on listing availability zones
In before, filtering of AZs is using the filter implementation of agents. To filter the AZs, users need to find the AZ fields to filter, locate their corresponding fields in agent, and compile the filters based on the mapping between AZ and agent. This is undesirable. This patch improve the filtering of AZ by converting the AZ filters to agent filters. The supporting AZ filters are: * name: the name of the availability zone * state: the value is either 'available' or 'unavailable' * resource: the value is either 'network' or 'router' NOTE: For backward-compatibility, the old filters still works but its usage is discourage. APIImpact: Add filters to specify attributes of availability zones Needed-By: Icbf427f20ca912a40d68e8042abeeabffeb3005f Change-Id: Id882f949cc73a34290c311f3ce3d69d1b809c29f
This commit is contained in:
parent
15d711b6b3
commit
7899ef2671
|
@ -19,6 +19,7 @@ from eventlet import greenthread
|
|||
from neutron_lib.agent import constants as agent_consts
|
||||
from neutron_lib.api import converters
|
||||
from neutron_lib.api.definitions import agent as agent_apidef
|
||||
from neutron_lib.api import extensions
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib.callbacks import resources
|
||||
|
@ -33,6 +34,7 @@ import oslo_messaging
|
|||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
|
||||
from neutron.agent.common import utils
|
||||
from neutron.api.rpc.callbacks import version_manager
|
||||
|
@ -42,6 +44,7 @@ from neutron.db import _model_query as model_query
|
|||
from neutron.db import _utils as db_utils
|
||||
from neutron.db import api as db_api
|
||||
from neutron.db.models import agent as agent_model
|
||||
from neutron.extensions import _availability_zone_filter_lib as azfil_ext
|
||||
from neutron.extensions import agent as ext_agent
|
||||
from neutron.extensions import availability_zone as az_ext
|
||||
from neutron.objects import agent as agent_obj
|
||||
|
@ -56,6 +59,22 @@ agents_db.register_db_agents_opts()
|
|||
# version_manager callback
|
||||
DOWNTIME_VERSIONS_RATIO = 2
|
||||
|
||||
RESOURCE_AGENT_TYPE_MAP = {
|
||||
'network': constants.AGENT_TYPE_DHCP,
|
||||
'router': constants.AGENT_TYPE_L3,
|
||||
}
|
||||
|
||||
AZ_ATTRIBUTE_MAP = {
|
||||
'name': {
|
||||
'agent_key': 'availability_zone',
|
||||
'convert_to': lambda x: x,
|
||||
},
|
||||
'resource': {
|
||||
'agent_key': 'agent_type',
|
||||
'convert_to': lambda x: RESOURCE_AGENT_TYPE_MAP.get(x, x),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_availability_zones_by_agent_type(context, agent_type,
|
||||
availability_zones):
|
||||
|
@ -69,9 +88,26 @@ def get_availability_zones_by_agent_type(context, agent_type,
|
|||
class AgentAvailabilityZoneMixin(az_ext.AvailabilityZonePluginBase):
|
||||
"""Mixin class to add availability_zone extension to AgentDbMixin."""
|
||||
|
||||
_is_az_filter_supported = None
|
||||
|
||||
@property
|
||||
def is_az_filter_supported(self):
|
||||
supported = self._is_az_filter_supported
|
||||
if supported is None:
|
||||
supported = False
|
||||
for plugin in directory.get_plugins().values():
|
||||
if extensions.is_extension_supported(plugin, azfil_ext.ALIAS):
|
||||
supported = True
|
||||
break
|
||||
self._is_az_filter_supported = supported
|
||||
|
||||
return self._is_az_filter_supported
|
||||
|
||||
def _list_availability_zones(self, context, filters=None):
|
||||
result = {}
|
||||
filters = filters or {}
|
||||
if self._is_az_filter_supported or self.is_az_filter_supported:
|
||||
filters = self._adjust_az_filters(filters)
|
||||
agents = agent_obj.Agent.get_objects(context, **filters)
|
||||
for agent in agents:
|
||||
if not agent.availability_zone:
|
||||
|
@ -83,21 +119,46 @@ class AgentAvailabilityZoneMixin(az_ext.AvailabilityZonePluginBase):
|
|||
else:
|
||||
continue
|
||||
key = (agent.availability_zone, resource)
|
||||
result[key] = agent.admin_state_up or result.get(key, False)
|
||||
value = agent.admin_state_up or result.get(key, False)
|
||||
result[key] = 'available' if value else 'unavailable'
|
||||
return result
|
||||
|
||||
def _adjust_az_filters(self, filters):
|
||||
# The intersect of sets gets us applicable filter keys (others ignored)
|
||||
common_keys = six.viewkeys(filters) & six.viewkeys(AZ_ATTRIBUTE_MAP)
|
||||
for key in common_keys:
|
||||
filter_key = AZ_ATTRIBUTE_MAP[key]['agent_key']
|
||||
filter_vals = filters.pop(key)
|
||||
if filter_vals:
|
||||
filter_vals = [AZ_ATTRIBUTE_MAP[key]['convert_to'](v)
|
||||
for v in filter_vals]
|
||||
filters.setdefault(filter_key, [])
|
||||
filters[filter_key] += filter_vals
|
||||
return filters
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
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 self._list_availability_zones(
|
||||
context, filters).items()]
|
||||
if self._is_az_filter_supported or self.is_az_filter_supported:
|
||||
filter_states = filters.pop('state', [])
|
||||
# NOTE(hichihara): 'tenant_id' is dummy for policy check.
|
||||
# it is not visible via API.
|
||||
return [{'state': v,
|
||||
'name': k[0], 'resource': k[1],
|
||||
'tenant_id': context.tenant_id}
|
||||
for k, v in self._list_availability_zones(
|
||||
context, filters).items()
|
||||
if not filter_states or v in filter_states]
|
||||
else:
|
||||
# NOTE(hichihara): 'tenant_id' is dummy for policy check.
|
||||
# it is not visible via API.
|
||||
return [{'state': v,
|
||||
'name': k[0], 'resource': k[1],
|
||||
'tenant_id': context.tenant_id}
|
||||
for k, v in self._list_availability_zones(
|
||||
context, filters).items()]
|
||||
|
||||
@db_api.retry_if_session_inactive()
|
||||
def validate_availability_zones(self, context, resource_type,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
TODO(hongbin): This module should be deleted once neutron-lib containing
|
||||
https://review.openstack.org/#/c/577545/ change is released.
|
||||
"""
|
||||
|
||||
from neutron_lib.api.definitions import availability_zone as az
|
||||
|
||||
|
||||
ALIAS = 'availability_zone_filter'
|
||||
IS_SHIM_EXTENSION = True
|
||||
IS_STANDARD_ATTR_EXTENSION = False
|
||||
NAME = 'Availability Zone Filter Extension'
|
||||
DESCRIPTION = 'Add filter parameters to AvailabilityZone resource'
|
||||
UPDATED_TIMESTAMP = '2018-06-22T10:00:00-00:00'
|
||||
RESOURCE_ATTRIBUTE_MAP = {}
|
||||
SUB_RESOURCE_ATTRIBUTE_MAP = {}
|
||||
ACTION_MAP = {}
|
||||
REQUIRED_EXTENSIONS = [
|
||||
az.ALIAS
|
||||
]
|
||||
OPTIONAL_EXTENSIONS = []
|
||||
ACTION_STATUS = {}
|
|
@ -0,0 +1,18 @@
|
|||
# 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.extensions import _availability_zone_filter_lib as apidef
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
|
||||
class Availability_zone_filter(extensions.APIExtensionDescriptor):
|
||||
api_definition = apidef
|
|
@ -155,6 +155,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
|||
"address-scope",
|
||||
"availability_zone",
|
||||
"network_availability_zone",
|
||||
"availability_zone_filter",
|
||||
"default-subnetpools",
|
||||
"subnet-service-types",
|
||||
"ip-substring-filtering",
|
||||
|
|
|
@ -5,6 +5,7 @@ NETWORK_API_EXTENSIONS+=",agent"
|
|||
NETWORK_API_EXTENSIONS+=",allowed-address-pairs"
|
||||
NETWORK_API_EXTENSIONS+=",auto-allocated-topology"
|
||||
NETWORK_API_EXTENSIONS+=",availability_zone"
|
||||
NETWORK_API_EXTENSIONS+=",availability_zone_filter"
|
||||
NETWORK_API_EXTENSIONS+=",binding"
|
||||
NETWORK_API_EXTENSIONS+=",default-subnetpools"
|
||||
NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler"
|
||||
|
|
|
@ -39,7 +39,8 @@ class AZExtensionManager(object):
|
|||
|
||||
class AZTestPlugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
agents_db.AgentDbMixin):
|
||||
supported_extension_aliases = ["agent", "availability_zone"]
|
||||
supported_extension_aliases = ["agent", "availability_zone",
|
||||
"availability_zone_filter"]
|
||||
|
||||
|
||||
class AZTestCommon(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||
|
@ -70,17 +71,45 @@ class TestAZAgentCase(AZTestCommon):
|
|||
res = self._list('availability_zones')
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected, azs)
|
||||
# list with filters
|
||||
res = self._list('availability_zones',
|
||||
query_params="availability_zone=nova1")
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected[:1], 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_availability_zones_with_filter(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)
|
||||
# list with filter of 'name'
|
||||
res = self._list('availability_zones',
|
||||
query_params="name=nova1")
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected[:1], azs)
|
||||
# list with filter of 'resource'
|
||||
res = self._list('availability_zones',
|
||||
query_params="resource=router")
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected[-2:], azs)
|
||||
# list with filter of 'state' as 'available'
|
||||
res = self._list('availability_zones',
|
||||
query_params="state=available")
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected[:3], azs)
|
||||
# list with filter of 'state' as 'unavailable'
|
||||
res = self._list('availability_zones',
|
||||
query_params="state=unavailable")
|
||||
azs = res['availability_zones']
|
||||
self.assertItemsEqual(expected[-1:], azs)
|
||||
|
||||
def test_list_agent_with_az(self):
|
||||
helpers.register_dhcp_agent(host='host1', az='nova1')
|
||||
res = self._list('agents')
|
||||
|
|
Loading…
Reference in New Issue