Merge "Adjust filters on listing availability zones"

This commit is contained in:
Zuul 2018-07-17 10:54:33 +00:00 committed by Gerrit Code Review
commit 156f29aea3
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
@ -34,6 +35,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.conf.agent.database import agents_db
from neutron.db import _model_query as model_query
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

@ -156,6 +156,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')