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:
Hongbin Lu 2018-05-03 22:23:18 +00:00
parent 15d711b6b3
commit 7899ef2671
6 changed files with 158 additions and 14 deletions

View File

@ -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,

View File

@ -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 = {}

View File

@ -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

View File

@ -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",

View File

@ -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"

View File

@ -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')