From 0c76484a5055f2c7f7414156e15debd32b91822a Mon Sep 17 00:00:00 2001 From: German Eichberger Date: Tue, 10 Jan 2017 14:04:57 -0500 Subject: [PATCH] Octavia Proxy Plugin This plugin proxies LBaaS V2 API requests to Octavia. Fixes test errors... Closes-Bug: #1596660 Change-Id: If1e1348e4734b5b9b839241453059f628d9c4948 --- .../services/loadbalancer/proxy_plugin.py | 366 +++++ .../db/loadbalancer/test_db_loadbalancerv2.py | 520 +------ .../unit/db/loadbalancer/test_proxy_plugin.py | 1372 +++++++++++++++++ .../tests/unit/db/loadbalancer/util.py | 529 +++++++ .../tests/unit/test_agent_scheduler.py | 3 +- setup.cfg | 1 + 6 files changed, 2278 insertions(+), 513 deletions(-) create mode 100644 neutron_lbaas/services/loadbalancer/proxy_plugin.py create mode 100644 neutron_lbaas/tests/unit/db/loadbalancer/test_proxy_plugin.py create mode 100644 neutron_lbaas/tests/unit/db/loadbalancer/util.py diff --git a/neutron_lbaas/services/loadbalancer/proxy_plugin.py b/neutron_lbaas/services/loadbalancer/proxy_plugin.py new file mode 100644 index 000000000..0324b9b06 --- /dev/null +++ b/neutron_lbaas/services/loadbalancer/proxy_plugin.py @@ -0,0 +1,366 @@ +# Copyright 2017, Rackspace US, Inc. +# +# 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 functools + +from neutron.db import servicetype_db as st_db +from neutron.services import provider_configuration as pconf +from neutron_lib import exceptions as lib_exc +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils +import requests + +from neutron_lbaas.extensions import loadbalancerv2 +from neutron_lbaas.services.loadbalancer import constants + +LOG = logging.getLogger(__name__) +VERSION = 'v2.0' +OCTAVIA_PROXY_CLIENT = ( + "LBaaS V2 Octavia Proxy/{version} " + "(https://wiki.openstack.org/wiki/Octavia)").format(version=VERSION) +FILTER = ['vip_address', 'vip_network_id', 'flavor_id', + 'provider', 'redirect_pool_id'] + +LOADBALANCER = 'loadbalancer' +LISTENER = 'listener' +POOL = 'pool' +L7POLICY = 'l7policy' +L7POLICY_RULE = 'rule' +MEMBER = 'member' +HEALTH_MONITOR = 'healthmonitor' +STATUS = 'statuses' +GRAPH = 'graph' + +OPTS = [ + cfg.StrOpt( + 'base_url', + default='http://127.0.0.1:9876', + help=_('URL of Octavia controller root'), + ), +] + +cfg.CONF.register_opts(OPTS, 'octavia') + + +def add_provider_configuration(type_manager, service_type): + type_manager.add_provider_configuration( + service_type, + pconf.ProviderConfiguration('neutron_lbaas_proxy')) + + +class LoadBalancerProxyPluginv2(loadbalancerv2.LoadBalancerPluginBaseV2): + """Implementation of the Neutron Loadbalancer Proxy Plugin. + + This class proxies all requests/reponses to Octavia + """ + + supported_extension_aliases = ["lbaasv2", + "shared_pools", + "l7", + "lbaas_agent_schedulerv2", + "service-type", + "lb-graph", + "lb_network_vip", + "hm_max_retries_down"] + path_prefix = loadbalancerv2.LOADBALANCERV2_PREFIX + + def __init__(self): + self.service_type_manager = st_db.ServiceTypeManager.get_instance() + add_provider_configuration( + self.service_type_manager, constants.LOADBALANCERV2) + self.get = functools.partial(self.request, 'GET') + self.post = functools.partial(self.request, 'POST') + self.put = functools.partial(self.request, 'PUT') + self.delete = functools.partial(self.request, 'DELETE') + self.base_url = '{}/{}/lbaas'.format(cfg.CONF.octavia.base_url, + VERSION) + + def get_plugin_type(self): + return constants.LOADBALANCERV2 + + def get_plugin_description(self): + return "Neutron LoadBalancer Proxy Plugin" + + def request(self, method, url, token=None, args=None, headers=None, + accepted_codes=[200, 201, 202, 204]): + params = {} + if args: + # extract filter and fields + if 'filters' in args: + params = args.pop('filters') + if 'fields' in args: + params['fields'] = args.pop('fields') + args = jsonutils.dumps(args) + if not headers: + headers = { + 'Content-type': 'application/json', + 'X-Auth-Token': token + } + headers['User-Agent'] = OCTAVIA_PROXY_CLIENT + + url = '{}/{}'.format(self.base_url, str(url)) + LOG.debug("url = %s", url) + LOG.debug("args = %s", args) + LOG.debug("params = %s", str(params)) + r = requests.request(method, url, data=args, params=params, + headers=headers) + LOG.debug("Octavia Response Code: {0}".format(r.status_code)) + LOG.debug("Octavia Response Body: {0}".format(r.content)) + LOG.debug("Octavia Response Headers: {0}".format(r.headers)) + + if r.status_code in accepted_codes: + if method != 'DELETE': + return r.json() + elif r.status_code == 413: + e = lib_exc.OverQuota() + e.msg = str(r.content) + raise e + elif r.status_code == 409: + e = lib_exc.Conflict() + e.msg = str(r.content) + raise e + elif r.status_code == 401: + e = lib_exc.NotAuthorized() + e.msg = str(r.content) + raise e + elif r.status_code == 404: + e = lib_exc.NotFound() + e.msg = str(r.content) + raise e + elif r.status_code == 400: + e = lib_exc.BadRequest(resource="", msg="") + e.msg = str(r.content) + raise e + else: + raise loadbalancerv2.DriverError(msg=str(r.content)) + + def _filter(self, keys, map): + """Filter the args map + + keys: The keys to filter out + map: the args in a map + + NOTE: This returns a deep copy - leaving the original alone + """ + + res = {} + for k in map: + if k not in keys: + if map[k]: + res[k] = map[k] + if 'tenant_id' in res: + res['project_id'] = res.pop('tenant_id') + return res + + def pluralize(self, name): + if name.endswith('y'): + return name[:-1] + "ies" + elif not name.endswith('s'): + return "{}s".format(name) + return name + + def _path(self, resource, sub_resource, resource_id): + url = resource + if sub_resource: + url = "{}/{}/{}".format(self.pluralize(resource), + resource_id, sub_resource) + return self.pluralize(url) + + def _create_resource(self, resource, context, res, sub_resource=None, + resource_id=None): + # clean up the map + resource_ = resource if not sub_resource else sub_resource + r = self._filter(FILTER, res[resource_]) + r = self.post(self._path(resource, sub_resource, resource_id), + context.auth_token, {resource_: r}) + return r[resource_] + + def _get_resources(self, resource, context, filters=None, fields=None, + sub_resource=None, resource_id=None): + # not sure how to test that or if we even support sorting/filtering? + resource_ = resource if not sub_resource else sub_resource + args = {} + if filters: + if 'tenant_id' in filters: + filters['project_id'] = filters.pop('tenant_id') + args['filters'] = filters + if fields: + args['fields'] = fields + res = self.get(self._path(resource, sub_resource, resource_id), + context.auth_token, args) + return res[self.pluralize(resource_)] + + def _get_resource(self, resource, context, id, fields=None, + sub_resource=None, resource_id=None): + # not sure how to test that or if we even support sorting/filtering? + args = {} + if fields: + args['fields'] = fields + resource_ = resource if not sub_resource else sub_resource + res = self.get('{}/{}'.format( + self._path(resource, sub_resource, resource_id), id), + context.auth_token, args) + return res[resource_] + + def _update_resource(self, resource, context, id, res, + sub_resource=None, resource_id=None): + # clean up the map + resource_ = resource if not sub_resource else sub_resource + r = self._filter(FILTER, res[resource_]) + res = self.put('{}/{}'.format(self._path( + resource, sub_resource, resource_id), id), + context.auth_token, + {resource_: r}) + return res[resource_] + + def _delete_resource(self, resource, context, id, + sub_resource=None, resource_id=None): + self.delete('{}/{}'.format(self._path( + resource, sub_resource, resource_id), id), + context.auth_token) + + def create_loadbalancer(self, context, loadbalancer): + return self._create_resource(LOADBALANCER, context, loadbalancer) + + def get_loadbalancers(self, context, filters=None, fields=None): + return self._get_resources(LOADBALANCER, context, filters, fields) + + def get_loadbalancer(self, context, id, fields=None): + return self._get_resource(LOADBALANCER, context, id, fields) + + def update_loadbalancer(self, context, id, loadbalancer): + return self._update_resource(LOADBALANCER, context, id, loadbalancer) + + def delete_loadbalancer(self, context, id): + self._delete_resource(LOADBALANCER, context, id) + + def create_listener(self, context, listener): + return self._create_resource(LISTENER, context, listener) + + def get_listener(self, context, id, fields=None): + return self._get_resource(LISTENER, context, id, fields) + + def get_listeners(self, context, filters=None, fields=None): + return self._get_resources(LISTENER, context, filters, fields) + + def update_listener(self, context, id, listener): + return self._update_resource(LISTENER, context, id, listener) + + def delete_listener(self, context, id): + return self._delete_resource(LISTENER, context, id) + + def get_pools(self, context, filters=None, fields=None): + return self._get_resources(POOL, context, filters, fields) + + def get_pool(self, context, id, fields=None): + return self._get_resource(POOL, context, id, fields) + + def create_pool(self, context, pool): + return self._create_resource(POOL, context, pool) + + def update_pool(self, context, id, pool): + return self._update_resource(POOL, context, id, pool) + + def delete_pool(self, context, id): + return self._delete_resource(POOL, context, id) + + def stats(self, context, loadbalancer_id): + pass + + def get_pool_members(self, context, pool_id, + filters=None, + fields=None): + return self._get_resources(POOL, context, filters, fields, + MEMBER, pool_id) + + def get_pool_member(self, context, id, pool_id, + fields=None): + return self._get_resource(POOL, context, id, fields, + MEMBER, pool_id) + + def create_pool_member(self, context, pool_id, member): + return self._create_resource(POOL, context, member, MEMBER, pool_id) + + def update_pool_member(self, context, id, pool_id, member): + return self._update_resource(POOL, context, id, member, + MEMBER, pool_id) + + def delete_pool_member(self, context, id, pool_id): + return self._delete_resource(POOL, context, id, MEMBER, pool_id) + + def get_healthmonitors(self, context, filters=None, fields=None): + return self._get_resources(HEALTH_MONITOR, context, filters, fields) + + def get_healthmonitor(self, context, id, fields=None): + return self._get_resource(HEALTH_MONITOR, context, id, fields) + + def create_healthmonitor(self, context, healthmonitor): + return self._create_resource(HEALTH_MONITOR, context, healthmonitor) + + def update_healthmonitor(self, context, id, healthmonitor): + return self._update_resource(HEALTH_MONITOR, context, + id, healthmonitor) + + def delete_healthmonitor(self, context, id): + return self._delete_resource(HEALTH_MONITOR, context, id) + + def get_members(self, context, filters=None, fields=None): + pass + + def get_member(self, context, id, fields=None): + pass + + def statuses(self, context, loadbalancer_id): + return self._get_resources(LOADBALANCER, context, sub_resource=STATUS, + resource_id=loadbalancer_id) + + def get_l7policies(self, context, filters=None, fields=None): + return self._get_resources(L7POLICY, context, filters, fields) + + def get_l7policy(self, context, id, fields=None): + return self._get_resource(L7POLICY, context, id, fields) + + def create_l7policy(self, context, l7policy): + return self._create_resource(L7POLICY, context, l7policy) + + def update_l7policy(self, context, id, l7policy): + return self._update_resource(L7POLICY, context, id, l7policy) + + def delete_l7policy(self, context, id): + return self._delete_resource(L7POLICY, context, id) + + def get_l7policy_rules(self, context, l7policy_id, + filters=None, fields=None): + return self._get_resources(L7POLICY, context, filters, fields, + L7POLICY_RULE, l7policy_id) + + def get_l7policy_rule(self, context, id, l7policy_id, fields=None): + return self._get_resource(L7POLICY, context, id, fields, + L7POLICY_RULE, l7policy_id) + + def create_l7policy_rule(self, context, rule, l7policy_id): + return self._create_resource(L7POLICY, context, rule, L7POLICY_RULE, + l7policy_id) + + def update_l7policy_rule(self, context, id, rule, l7policy_id): + return self._update_resource(L7POLICY, context, id, rule, + L7POLICY_RULE, l7policy_id) + + def delete_l7policy_rule(self, context, id, l7policy_id): + return self._delete_resource(L7POLICY, context, id, L7POLICY_RULE, + l7policy_id) + + def create_graph(self, context, graph): + return self._create_resource(GRAPH, context, graph) diff --git a/neutron_lbaas/tests/unit/db/loadbalancer/test_db_loadbalancerv2.py b/neutron_lbaas/tests/unit/db/loadbalancer/test_db_loadbalancerv2.py index c2b607dea..f428e43e8 100644 --- a/neutron_lbaas/tests/unit/db/loadbalancer/test_db_loadbalancerv2.py +++ b/neutron_lbaas/tests/unit/db/loadbalancer/test_db_loadbalancerv2.py @@ -13,14 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -import contextlib import copy import mock +import six +import testtools +import webob.exc + from neutron.api import extensions from neutron.common import config -import neutron.db.l3_db # noqa -from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron_lib import constants as n_constants from neutron_lib import context from neutron_lib import exceptions as n_exc @@ -28,26 +29,20 @@ from neutron_lib.plugins import constants from neutron_lib.plugins import directory from oslo_config import cfg from oslo_utils import uuidutils -import six -import testtools -import webob.exc -from neutron_lbaas._i18n import _ from neutron_lbaas.common.cert_manager import cert_manager from neutron_lbaas.common import exceptions from neutron_lbaas.db.loadbalancer import loadbalancer_dbv2 from neutron_lbaas.db.loadbalancer import models from neutron_lbaas.drivers.logging_noop import driver as noop_driver import neutron_lbaas.extensions -from neutron_lbaas.extensions import healthmonitor_max_retries_down from neutron_lbaas.extensions import l7 -from neutron_lbaas.extensions import lb_graph -from neutron_lbaas.extensions import lb_network_vip from neutron_lbaas.extensions import loadbalancerv2 from neutron_lbaas.extensions import sharedpools from neutron_lbaas.services.loadbalancer import constants as lb_const from neutron_lbaas.services.loadbalancer import plugin as loadbalancer_plugin from neutron_lbaas.tests import base +from neutron_lbaas.tests.unit.db.loadbalancer import util DB_CORE_PLUGIN_CLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' @@ -63,507 +58,8 @@ extensions_path = ':'.join(neutron_lbaas.extensions.__path__) _subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14" -class LbaasTestMixin(object): - resource_keys = list(loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.keys()) - resource_keys.extend(l7.RESOURCE_ATTRIBUTE_MAP.keys()) - resource_keys.extend(lb_graph.RESOURCE_ATTRIBUTE_MAP.keys()) - resource_keys.extend(lb_network_vip.EXTENDED_ATTRIBUTES_2_0.keys()) - resource_keys.extend(healthmonitor_max_retries_down. - EXTENDED_ATTRIBUTES_2_0.keys()) - resource_prefix_map = dict( - (k, loadbalancerv2.LOADBALANCERV2_PREFIX) - for k in resource_keys) - - def _get_loadbalancer_optional_args(self): - return ('description', 'vip_address', 'admin_state_up', 'name', - 'listeners', 'vip_network_id', 'vip_subnet_id') - - def _create_loadbalancer(self, fmt, subnet_id, - expected_res_status=None, **kwargs): - data = {'loadbalancer': {'vip_subnet_id': subnet_id, - 'tenant_id': self._tenant_id}} - args = self._get_loadbalancer_optional_args() - for arg in args: - if arg in kwargs: - if kwargs[arg] is not None: - data['loadbalancer'][arg] = kwargs[arg] - else: - data['loadbalancer'].pop(arg, None) - - lb_req = self.new_create_request('loadbalancers', data, fmt) - lb_res = lb_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, lb_res.status_int) - - return lb_res - - def _create_graph(self, fmt, subnet_id, expected_res_status=None, - **kwargs): - data = {'vip_subnet_id': subnet_id, 'tenant_id': self._tenant_id} - args = self._get_loadbalancer_optional_args() - for arg in args: - if arg in kwargs and kwargs[arg] is not None: - data[arg] = kwargs[arg] - - data = {'graph': {'loadbalancer': data, 'tenant_id': self._tenant_id}} - lb_req = self.new_create_request('graphs', data, fmt) - lb_res = lb_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, lb_res.status_int) - - return lb_res - - def _get_listener_optional_args(self): - return ('name', 'description', 'default_pool_id', 'loadbalancer_id', - 'connection_limit', 'admin_state_up', - 'default_tls_container_ref', 'sni_container_refs') - - def _create_listener(self, fmt, protocol, protocol_port, - loadbalancer_id=None, default_pool_id=None, - expected_res_status=None, **kwargs): - data = {'listener': {'protocol': protocol, - 'protocol_port': protocol_port, - 'tenant_id': self._tenant_id}} - if loadbalancer_id: - data['listener']['loadbalancer_id'] = loadbalancer_id - if default_pool_id: - data['listener']['default_pool_id'] = default_pool_id - - args = self._get_listener_optional_args() - for arg in args: - if arg in kwargs and kwargs[arg] is not None: - data['listener'][arg] = kwargs[arg] - - listener_req = self.new_create_request('listeners', data, fmt) - listener_res = listener_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, listener_res.status_int) - - return listener_res - - def _get_pool_optional_args(self): - return 'name', 'description', 'admin_state_up', 'session_persistence' - - def _create_pool(self, fmt, protocol, lb_algorithm, listener_id=None, - loadbalancer_id=None, expected_res_status=None, **kwargs): - data = {'pool': {'protocol': protocol, - 'lb_algorithm': lb_algorithm, - 'tenant_id': self._tenant_id}} - if listener_id: - data['pool']['listener_id'] = listener_id - if loadbalancer_id: - data['pool']['loadbalancer_id'] = loadbalancer_id - - args = self._get_pool_optional_args() - for arg in args: - if arg in kwargs and kwargs[arg] is not None: - data['pool'][arg] = kwargs[arg] - - pool_req = self.new_create_request('pools', data, fmt) - pool_res = pool_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, pool_res.status_int) - - return pool_res - - def _get_member_optional_args(self): - return 'weight', 'admin_state_up', 'name' - - def _create_member(self, fmt, pool_id, address, protocol_port, subnet_id, - expected_res_status=None, **kwargs): - data = {'member': {'address': address, - 'protocol_port': protocol_port, - 'subnet_id': subnet_id, - 'tenant_id': self._tenant_id}} - - args = self._get_member_optional_args() - for arg in args: - if arg in kwargs and kwargs[arg] is not None: - data['member'][arg] = kwargs[arg] - member_req = self.new_create_request('pools', - data, - fmt=fmt, - id=pool_id, - subresource='members') - member_res = member_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, member_res.status_int) - - return member_res - - def _get_healthmonitor_optional_args(self): - return ('weight', 'admin_state_up', 'expected_codes', 'url_path', - 'http_method', 'name', 'max_retries_down') - - def _create_healthmonitor(self, fmt, pool_id, type, delay, timeout, - max_retries, expected_res_status=None, **kwargs): - data = {'healthmonitor': {'type': type, - 'delay': delay, - 'timeout': timeout, - 'max_retries': max_retries, - 'pool_id': pool_id, - 'tenant_id': self._tenant_id}} - - args = self._get_healthmonitor_optional_args() - for arg in args: - if arg in kwargs and kwargs[arg] is not None: - data['healthmonitor'][arg] = kwargs[arg] - - hm_req = self.new_create_request('healthmonitors', data, fmt=fmt) - hm_res = hm_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(expected_res_status, hm_res.status_int) - - return hm_res - - def _add_optional_args(self, optional_args, data, **kwargs): - for arg in optional_args: - if arg in kwargs and kwargs[arg] is not None: - data[arg] = kwargs[arg] - - def _get_l7policy_optional_args(self): - return ('name', 'description', 'redirect_pool_id', - 'redirect_url', 'admin_state_up', 'position') - - def _create_l7policy(self, fmt, listener_id, action, - expected_res_status=None, **kwargs): - data = {'l7policy': {'listener_id': listener_id, - 'action': action, - 'tenant_id': self._tenant_id}} - - optional_args = self._get_l7policy_optional_args() - self._add_optional_args(optional_args, data['l7policy'], **kwargs) - - l7policy_req = self.new_create_request('l7policies', data, fmt) - l7policy_res = l7policy_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(l7policy_res.status_int, expected_res_status) - - return l7policy_res - - def _get_l7rule_optional_args(self): - return ('invert', 'key', 'admin_state_up') - - def _create_l7policy_rule(self, fmt, l7policy_id, type, compare_type, - value, expected_res_status=None, **kwargs): - data = {'rule': {'type': type, - 'compare_type': compare_type, - 'value': value, - 'tenant_id': self._tenant_id}} - - optional_args = self._get_l7rule_optional_args() - self._add_optional_args(optional_args, data['rule'], **kwargs) - - rule_req = self.new_create_request('l7policies', data, fmt, - id=l7policy_id, - subresource='rules') - rule_res = rule_req.get_response(self.ext_api) - if expected_res_status: - self.assertEqual(rule_res.status_int, expected_res_status) - - return rule_res - - @contextlib.contextmanager - def loadbalancer(self, fmt=None, subnet=None, no_delete=False, **kwargs): - if not fmt: - fmt = self.fmt - - with test_db_base_plugin_v2.optional_ctx( - subnet, self.subnet) as tmp_subnet: - - res = self._create_loadbalancer(fmt, - tmp_subnet['subnet']['id'], - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - exc = webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % - res.status_int - ) - exc.code = res.status_int - exc.status_code = res.status_int - raise exc - lb = self.deserialize(fmt or self.fmt, res) - yield lb - if not no_delete: - self._delete('loadbalancers', lb['loadbalancer']['id']) - - @contextlib.contextmanager - def graph(self, fmt=None, subnet=None, no_delete=False, **kwargs): - if not fmt: - fmt = self.fmt - - with test_db_base_plugin_v2.optional_ctx( - subnet, self.subnet) as tmp_subnet: - - res = self._create_graph(fmt, tmp_subnet['subnet']['id'], - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - exc = webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % - res.status_int - ) - exc.code = res.status_int - exc.status_code = res.status_int - raise exc - graph = self.deserialize(fmt or self.fmt, res) - yield graph - if not no_delete: - # delete loadbalancer children if this was a loadbalancer - # graph create call - lb = graph['graph']['loadbalancer'] - for listener in lb.get('listeners', []): - pool = listener.get('default_pool') - if pool: - hm = pool.get('healthmonitor') - if hm: - self._delete('healthmonitors', hm['id']) - members = pool.get('members', []) - for member in members: - self._delete('pools', pool['id'], - subresource='members', - sub_id=member['id']) - self._delete('pools', pool['id']) - policies = listener.get('l7policies', []) - for policy in policies: - r_pool = policy.get('redirect_pool') - if r_pool: - r_hm = r_pool.get('healthmonitor') - if r_hm: - self._delete('healthmonitors', r_hm['id']) - r_members = r_pool.get('members', []) - for r_member in r_members: - self._delete('pools', r_pool['id'], - subresource='members', - sub_id=r_member['id']) - self._delete('pools', r_pool['id']) - self._delete('l7policies', policy['id']) - self._delete('listeners', listener['id']) - self._delete('loadbalancers', lb['id']) - - @contextlib.contextmanager - def listener(self, fmt=None, protocol='HTTP', loadbalancer_id=None, - protocol_port=80, default_pool_id=None, no_delete=False, - **kwargs): - if not fmt: - fmt = self.fmt - - if loadbalancer_id and default_pool_id: - res = self._create_listener(fmt, protocol, protocol_port, - loadbalancer_id=loadbalancer_id, - default_pool_id=default_pool_id, - **kwargs) - elif loadbalancer_id: - res = self._create_listener(fmt, protocol, protocol_port, - loadbalancer_id=loadbalancer_id, - **kwargs) - else: - res = self._create_listener(fmt, protocol, protocol_port, - default_pool_id=default_pool_id, - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - listener = self.deserialize(fmt or self.fmt, res) - yield listener - if not no_delete: - self._delete('listeners', listener['listener']['id']) - - @contextlib.contextmanager - def pool(self, fmt=None, protocol='HTTP', lb_algorithm='ROUND_ROBIN', - no_delete=False, listener_id=None, - loadbalancer_id=None, **kwargs): - if not fmt: - fmt = self.fmt - - if listener_id and loadbalancer_id: - res = self._create_pool(fmt, - protocol=protocol, - lb_algorithm=lb_algorithm, - listener_id=listener_id, - loadbalancer_id=loadbalancer_id, - **kwargs) - elif listener_id: - res = self._create_pool(fmt, - protocol=protocol, - lb_algorithm=lb_algorithm, - listener_id=listener_id, - **kwargs) - else: - res = self._create_pool(fmt, - protocol=protocol, - lb_algorithm=lb_algorithm, - loadbalancer_id=loadbalancer_id, - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - pool = self.deserialize(fmt or self.fmt, res) - yield pool - if not no_delete: - self._delete('pools', pool['pool']['id']) - - @contextlib.contextmanager - def member(self, fmt=None, pool_id='pool1id', address='127.0.0.1', - protocol_port=80, subnet=None, no_delete=False, - **kwargs): - if not fmt: - fmt = self.fmt - subnet = subnet or self.test_subnet - with test_db_base_plugin_v2.optional_ctx( - subnet, self.subnet) as tmp_subnet: - - res = self._create_member(fmt, - pool_id=pool_id, - address=address, - protocol_port=protocol_port, - subnet_id=tmp_subnet['subnet']['id'], - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - member = self.deserialize(fmt or self.fmt, res) - yield member - if not no_delete: - self._delete('pools', id=pool_id, subresource='members', - sub_id=member['member']['id']) - - @contextlib.contextmanager - def healthmonitor(self, fmt=None, pool_id='pool1id', type='TCP', delay=1, - timeout=1, max_retries=2, no_delete=False, **kwargs): - if not fmt: - fmt = self.fmt - - res = self._create_healthmonitor(fmt, - pool_id=pool_id, - type=type, - delay=delay, - timeout=timeout, - max_retries=max_retries, - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - healthmonitor = self.deserialize(fmt or self.fmt, res) - yield healthmonitor - if not no_delete: - del_req = self.new_delete_request( - 'healthmonitors', fmt=fmt, - id=healthmonitor['healthmonitor']['id']) - del_res = del_req.get_response(self.ext_api) - self.assertEqual(webob.exc.HTTPNoContent.code, del_res.status_int) - - @contextlib.contextmanager - def l7policy(self, listener_id, fmt=None, - action=lb_const.L7_POLICY_ACTION_REJECT, - no_delete=False, **kwargs): - if not fmt: - fmt = self.fmt - - res = self._create_l7policy(fmt, - listener_id=listener_id, - action=action, - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - l7policy = self.deserialize(fmt or self.fmt, res) - yield l7policy - if not no_delete: - self.plugin.db.update_status(context.get_admin_context(), - models.L7Policy, - l7policy['l7policy']['id'], - n_constants.ACTIVE) - del_req = self.new_delete_request( - 'l7policies', - fmt=fmt, - id=l7policy['l7policy']['id']) - del_res = del_req.get_response(self.ext_api) - self.assertEqual(del_res.status_int, - webob.exc.HTTPNoContent.code) - - @contextlib.contextmanager - def l7policy_rule(self, l7policy_id, fmt=None, value='value1', - type=lb_const.L7_RULE_TYPE_HOST_NAME, - compare_type=lb_const.L7_RULE_COMPARE_TYPE_EQUAL_TO, - no_delete=False, **kwargs): - if not fmt: - fmt = self.fmt - res = self._create_l7policy_rule(fmt, - l7policy_id=l7policy_id, - type=type, - compare_type=compare_type, - value=value, - **kwargs) - if res.status_int >= webob.exc.HTTPClientError.code: - raise webob.exc.HTTPClientError( - explanation=_("Unexpected error code: %s") % res.status_int - ) - - rule = self.deserialize(fmt or self.fmt, res) - yield rule - if not no_delete: - self.plugin.db.update_status(context.get_admin_context(), - models.L7Rule, - rule['rule']['id'], - n_constants.ACTIVE) - del_req = self.new_delete_request( - 'l7policies', - fmt=fmt, - id=l7policy_id, - subresource='rules', - sub_id=rule['rule']['id']) - del_res = del_req.get_response(self.ext_api) - self.assertEqual(del_res.status_int, - webob.exc.HTTPNoContent.code) - - -class ExtendedPluginAwareExtensionManager(object): - def __init__(self, extension_aliases): - self.extension_aliases = extension_aliases - - def get_resources(self): - extensions_list = [] - if 'shared_pools' in self.extension_aliases: - extensions_list.append(sharedpools) - if 'l7' in self.extension_aliases: - extensions_list.append(l7) - if 'lb-graph' in self.extension_aliases: - extensions_list.append(lb_graph) - if 'lb_network_vip' in self.extension_aliases: - extensions_list.append(lb_network_vip) - if 'hm_max_retries_down' in self.extension_aliases: - extensions_list.append(healthmonitor_max_retries_down) - for extension in extensions_list: - if 'RESOURCE_ATTRIBUTE_MAP' in extension.__dict__: - loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.update( - extension.RESOURCE_ATTRIBUTE_MAP) - if 'SUB_RESOURCE_ATTRIBUTE_MAP' in extension.__dict__: - loadbalancerv2.SUB_RESOURCE_ATTRIBUTE_MAP.update( - extension.SUB_RESOURCE_ATTRIBUTE_MAP) - if 'EXTENDED_ATTRIBUTES_2_0' in extension.__dict__: - for key in loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.keys(): - loadbalancerv2.RESOURCE_ATTRIBUTE_MAP[key].update( - extension.EXTENDED_ATTRIBUTES_2_0.get(key, {})) - return loadbalancerv2.Loadbalancerv2.get_resources() - - def get_actions(self): - return [] - - def get_request_extensions(self): - return [] - - -class LbaasPluginDbTestCase(LbaasTestMixin, base.NeutronDbPluginV2TestCase): +class LbaasPluginDbTestCase(util.LbaasTestMixin, + base.NeutronDbPluginV2TestCase): def setUp(self, core_plugin=None, lb_plugin=None, lbaas_provider=None, ext_mgr=None): service_plugins = {'lb_plugin_name': DB_LB_PLUGIN_CLASS} @@ -595,7 +91,7 @@ class LbaasPluginDbTestCase(LbaasTestMixin, base.NeutronDbPluginV2TestCase): # finding algorithm below will find the loadbalancerv2 # extension and fail to initizlize the main API router with # extensions' resources - ext_mgr = ExtendedPluginAwareExtensionManager( + ext_mgr = util.ExtendedPluginAwareExtensionManager( LBPlugin.supported_extension_aliases) app = config.load_paste_app('extensions_test_app') diff --git a/neutron_lbaas/tests/unit/db/loadbalancer/test_proxy_plugin.py b/neutron_lbaas/tests/unit/db/loadbalancer/test_proxy_plugin.py new file mode 100644 index 000000000..bfca5e74e --- /dev/null +++ b/neutron_lbaas/tests/unit/db/loadbalancer/test_proxy_plugin.py @@ -0,0 +1,1372 @@ +# Copyright 2017, Rackspace US, Inc. +# +# 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 uuid + +import requests_mock +import webob.exc + +from neutron.api import extensions +from neutron.common import config +from neutron.tests.unit.api.v2 import test_base +from neutron_lib import constants as n_constants +from neutron_lib import context +from neutron_lib import exceptions as lib_exc +from oslo_config import cfg +from oslo_utils import uuidutils + +from neutron_lbaas.services.loadbalancer import constants as lb_const +from neutron_lbaas.services.loadbalancer import \ + proxy_plugin as loadbalancer_plugin +from neutron_lbaas.tests import base +from neutron_lbaas.tests.unit.db.loadbalancer import util + +LB_PLUGIN_CLASS = ( + "neutron_lbaas.services.loadbalancer." + "proxy_plugin.LoadBalancerProxyPluginv2" +) + +_uuid = uuidutils.generate_uuid +_get_path = test_base._get_path + + +_subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14" +base_url = '{}/{}'.format('http://127.0.0.1:9876', 'v2.0/lbaas') + + +class TestLbaasProxyPluginDbTestCase(util.LbaasTestMixin, + base.NeutronDbPluginV2TestCase): + fmt = 'json' + + def setUp(self, core_plugin=None, lb_plugin=None, lbaas_provider=None, + ext_mgr=None): + service_plugins = {'lb_plugin_name': LB_PLUGIN_CLASS} + + # removing service-type because it resides in neutron and tests + # dont care + LBPlugin = loadbalancer_plugin.LoadBalancerProxyPluginv2 + sea_index = None + for index, sea in enumerate(LBPlugin.supported_extension_aliases): + if sea == 'service-type': + sea_index = index + if sea_index: + del LBPlugin.supported_extension_aliases[sea_index] + + super(TestLbaasProxyPluginDbTestCase, self).setUp( + ext_mgr=ext_mgr, + service_plugins=service_plugins + ) + + if not ext_mgr: + self.plugin = loadbalancer_plugin.LoadBalancerProxyPluginv2() + # This is necessary because the automatic extension manager + # finding algorithm below will find the loadbalancerv2 + # extension and fail to initizlize the main API router with + # extensions' resources + ext_mgr = util.ExtendedPluginAwareExtensionManager( + LBPlugin.supported_extension_aliases) + + app = config.load_paste_app('extensions_test_app') + self.ext_api = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) + + self._subnet_id = _subnet_id + # set quotas to -1 (unlimited) + cfg.CONF.set_override('quota_loadbalancer', -1, group='QUOTAS') + + def _update_loadbalancer_api(self, lb_id, data): + req = self.new_update_request('loadbalancers', data, lb_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, req.get_response(self.ext_api)) + return resp, body + + def _delete_loadbalancer_api(self, lb_id): + req = self.new_delete_request('loadbalancers', lb_id) + resp = req.get_response(self.ext_api) + return resp + + def _get_loadbalancer_api(self, lb_id): + req = self.new_show_request('loadbalancers', lb_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _list_loadbalancers_api(self): + req = self.new_list_request('loadbalancers') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _get_loadbalancer_stats_api(self, lb_id): + req = self.new_show_request('loadbalancers', lb_id, + subresource='stats') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _get_loadbalancer_statuses_api(self, lb_id): + req = self.new_show_request('loadbalancers', lb_id, + subresource='statuses') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + +class LbaasLoadBalancerTests(TestLbaasProxyPluginDbTestCase): + url = '{}/{}'.format(base_url, 'loadbalancers') + + @requests_mock.mock() + def test_create_loadbalancer(self, m, **extras): + expected = { + 'name': 'vip1', + 'description': '', + 'admin_state_up': True, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'tenant_id': self._tenant_id, + 'listeners': [], + 'pools': [], + 'provider': 'lbaas' + } + + res = expected.copy() + res['id'] = 'fake_uuid' + expected.update(extras) + + m.post(self.url, json={'loadbalancer': res}) + with self.subnet() as subnet: + name = expected['name'] + with self.loadbalancer(name=name, subnet=subnet, + no_delete=True, **extras) as lb: + lb_id = lb['loadbalancer']['id'] + self.assertEqual('fake_uuid', lb_id) + actual = dict((k, v) + for k, v in lb['loadbalancer'].items() + if k in expected) + self.assertEqual(expected, actual) + return lb + + @requests_mock.mock() + def test_list_loadbalancers(self, m): + name = 'lb_show' + description = 'lb_show description' + expected_values = {'name': name, + 'description': description, + 'vip_address': '10.0.0.10', + 'admin_state_up': True, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'listeners': [], + 'provider': 'lbaas'} + + m.get(self.url, json={'loadbalancers': [expected_values]}) + resp, body = self._list_loadbalancers_api() + self.assertEqual(1, len(body['loadbalancers'])) + for k in expected_values: + self.assertEqual(expected_values[k], + body['loadbalancers'][0][k]) + + def _build_lb_list(self): + expected_values = [] + for name in ['lb1', 'lb2', 'lb3']: + expected_values.append({'name': name, + 'description': 'lb_show description', + 'vip_address': '10.0.0.10', + 'admin_state_up': True, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'listeners': [], + 'provider': 'lbaas', + 'id': name}) + return expected_values + + @requests_mock.mock() + def test_list_loadbalancers_with_sort_emulated(self, m): + expected_values = self._build_lb_list() + m.get(self.url, json={'loadbalancers': expected_values}) + + self._test_list_with_sort( + 'loadbalancer', + ({'loadbalancer': expected_values[0]}, + {'loadbalancer': expected_values[1]}, + {'loadbalancer': expected_values[2]}), + [('name', 'asc')] + ) + + @requests_mock.mock() + def test_list_loadbalancers_with_pagination_emulated(self, m): + expected_values = self._build_lb_list() + m.get(self.url, json={'loadbalancers': expected_values}) + self._test_list_with_pagination( + 'loadbalancer', + ({'loadbalancer': expected_values[0]}, + {'loadbalancer': expected_values[1]}, + {'loadbalancer': expected_values[2]}), + ('name', 'asc'), 2, 2 + ) + + @requests_mock.mock() + def test_list_loadbalancers_with_pagination_reverse_emulated(self, m): + expected_values = self._build_lb_list() + m.get(self.url, json={'loadbalancers': expected_values}) + self._test_list_with_pagination_reverse( + 'loadbalancer', + ({'loadbalancer': expected_values[0]}, + {'loadbalancer': expected_values[1]}, + {'loadbalancer': expected_values[2]}), + ('name', 'asc'), 2, 2 + ) + + @requests_mock.mock() + def test_show_loadbalancer(self, m): + name = 'lb_show' + description = 'lb_show description' + lb_id = "testid" + expected_values = {'name': name, + 'description': description, + 'vip_address': '10.0.0.10', + 'admin_state_up': True, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'listeners': [], + 'provider': 'lbaas', + 'id': lb_id} + m.get("{}/{}".format(self.url, lb_id), + json={'loadbalancer': expected_values}) + resp, body = self._get_loadbalancer_api(lb_id) + for k in expected_values: + self.assertEqual(expected_values[k], + body['loadbalancer'][k]) + + @requests_mock.mock() + def test_update_loadbalancer(self, m): + loadbalancer_id = "test_uuid" + name = 'new_loadbalancer' + description = 'a crazy loadbalancer' + expected_values = {'name': name, + 'description': description, + 'admin_state_up': False, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'listeners': [], + 'provider': 'lbaas', + 'id': loadbalancer_id} + + m.put("{}/{}".format(self.url, loadbalancer_id), + json={'loadbalancer': expected_values}) + # wonder why an update triggers a get... + m.get("{}/{}".format(self.url, loadbalancer_id), + json={'loadbalancer': expected_values}) + data = {'loadbalancer': {'name': name, + 'description': description, + 'admin_state_up': False}} + resp, res = self._update_loadbalancer_api(loadbalancer_id, + data) + for k in expected_values: + self.assertEqual(expected_values[k], + res['loadbalancer'][k]) + + @requests_mock.mock() + def test_delete_loadbalancer(self, m): + loadbalancer_id = "test_uuid" + expected_values = {'name': "test", + 'description': "test", + 'vip_address': '10.0.0.10', + 'admin_state_up': True, + 'provisioning_status': n_constants.ACTIVE, + 'operating_status': lb_const.ONLINE, + 'listeners': [], + 'provider': 'lbaas', + 'id': loadbalancer_id} + # wonder why an delete triggers a get... + m.get("{}/{}".format(self.url, loadbalancer_id), + json={'loadbalancer': expected_values}) + m.delete("{}/{}".format(self.url, loadbalancer_id)) + resp = self._delete_loadbalancer_api(loadbalancer_id) + self.assertEqual(webob.exc.HTTPNoContent.code, resp.status_int) + + @requests_mock.mock() + def test_delete_loadbalancer_when_loadbalancer_in_use(self, m): + lb_id = "123" + m.delete("{}/{}".format(self.url, lb_id), status_code=409) + ctx = context.get_admin_context() + # notice we raise a more generic exception then the stabdard plugin + self.assertRaises(lib_exc.Conflict, + self.plugin.delete_loadbalancer, + ctx, lb_id) + + +class ListenerTestBase(TestLbaasProxyPluginDbTestCase): + lb_id = uuid.uuid4().hex + + def _create_listener_api(self, data): + req = self.new_create_request("listeners", data, self.fmt) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _update_listener_api(self, listener_id, data): + req = self.new_update_request('listeners', data, listener_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, req.get_response(self.ext_api)) + return resp, body + + def _delete_listener_api(self, listener_id): + req = self.new_delete_request('listeners', listener_id) + resp = req.get_response(self.ext_api) + return resp + + def _get_listener_api(self, listener_id): + req = self.new_show_request('listeners', listener_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _list_listeners_api(self): + req = self.new_list_request('listeners') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + +class LbaasListenerTests(ListenerTestBase): + url = '{}/{}'.format(base_url, 'listeners') + + @requests_mock.mock() + def test_create_listener(self, m): + expected = { + 'protocol': 'HTTP', + 'protocol_port': 80, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'default_pool_id': None, + 'loadbalancers': [{'id': self.lb_id}], + 'id': '123' + } + + m.post(self.url, json={'listener': expected}) + with self.listener(loadbalancer_id=self.lb_id, + no_delete=True) as listener: + listener_id = listener['listener'].get('id') + self.assertTrue(listener_id) + actual = {} + for k, v in listener['listener'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + return listener + + @requests_mock.mock() + def test_create_listener_same_port_same_load_balancer(self, m): + m.post(self.url, status_code=409) + self._create_listener(self.fmt, 'HTTP', 80, + loadbalancer_id=self.lb_id, + expected_res_status=409, + no_delete=True) + + @requests_mock.mock() + def test_create_listener_with_tls_no_default_container(self, m, **extras): + listener_data = { + 'protocol': lb_const.PROTOCOL_TERMINATED_HTTPS, + 'default_tls_container_ref': None, + 'protocol_port': 443, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'loadbalancer_id': self.lb_id, + } + m.post(self.url, status_code=400) + listener_data.update(extras) + self.assertRaises( + lib_exc.BadRequest, + self.plugin.create_listener, + context.get_admin_context(), + {'listener': listener_data}) + + @requests_mock.mock() + def test_create_listener_loadbalancer_id_does_not_exist(self, m): + m.post(self.url, status_code=404) + self._create_listener(self.fmt, 'HTTP', 80, + loadbalancer_id=uuidutils.generate_uuid(), + expected_res_status=404) + + @requests_mock.mock() + def test_update_listener(self, m): + name = 'new_listener' + expected_values = {'name': name, + 'protocol_port': 80, + 'protocol': 'HTTP', + 'connection_limit': 100, + 'admin_state_up': False, + 'tenant_id': self._tenant_id, + 'loadbalancers': [{'id': self.lb_id}]} + + listener_id = uuidutils.generate_uuid() + # needs a get before a put... + m.get("{}/{}".format(self.url, listener_id), + json={'listener': expected_values}) + m.put("{}/{}".format(self.url, listener_id), + json={'listener': expected_values}) + data = {'listener': {'name': name, + 'connection_limit': 100, + 'admin_state_up': False}} + resp, body = self._update_listener_api(listener_id, data) + for k in expected_values: + self.assertEqual(expected_values[k], body['listener'][k]) + + @requests_mock.mock() + def test_delete_listener(self, m): + expected_values = {'name': 'test', + 'protocol_port': 80, + 'protocol': 'HTTP', + 'connection_limit': 100, + 'admin_state_up': False, + 'tenant_id': self._tenant_id, + 'loadbalancers': [{'id': self.lb_id}]} + listener_id = uuidutils.generate_uuid() + m.get("{}/{}".format(self.url, listener_id), + json={'listener': expected_values}) + m.delete("{}/{}".format(self.url, listener_id)) + + resp = self._delete_listener_api(listener_id) + self.assertEqual(webob.exc.HTTPNoContent.code, resp.status_int) + + @requests_mock.mock() + def test_show_listener(self, m): + name = 'show_listener' + expected_values = {'name': name, + 'protocol_port': 80, + 'protocol': 'HTTP', + 'connection_limit': -1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'default_pool_id': None, + 'loadbalancers': [{'id': self.lb_id}]} + listener_id = uuidutils.generate_uuid() + m.get("{}/{}".format(self.url, listener_id), + json={'listener': expected_values}) + resp, body = self._get_listener_api(listener_id) + for k in expected_values: + self.assertEqual(expected_values[k], body['listener'][k]) + + @requests_mock.mock() + def test_list_listeners(self, m): + name = 'list_listeners' + expected_values = {'name': name, + 'protocol_port': 80, + 'protocol': 'HTTP', + 'connection_limit': -1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'loadbalancers': [{'id': self.lb_id}]} + + listener_id = uuidutils.generate_uuid() + m.get(self.url, json={'listeners': [expected_values]}) + expected_values['id'] = listener_id + resp, body = self._list_listeners_api() + listener_list = body['listeners'] + self.assertEqual(1, len(listener_list)) + for k in expected_values: + self.assertEqual(expected_values[k], listener_list[0][k]) + + +class LbaasL7Tests(ListenerTestBase): + url = '{}/{}'.format(base_url, 'l7policies') + + def _rules(self, policy_id=uuidutils.generate_uuid()): + return "{}/{}/rules".format(self.url, policy_id) + + @requests_mock.mock() + def test_create_l7policy_invalid_listener_id(self, m, **extras): + m.post(self.url, status_code=404) + self._create_l7policy(self.fmt, uuidutils.generate_uuid(), + lb_const.L7_POLICY_ACTION_REJECT, + expected_res_status=webob.exc.HTTPNotFound.code) + + @requests_mock.mock() + def test_create_l7policy(self, m, **extras): + expected = { + 'action': lb_const.L7_POLICY_ACTION_REJECT, + 'redirect_pool_id': None, + 'redirect_url': None, + 'tenant_id': self._tenant_id, + } + expected.update(extras) + listener_id = uuidutils.generate_uuid() + expected['listener_id'] = listener_id + + m.post(self.url, json={'l7policy': expected}) + with self.l7policy(listener_id, no_delete=True) as p: + actual = {} + for k, v in p['l7policy'].items(): + if k in expected: + actual[k] = v + self.assertEqual(actual, expected) + + @requests_mock.mock() + def test_create_l7policy_pool_redirect(self, m, **extras): + expected = { + 'action': lb_const.L7_POLICY_ACTION_REDIRECT_TO_POOL, + 'redirect_pool_id': None, + 'redirect_url': None, + 'tenant_id': self._tenant_id, + } + expected.update(extras) + listener_id = uuidutils.generate_uuid() + pool_id = uuidutils.generate_uuid() + expected['listener_id'] = listener_id + expected['redirect_pool_id'] = pool_id + + m.post(self.url, json={'l7policy': expected}) + with self.l7policy( + listener_id, + action=lb_const.L7_POLICY_ACTION_REDIRECT_TO_POOL, + redirect_pool_id=pool_id, no_delete=True) as p: + actual = {} + for k, v in p['l7policy'].items(): + if k in expected: + actual[k] = v + self.assertEqual(actual, expected) + + @requests_mock.mock() + def test_update_l7policy(self, m, **extras): + l7policy_id = uuidutils.generate_uuid() + expected = { + 'admin_state_up': False, + 'action': lb_const.L7_POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_pool_id': None, + 'redirect_url': 'redirect_url', + 'tenant_id': self._tenant_id, + 'position': 1, + } + expected.update(extras) + + m.get('{}/{}'.format(self.url, l7policy_id), + json={'l7policy': expected}) + m.put('{}/{}'.format(self.url, l7policy_id), + json={'l7policy': expected}) + + data = { + 'l7policy': { + 'action': lb_const.L7_POLICY_ACTION_REDIRECT_TO_URL, + 'redirect_url': 'redirect_url', + 'admin_state_up': False}} + + ctx = context.get_admin_context() + self.plugin.update_l7policy(ctx, l7policy_id, data) + l7policy = self.plugin.get_l7policy(ctx, l7policy_id) + actual = {} + for k, v in l7policy.items(): + if k in expected: + actual[k] = v + self.assertEqual(actual, expected) + + @requests_mock.mock() + def test_delete_l7policy(self, m, **extras): + l7policy_id = uuidutils.generate_uuid() + + m.delete('{}/{}'.format(self.url, l7policy_id)) + + c = context.get_admin_context() + self.plugin.delete_l7policy(c, l7policy_id) + + @requests_mock.mock() + def test_show_l7policy(self, m, **extras): + listener_id = uuidutils.generate_uuid() + l7policy_id = uuidutils.generate_uuid() + expected = { + 'position': 1, + 'action': lb_const.L7_POLICY_ACTION_REJECT, + 'redirect_pool_id': None, + 'redirect_url': None, + 'tenant_id': self._tenant_id, + 'listener_id': listener_id, + 'id': l7policy_id, + } + expected.update(extras) + + m.get('{}/{}'.format(self.url, l7policy_id), + json={'l7policy': expected}) + + req = self.new_show_request('l7policies', + l7policy_id, + fmt=self.fmt) + res = self.deserialize(self.fmt, + req.get_response(self.ext_api)) + actual = {} + for k, v in res['l7policy'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + @requests_mock.mock() + def test_list_l7policies_with_sort_emulated(self, m): + listener_id = uuidutils.generate_uuid() + l7policy_id = uuidutils.generate_uuid() + expected = { + 'position': 1, + 'action': lb_const.L7_POLICY_ACTION_REJECT, + 'redirect_pool_id': None, + 'redirect_url': None, + 'tenant_id': self._tenant_id, + 'listener_id': listener_id, + 'id': l7policy_id, + } + m.get(self.url, json={'l7policies': [expected]}) + self._test_list_with_sort('l7policy', [{'l7policy': expected}], + [('name', 'asc')], + resources='l7policies') + + @requests_mock.mock() + def test_create_l7rule_invalid_policy_id(self, m, **extras): + l7_policy_id = uuidutils.generate_uuid() + m.post(self._rules(l7_policy_id), status_code=404) + self._create_l7policy_rule( + self.fmt, l7_policy_id, + lb_const.L7_RULE_TYPE_HOST_NAME, + lb_const.L7_RULE_COMPARE_TYPE_REGEX, + 'value', + expected_res_status=webob.exc.HTTPNotFound.code) + + @requests_mock.mock() + def test_create_l7rule(self, m, **extras): + l7_policy_id = uuidutils.generate_uuid() + expected = { + 'type': lb_const.L7_RULE_TYPE_HOST_NAME, + 'compare_type': lb_const.L7_RULE_COMPARE_TYPE_EQUAL_TO, + 'key': None, + 'value': 'value1' + } + + m.post(self._rules(l7_policy_id), json={'rule': expected}) + with self.l7policy_rule(l7_policy_id, no_delete=True) as r_def: + self.assertEqual(expected, r_def['rule']) + + @requests_mock.mock() + def test_update_l7rule(self, m, **extras): + l7_policy_id = uuidutils.generate_uuid() + l7_policy_rule_id = uuidutils.generate_uuid() + + expected = {} + expected['type'] = lb_const.L7_RULE_TYPE_HEADER + expected['compare_type'] = ( + lb_const.L7_RULE_COMPARE_TYPE_REGEX) + expected['value'] = '/.*/' + expected['key'] = 'HEADER1' + expected['invert'] = True + expected['admin_state_up'] = False + + m.get("{}/{}".format(self._rules(l7_policy_id), + l7_policy_rule_id), + json={'rule': expected}) + m.put("{}/{}".format(self._rules(l7_policy_id), + l7_policy_rule_id), + json={'rule': expected}) + req = self.new_update_request( + 'l7policies', {'rule': expected}, + l7_policy_id, subresource='rules', + sub_id=l7_policy_rule_id) + res = self.deserialize( + self.fmt, + req.get_response(self.ext_api) + ) + actual = {} + for k, v in res['rule'].items(): + if k in expected: + actual[k] = v + self.assertEqual(actual, expected) + + @requests_mock.mock() + def test_delete_l7rule(self, m): + l7_policy_id = uuidutils.generate_uuid() + l7_policy_rule_id = uuidutils.generate_uuid() + + m.get("{}/{}".format( + self._rules(l7_policy_id), l7_policy_rule_id), + json={'rule': {}}) + m.delete("{}/{}".format(self._rules(l7_policy_id), + l7_policy_rule_id)) + + req = self.new_delete_request('l7policies', + l7_policy_id, + subresource='rules', + sub_id=l7_policy_rule_id) + res = req.get_response(self.ext_api) + self.assertEqual(res.status_int, + webob.exc.HTTPNoContent.code) + + @requests_mock.mock() + def test_list_l7rules_with_sort_emulated(self, m): + l7_policy_id = uuidutils.generate_uuid() + expected = { + 'position': 1, + 'action': lb_const.L7_POLICY_ACTION_REJECT, + 'redirect_pool_id': None, + 'redirect_url': None, + 'tenant_id': self._tenant_id, + 'listener_id': uuidutils.generate_uuid(), + 'id': uuidutils.generate_uuid(), + } + + m.get(self._rules(l7_policy_id), + json={'rules': [expected]}) + + self._test_list_with_sort('l7policy', [{'rule': expected}], + [('value', 'asc')], + id=l7_policy_id, + resources='l7policies', + subresource='rule', + subresources='rules') + + +class PoolTestBase(ListenerTestBase): + + def _create_pool_api(self, data): + req = self.new_create_request("pools", data, self.fmt) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _update_pool_api(self, pool_id, data): + req = self.new_update_request('pools', data, pool_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _delete_pool_api(self, pool_id): + req = self.new_delete_request('pools', pool_id) + resp = req.get_response(self.ext_api) + return resp + + def _get_pool_api(self, pool_id): + req = self.new_show_request('pools', pool_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _list_pools_api(self): + req = self.new_list_request('pools') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + +class LbaasPoolTests(PoolTestBase): + url = '{}/{}'.format(base_url, 'pools') + listener_id = uuidutils.generate_uuid() + + @requests_mock.mock() + def test_create_pool(self, m, **extras): + expected = { + 'name': '', + 'description': '', + 'protocol': 'HTTP', + 'lb_algorithm': 'ROUND_ROBIN', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'healthmonitor_id': None, + 'members': [], + 'id': uuidutils.generate_uuid() + } + + expected.update(extras) + m.post(self.url, json={'pool': expected}) + m.get(self.url, json={'pools': [expected]}) + + with self.pool(listener_id=self.listener_id, no_delete=True, **extras) \ + as pool: + pool_id = pool['pool'].get('id') + if ('session_persistence' in expected.keys() and + expected['session_persistence'] is not None and + not expected['session_persistence'].get('cookie_name')): + expected['session_persistence']['cookie_name'] = None + self.assertTrue(pool_id) + + actual = {} + for k, v in pool['pool'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + return pool + + @requests_mock.mock() + def test_show_pool(self, m, **extras): + pool_id = uuidutils.generate_uuid() + expected = { + 'name': '', + 'description': '', + 'protocol': 'HTTP', + 'lb_algorithm': 'ROUND_ROBIN', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'listeners': [{'id': self.listener_id}], + 'healthmonitor_id': None, + 'members': [] + } + + expected.update(extras) + + m.get('{}/{}'.format(self.url, pool_id), json={'pool': expected}) + resp, body = self._get_pool_api(pool_id) + actual = {} + for k, v in body['pool'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + return resp + + @requests_mock.mock() + def test_update_pool(self, m, **extras): + pool_id = uuidutils.generate_uuid() + expected = { + 'name': '', + 'description': '', + 'protocol': 'HTTP', + 'lb_algorithm': 'LEAST_CONNECTIONS', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'listeners': [{'id': self.listener_id}], + 'healthmonitor_id': None, + 'members': [] + } + + expected.update(extras) + m.get('{}/{}'.format(self.url, pool_id), json={'pool': expected}) + m.put('{}/{}'.format(self.url, pool_id), json={'pool': expected}) + + data = {'pool': {'lb_algorithm': 'LEAST_CONNECTIONS'}} + resp, body = self._update_pool_api(pool_id, data) + actual = {} + for k, v in body['pool'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + return resp + + @requests_mock.mock() + def test_delete_pool(self, m): + pool_id = uuidutils.generate_uuid() + m.get('{}/{}'.format(self.url, pool_id), json={'pool': {}}) + m.delete('{}/{}'.format(self.url, pool_id)) + resp = self._delete_pool_api(pool_id) + self.assertEqual(webob.exc.HTTPNoContent.code, resp.status_int) + + @requests_mock.mock() + def test_cannot_add_multiple_pools_to_listener(self, m): + data = {'pool': {'name': '', + 'description': '', + 'protocol': 'HTTP', + 'lb_algorithm': 'ROUND_ROBIN', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'listener_id': self.listener_id}} + m.get(self.url, json={'pools': [data]}) + m.post(self.url, status_code=webob.exc.HTTPConflict.code) + resp, body = self._create_pool_api(data) + self.assertEqual(webob.exc.HTTPConflict.code, resp.status_int) + + @requests_mock.mock() + def test_create_pool_with_protocol_invalid(self, m): + data = {'pool': { + 'name': '', + 'description': '', + 'protocol': 'BLANK', + 'lb_algorithm': 'LEAST_CONNECTIONS', + 'admin_state_up': True, + 'tenant_id': self._tenant_id + }} + m.get(self.url, json={'pools': [data]}) + m.post(self.url, status_code=webob.exc.HTTPBadRequest.code) + resp, body = self._create_pool_api(data) + self.assertEqual(webob.exc.HTTPBadRequest.code, resp.status_int) + + @requests_mock.mock() + def test_list_pools(self, m): + name = 'list_pools' + expected_values = {'name': name, + 'protocol': 'HTTP', + 'description': 'apool', + 'lb_algorithm': 'ROUND_ROBIN', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'session_persistence': {'cookie_name': None, + 'type': 'HTTP_COOKIE'}, + 'loadbalancers': [{'id': self.lb_id}], + 'members': []} + + m.get(self.url, json={'pools': [expected_values]}) + resp, body = self._list_pools_api() + pool_list = body['pools'] + self.assertEqual(1, len(pool_list)) + for k in expected_values: + self.assertEqual(expected_values[k], pool_list[0][k]) + + +class MemberTestBase(PoolTestBase): + + def _create_member_api(self, pool_id, data): + req = self.new_create_request("pools", data, self.fmt, id=pool_id, + subresource='members') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _update_member_api(self, pool_id, member_id, data): + req = self.new_update_request('pools', data, pool_id, + subresource='members', sub_id=member_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _delete_member_api(self, pool_id, member_id): + req = self.new_delete_request('pools', pool_id, subresource='members', + sub_id=member_id) + resp = req.get_response(self.ext_api) + return resp + + def _get_member_api(self, pool_id, member_id): + req = self.new_show_request('pools', pool_id, subresource='members', + sub_id=member_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _list_members_api(self, pool_id): + req = self.new_list_request('pools', id=pool_id, subresource='members') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + +class LbaasMemberTests(MemberTestBase): + pool_id = uuidutils.generate_uuid() + member_id = uuidutils.generate_uuid() + url = '{}/{}'.format(base_url, 'pools') + test_subnet_id = uuidutils.generate_uuid() + + def _members(self, pool_id=None, member_id=None): + pool_id = pool_id if pool_id else self.pool_id + member = '/{}'.format(member_id) if member_id else '' + return "{}/{}/members{}".format(self.url, pool_id, member) + + @requests_mock.mock() + def test_create_member(self, m, **extras): + network = self._make_network(self.fmt, 'test-net', True) + self.test_subnet = self._make_subnet( + self.fmt, network, gateway=n_constants.ATTR_NOT_SPECIFIED, + cidr='10.0.0.0/24') + self.test_subnet_id = self.test_subnet['subnet']['id'] + expected = { + 'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': '', + 'name': 'member1', + 'id': uuidutils.generate_uuid() + } + + expected.update(extras) + expected['subnet_id'] = self.test_subnet_id + + m.post(self._members(), json={'member': expected}) + with self.member(pool_id=self.pool_id, name='member1', + no_delete=True) as member: + member_id = member['member'].get('id') + self.assertTrue(member_id) + + actual = {} + for k, v in member['member'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + return member + + @requests_mock.mock() + def test_update_member(self, m): + expected = { + 'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': '', + 'name': 'member1', + 'id': uuidutils.generate_uuid() + } + + m.get(self._members(member_id=self.member_id), + json={'member': expected}) + m.put(self._members(member_id=self.member_id), + json={'member': expected}) + + data = {'member': {'weight': 10, 'admin_state_up': False, + 'name': 'member2'}} + resp, body = self._update_member_api(self.pool_id, + self.member_id, data) + + actual = {} + for k, v in body['member'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + @requests_mock.mock() + def test_delete_member(self, m): + m.get(self._members(member_id=self.member_id), json={'member': {}}) + m.delete(self._members(member_id=self.member_id)) + resp = self._delete_member_api(self.pool_id, self.member_id) + self.assertEqual(webob.exc.HTTPNoContent.code, resp.status_int) + + @requests_mock.mock() + def test_show_member(self, m): + expected = { + 'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': '', + 'name': 'member1', + 'id': uuidutils.generate_uuid() + } + + m.get(self._members(member_id=self.member_id), + json={'member': expected}) + resp, body = self._get_member_api(self.pool_id, self.member_id) + actual = {} + for k, v in body['member'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + @requests_mock.mock() + def test_list_members(self, m): + expected = { + 'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': '', + 'name': 'member1', + 'id': uuidutils.generate_uuid() + } + m.get(self._members(), json={'members': [expected]}) + resp, body = self._list_members_api(self.pool_id) + self.assertEqual(1, len(body['members'])) + + @requests_mock.mock() + def test_create_member_invalid_pool_id(self, m): + data = {'member': {'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': self.test_subnet_id}} + m.post(self._members(pool_id='WRONG_POOL_ID'), status_code=404) + resp, body = self._create_member_api('WRONG_POOL_ID', data) + self.assertEqual(webob.exc.HTTPNotFound.code, resp.status_int) + + @requests_mock.mock() + def test_create_member_invalid_name(self, m): + m.post(self._members(), status_code=webob.exc.HTTPBadRequest.code) + data = {'member': {'address': '127.0.0.1', + 'protocol_port': 80, + 'weight': 1, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'subnet_id': self.test_subnet_id, + 'name': 123}} + resp, body = self._create_member_api('POOL_ID', data) + self.assertEqual(webob.exc.HTTPBadRequest.code, resp.status_int) + + @requests_mock.mock() + def test_delete_member_invalid_pool_id(self, m): + m.get(self._members('WRONG_POOL_ID', self.member_id), status_code=404) + + resp = self._delete_member_api('WRONG_POOL_ID', self.member_id) + self.assertEqual(webob.exc.HTTPNotFound.code, resp.status_int) + + +class HealthMonitorTestBase(MemberTestBase): + + def _create_healthmonitor_api(self, data): + req = self.new_create_request("healthmonitors", data, self.fmt) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _update_healthmonitor_api(self, hm_id, data): + req = self.new_update_request('healthmonitors', data, hm_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _delete_healthmonitor_api(self, hm_id): + req = self.new_delete_request('healthmonitors', hm_id) + resp = req.get_response(self.ext_api) + return resp + + def _get_healthmonitor_api(self, hm_id): + req = self.new_show_request('healthmonitors', hm_id) + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + def _list_healthmonitors_api(self): + req = self.new_list_request('healthmonitors') + resp = req.get_response(self.ext_api) + body = self.deserialize(self.fmt, resp) + return resp, body + + +class TestLbaasHealthMonitorTests(HealthMonitorTestBase): + url = '{}/{}'.format(base_url, 'healthmonitors') + hm_id = uuidutils.generate_uuid() + pool_id = uuidutils.generate_uuid() + + @requests_mock.mock() + def test_create_healthmonitor(self, m, **extras): + expected = { + 'type': 'HTTP', + 'delay': 1, + 'timeout': 1, + 'max_retries': 2, + 'http_method': 'GET', + 'url_path': '/', + 'expected_codes': '200', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'pools': [{'id': self.pool_id}], + 'name': 'monitor1', + 'id': self.hm_id + } + + expected.update(extras) + m.post(self.url, json={'healthmonitor': expected}) + + with self.healthmonitor(pool_id=self.pool_id, type='HTTP', + name='monitor1', no_delete=True, + **extras) as healthmonitor: + hm_id = healthmonitor['healthmonitor'].get('id') + self.assertTrue(hm_id) + + actual = {} + for k, v in healthmonitor['healthmonitor'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + return healthmonitor + + @requests_mock.mock() + def test_show_healthmonitor(self, m, **extras): + expected = { + 'type': 'HTTP', + 'delay': 1, + 'timeout': 1, + 'max_retries': 2, + 'http_method': 'GET', + 'url_path': '/', + 'expected_codes': '200', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'pools': [{'id': self.pool_id}], + 'name': 'monitor1' + } + + expected.update(extras) + + m.get('{}/{}'.format(self.url, self.hm_id), + json={'healthmonitor': expected}) + + resp, body = self._get_healthmonitor_api(self.hm_id) + actual = {} + for k, v in body['healthmonitor'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + @requests_mock.mock() + def test_update_healthmonitor(self, m, **extras): + expected = { + 'type': 'HTTP', + 'delay': 30, + 'timeout': 10, + 'max_retries': 4, + 'http_method': 'GET', + 'url_path': '/index.html', + 'expected_codes': '200,404', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'pools': [{'id': self.pool_id}], + 'name': 'monitor2' + } + + expected.update(extras) + m.get('{}/{}'.format(self.url, self.hm_id), + json={'healthmonitor': expected}) + m.put('{}/{}'.format(self.url, self.hm_id), + json={'healthmonitor': expected}) + + data = {'healthmonitor': {'delay': 30, + 'timeout': 10, + 'max_retries': 4, + 'expected_codes': '200,404', + 'url_path': '/index.html', + 'name': 'monitor2'}} + resp, body = self._update_healthmonitor_api(self.hm_id, data) + actual = {} + for k, v in body['healthmonitor'].items(): + if k in expected: + actual[k] = v + self.assertEqual(expected, actual) + + @requests_mock.mock() + def test_delete_healthmonitor(self, m): + m.get('{}/{}'.format(self.url, self.hm_id), json={'healthmonitor': {}}) + m.delete('{}/{}'.format(self.url, self.hm_id)) + resp = self._delete_healthmonitor_api(self.hm_id) + self.assertEqual(webob.exc.HTTPNoContent.code, resp.status_int) + + @requests_mock.mock() + def test_create_health_monitor_with_timeout_invalid(self, m): + m.post(self.url, status_code=webob.exc.HTTPBadRequest.code) + data = {'healthmonitor': {'type': 'HTTP', + 'delay': 1, + 'timeout': -1, + 'max_retries': 2, + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'pool_id': self.pool_id}} + resp, body = self._create_healthmonitor_api(data) + self.assertEqual(webob.exc.HTTPBadRequest.code, resp.status_int) + + @requests_mock.mock() + def test_create_healthmonitor_invalid_pool_id(self, m): + m.post(self.url, status_code=webob.exc.HTTPNotFound.code) + data = {'healthmonitor': {'type': lb_const.HEALTH_MONITOR_TCP, + 'delay': 1, + 'timeout': 1, + 'max_retries': 1, + 'tenant_id': self._tenant_id, + 'pool_id': uuidutils.generate_uuid()}} + resp, body = self._create_healthmonitor_api(data) + self.assertEqual(webob.exc.HTTPNotFound.code, resp.status_int) + + @requests_mock.mock() + def test_only_one_healthmonitor_per_pool(self, m): + m.post(self.url, status_code=webob.exc.HTTPConflict.code) + data = {'healthmonitor': {'type': lb_const.HEALTH_MONITOR_TCP, + 'delay': 1, + 'timeout': 1, + 'max_retries': 1, + 'tenant_id': self._tenant_id, + 'pool_id': self.pool_id}} + resp, body = self._create_healthmonitor_api(data) + self.assertEqual(webob.exc.HTTPConflict.code, resp.status_int) + + @requests_mock.mock() + def test_list_healthmonitors(self, m): + expected = { + 'type': 'HTTP', + 'delay': 1, + 'timeout': 1, + 'max_retries': 2, + 'http_method': 'GET', + 'url_path': '/', + 'expected_codes': '200', + 'admin_state_up': True, + 'tenant_id': self._tenant_id, + 'pools': [{'id': self.pool_id}], + 'name': '', + 'max_retries_down': 3, + 'id': self.hm_id + } + + m.get(self.url, json={'healthmonitors': [expected]}) + + resp, body = self._list_healthmonitors_api() + self.assertEqual([expected], body['healthmonitors']) + + +class LbaasStatusesTest(MemberTestBase): + lb_id = uuidutils.generate_uuid() + url = '{}/{}'.format(base_url, 'loadbalancers') + + @requests_mock.mock() + def test_status_tree_lb(self, m): + expected = { + "loadbalancer": { + "operating_status": "ONLINE", + "provisioning_status": "ACTIVE", + "listeners": [ + { + "id": "6978ba19-1090-48a2-93d5-3523592b562a", + "operating_status": "ONLINE", + "provisioning_status": "ACTIVE", + "pools": [ + { + "id": "f6aedfcb-9f7d-4cc5-83e1-8c02fd833922", + "operating_status": "ONLINE", + "provisioning_status": "ACTIVE", + "members": [ + { + "id": + "fcf23bde-8cf9-4616-883f-208cebcbf858", + "operating_status": "ONLINE", + "provisioning_status": "ACTIVE", + } + ], + "healthmonitor": { + "id": + "785131d2-8f7b-4fee-a7e7-3196e11b4518", + "provisioning_status": "ACTIVE", + } + } + ] + } + ] + } + } + m.get('{}/{}'.format(self.url, self.lb_id), + json={'loadbalancer': {'id': self.lb_id}}) + m.get('{}/{}/statuses'.format(self.url, self.lb_id), + json={'statuses': expected}) + statuses = self._get_loadbalancer_statuses_api(self.lb_id)[1] + self.assertEqual(expected, statuses) + + +class LbaasGraphTest(MemberTestBase): + lb_id = uuidutils.generate_uuid() + url = '{}/{}'.format(base_url, 'graph') + + @requests_mock.mock() + def create_graph(self, m, expected_lb_graph, listeners): + with self.subnet() as subnet: + expected_lb_graph['vip_subnet_id'] = subnet['subnet']['id'] + for listener in listeners: + for member in listener.get('default_pool', + {}).get('members', []): + member['subnet_id'] = subnet['subnet']['id'] + for listener in expected_lb_graph.get('listeners', []): + for member in listener.get('default_pool', + {}).get('members', []): + member['subnet_id'] = subnet['subnet']['id'] + name = expected_lb_graph.get('name') + kwargs = {'name': name, 'subnet': subnet, 'listeners': listeners} + m.post(self.url, json={'graph': expected_lb_graph}) + with self.graph(no_delete=True, **kwargs) as graph: + lb = graph['graph']['loadbalancer'] + self._assert_graphs_equal(expected_lb_graph, lb) diff --git a/neutron_lbaas/tests/unit/db/loadbalancer/util.py b/neutron_lbaas/tests/unit/db/loadbalancer/util.py new file mode 100644 index 000000000..09e3d155c --- /dev/null +++ b/neutron_lbaas/tests/unit/db/loadbalancer/util.py @@ -0,0 +1,529 @@ +# Copyright 2017, Rackspace US, Inc. +# +# 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 contextlib + +from neutron.tests.unit.db import test_db_base_plugin_v2 +from neutron_lib import constants +from neutron_lib import context +import webob.exc + +from neutron_lbaas._i18n import _ +from neutron_lbaas.db.loadbalancer import models +from neutron_lbaas.extensions import healthmonitor_max_retries_down +from neutron_lbaas.extensions import l7 +from neutron_lbaas.extensions import lb_graph +from neutron_lbaas.extensions import lb_network_vip +from neutron_lbaas.extensions import loadbalancerv2 +from neutron_lbaas.extensions import sharedpools +from neutron_lbaas.services.loadbalancer import constants as lb_const + + +class ExtendedPluginAwareExtensionManager(object): + def __init__(self, extension_aliases): + self.extension_aliases = extension_aliases + + def get_resources(self): + extensions_list = [] + if 'shared_pools' in self.extension_aliases: + extensions_list.append(sharedpools) + if 'l7' in self.extension_aliases: + extensions_list.append(l7) + if 'lb-graph' in self.extension_aliases: + extensions_list.append(lb_graph) + if 'lb_network_vip' in self.extension_aliases: + extensions_list.append(lb_network_vip) + if 'hm_max_retries_down' in self.extension_aliases: + extensions_list.append(healthmonitor_max_retries_down) + for extension in extensions_list: + if 'RESOURCE_ATTRIBUTE_MAP' in extension.__dict__: + loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.update( + extension.RESOURCE_ATTRIBUTE_MAP) + if 'SUB_RESOURCE_ATTRIBUTE_MAP' in extension.__dict__: + loadbalancerv2.SUB_RESOURCE_ATTRIBUTE_MAP.update( + extension.SUB_RESOURCE_ATTRIBUTE_MAP) + if 'EXTENDED_ATTRIBUTES_2_0' in extension.__dict__: + for key in loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.keys(): + loadbalancerv2.RESOURCE_ATTRIBUTE_MAP[key].update( + extension.EXTENDED_ATTRIBUTES_2_0.get(key, {})) + return loadbalancerv2.Loadbalancerv2.get_resources() + + def get_actions(self): + return [] + + def get_request_extensions(self): + return [] + + +class LbaasTestMixin(object): + resource_keys = list(loadbalancerv2.RESOURCE_ATTRIBUTE_MAP.keys()) + resource_keys.extend(l7.RESOURCE_ATTRIBUTE_MAP.keys()) + resource_keys.extend(lb_graph.RESOURCE_ATTRIBUTE_MAP.keys()) + resource_keys.extend(lb_network_vip.EXTENDED_ATTRIBUTES_2_0.keys()) + resource_keys.extend(healthmonitor_max_retries_down. + EXTENDED_ATTRIBUTES_2_0.keys()) + resource_prefix_map = dict( + (k, loadbalancerv2.LOADBALANCERV2_PREFIX) + for k in resource_keys) + + def _get_loadbalancer_optional_args(self): + return ('description', 'vip_address', 'admin_state_up', 'name', + 'listeners', 'vip_network_id', 'vip_subnet_id') + + def _create_loadbalancer(self, fmt, subnet_id, + expected_res_status=None, **kwargs): + data = {'loadbalancer': {'vip_subnet_id': subnet_id, + 'tenant_id': self._tenant_id}} + args = self._get_loadbalancer_optional_args() + for arg in args: + if arg in kwargs: + if kwargs[arg] is not None: + data['loadbalancer'][arg] = kwargs[arg] + else: + data['loadbalancer'].pop(arg, None) + + lb_req = self.new_create_request('loadbalancers', data, fmt) + lb_res = lb_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, lb_res.status_int) + + return lb_res + + def _create_graph(self, fmt, subnet_id, expected_res_status=None, + **kwargs): + data = {'vip_subnet_id': subnet_id, 'tenant_id': self._tenant_id} + args = self._get_loadbalancer_optional_args() + for arg in args: + if arg in kwargs and kwargs[arg] is not None: + data[arg] = kwargs[arg] + + data = {'graph': {'loadbalancer': data, 'tenant_id': self._tenant_id}} + lb_req = self.new_create_request('graphs', data, fmt) + lb_res = lb_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, lb_res.status_int) + + return lb_res + + def _get_listener_optional_args(self): + return ('name', 'description', 'default_pool_id', 'loadbalancer_id', + 'connection_limit', 'admin_state_up', + 'default_tls_container_ref', 'sni_container_refs') + + def _create_listener(self, fmt, protocol, protocol_port, + loadbalancer_id=None, default_pool_id=None, + expected_res_status=None, **kwargs): + data = {'listener': {'protocol': protocol, + 'protocol_port': protocol_port, + 'tenant_id': self._tenant_id}} + if loadbalancer_id: + data['listener']['loadbalancer_id'] = loadbalancer_id + if default_pool_id: + data['listener']['default_pool_id'] = default_pool_id + + args = self._get_listener_optional_args() + for arg in args: + if arg in kwargs and kwargs[arg] is not None: + data['listener'][arg] = kwargs[arg] + + listener_req = self.new_create_request('listeners', data, fmt) + listener_res = listener_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, listener_res.status_int) + + return listener_res + + def _get_pool_optional_args(self): + return 'name', 'description', 'admin_state_up', 'session_persistence' + + def _create_pool(self, fmt, protocol, lb_algorithm, listener_id=None, + loadbalancer_id=None, expected_res_status=None, **kwargs): + data = {'pool': {'protocol': protocol, + 'lb_algorithm': lb_algorithm, + 'tenant_id': self._tenant_id}} + if listener_id: + data['pool']['listener_id'] = listener_id + if loadbalancer_id: + data['pool']['loadbalancer_id'] = loadbalancer_id + + args = self._get_pool_optional_args() + for arg in args: + if arg in kwargs and kwargs[arg] is not None: + data['pool'][arg] = kwargs[arg] + + pool_req = self.new_create_request('pools', data, fmt) + pool_res = pool_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, pool_res.status_int) + + return pool_res + + def _get_member_optional_args(self): + return 'weight', 'admin_state_up', 'name' + + def _create_member(self, fmt, pool_id, address, protocol_port, subnet_id, + expected_res_status=None, **kwargs): + data = {'member': {'address': address, + 'protocol_port': protocol_port, + 'subnet_id': subnet_id, + 'tenant_id': self._tenant_id}} + + args = self._get_member_optional_args() + for arg in args: + if arg in kwargs and kwargs[arg] is not None: + data['member'][arg] = kwargs[arg] + member_req = self.new_create_request('pools', + data, + fmt=fmt, + id=pool_id, + subresource='members') + member_res = member_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, member_res.status_int) + + return member_res + + def _get_healthmonitor_optional_args(self): + return ('weight', 'admin_state_up', 'expected_codes', 'url_path', + 'http_method', 'name', 'max_retries_down') + + def _create_healthmonitor(self, fmt, pool_id, type, delay, timeout, + max_retries, expected_res_status=None, **kwargs): + data = {'healthmonitor': {'type': type, + 'delay': delay, + 'timeout': timeout, + 'max_retries': max_retries, + 'pool_id': pool_id, + 'tenant_id': self._tenant_id}} + + args = self._get_healthmonitor_optional_args() + for arg in args: + if arg in kwargs and kwargs[arg] is not None: + data['healthmonitor'][arg] = kwargs[arg] + + hm_req = self.new_create_request('healthmonitors', data, fmt=fmt) + hm_res = hm_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(expected_res_status, hm_res.status_int) + + return hm_res + + def _add_optional_args(self, optional_args, data, **kwargs): + for arg in optional_args: + if arg in kwargs and kwargs[arg] is not None: + data[arg] = kwargs[arg] + + def _get_l7policy_optional_args(self): + return ('name', 'description', 'redirect_pool_id', + 'redirect_url', 'admin_state_up', 'position') + + def _create_l7policy(self, fmt, listener_id, action, + expected_res_status=None, **kwargs): + data = {'l7policy': {'listener_id': listener_id, + 'action': action, + 'tenant_id': self._tenant_id}} + + optional_args = self._get_l7policy_optional_args() + self._add_optional_args(optional_args, data['l7policy'], **kwargs) + + l7policy_req = self.new_create_request('l7policies', data, fmt) + l7policy_res = l7policy_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(l7policy_res.status_int, expected_res_status) + + return l7policy_res + + def _get_l7rule_optional_args(self): + return ('invert', 'key', 'admin_state_up') + + def _create_l7policy_rule(self, fmt, l7policy_id, type, compare_type, + value, expected_res_status=None, **kwargs): + data = {'rule': {'type': type, + 'compare_type': compare_type, + 'value': value, + 'tenant_id': self._tenant_id}} + + optional_args = self._get_l7rule_optional_args() + self._add_optional_args(optional_args, data['rule'], **kwargs) + + rule_req = self.new_create_request('l7policies', data, fmt, + id=l7policy_id, + subresource='rules') + rule_res = rule_req.get_response(self.ext_api) + if expected_res_status: + self.assertEqual(rule_res.status_int, expected_res_status) + + return rule_res + + @contextlib.contextmanager + def loadbalancer(self, fmt=None, subnet=None, no_delete=False, **kwargs): + if not fmt: + fmt = self.fmt + + with test_db_base_plugin_v2.optional_ctx( + subnet, self.subnet) as tmp_subnet: + + res = self._create_loadbalancer(fmt, + tmp_subnet['subnet']['id'], + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + exc = webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % + res.status_int) + exc.code = res.status_int + exc.status_code = res.status_int + raise exc + lb = self.deserialize(fmt or self.fmt, res) + yield lb + if not no_delete: + self._delete('loadbalancers', lb['loadbalancer']['id']) + + @contextlib.contextmanager + def graph(self, fmt=None, subnet=None, no_delete=False, **kwargs): + if not fmt: + fmt = self.fmt + + with test_db_base_plugin_v2.optional_ctx( + subnet, self.subnet) as tmp_subnet: + + res = self._create_graph(fmt, tmp_subnet['subnet']['id'], + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + exc = webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % + res.status_int + ) + exc.code = res.status_int + exc.status_code = res.status_int + raise exc + graph = self.deserialize(fmt or self.fmt, res) + yield graph + if not no_delete: + # delete loadbalancer children if this was a loadbalancer + # graph create call + lb = graph['graph']['loadbalancer'] + for listener in lb.get('listeners', []): + pool = listener.get('default_pool') + if pool: + hm = pool.get('healthmonitor') + if hm: + self._delete('healthmonitors', hm['id']) + members = pool.get('members', []) + for member in members: + self._delete('pools', pool['id'], + subresource='members', + sub_id=member['id']) + self._delete('pools', pool['id']) + policies = listener.get('l7policies', []) + for policy in policies: + r_pool = policy.get('redirect_pool') + if r_pool: + r_hm = r_pool.get('healthmonitor') + if r_hm: + self._delete('healthmonitors', r_hm['id']) + r_members = r_pool.get('members', []) + for r_member in r_members: + self._delete('pools', r_pool['id'], + subresource='members', + sub_id=r_member['id']) + self._delete('pools', r_pool['id']) + self._delete('l7policies', policy['id']) + self._delete('listeners', listener['id']) + self._delete('loadbalancers', lb['id']) + + @contextlib.contextmanager + def listener(self, fmt=None, protocol='HTTP', loadbalancer_id=None, + protocol_port=80, default_pool_id=None, no_delete=False, + **kwargs): + if not fmt: + fmt = self.fmt + + if loadbalancer_id and default_pool_id: + res = self._create_listener(fmt, protocol, protocol_port, + loadbalancer_id=loadbalancer_id, + default_pool_id=default_pool_id, + **kwargs) + elif loadbalancer_id: + res = self._create_listener(fmt, protocol, protocol_port, + loadbalancer_id=loadbalancer_id, + **kwargs) + else: + res = self._create_listener(fmt, protocol, protocol_port, + default_pool_id=default_pool_id, + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + listener = self.deserialize(fmt or self.fmt, res) + yield listener + if not no_delete: + self._delete('listeners', listener['listener']['id']) + + @contextlib.contextmanager + def pool(self, fmt=None, protocol='HTTP', lb_algorithm='ROUND_ROBIN', + no_delete=False, listener_id=None, + loadbalancer_id=None, **kwargs): + if not fmt: + fmt = self.fmt + + if listener_id and loadbalancer_id: + res = self._create_pool(fmt, + protocol=protocol, + lb_algorithm=lb_algorithm, + listener_id=listener_id, + loadbalancer_id=loadbalancer_id, + **kwargs) + elif listener_id: + res = self._create_pool(fmt, + protocol=protocol, + lb_algorithm=lb_algorithm, + listener_id=listener_id, + **kwargs) + else: + res = self._create_pool(fmt, + protocol=protocol, + lb_algorithm=lb_algorithm, + loadbalancer_id=loadbalancer_id, + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + pool = self.deserialize(fmt or self.fmt, res) + yield pool + if not no_delete: + self._delete('pools', pool['pool']['id']) + + @contextlib.contextmanager + def member(self, fmt=None, pool_id='pool1id', address='127.0.0.1', + protocol_port=80, subnet=None, no_delete=False, + **kwargs): + if not fmt: + fmt = self.fmt + subnet = subnet or self.test_subnet + with test_db_base_plugin_v2.optional_ctx( + subnet, self.subnet) as tmp_subnet: + + res = self._create_member(fmt, + pool_id=pool_id, + address=address, + protocol_port=protocol_port, + subnet_id=tmp_subnet['subnet']['id'], + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + member = self.deserialize(fmt or self.fmt, res) + yield member + if not no_delete: + self._delete('pools', id=pool_id, subresource='members', + sub_id=member['member']['id']) + + @contextlib.contextmanager + def healthmonitor(self, fmt=None, pool_id='pool1id', type='TCP', delay=1, + timeout=1, max_retries=2, no_delete=False, **kwargs): + if not fmt: + fmt = self.fmt + + res = self._create_healthmonitor(fmt, + pool_id=pool_id, + type=type, + delay=delay, + timeout=timeout, + max_retries=max_retries, + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + healthmonitor = self.deserialize(fmt or self.fmt, res) + yield healthmonitor + if not no_delete: + del_req = self.new_delete_request( + 'healthmonitors', fmt=fmt, + id=healthmonitor['healthmonitor']['id']) + del_res = del_req.get_response(self.ext_api) + self.assertEqual(webob.exc.HTTPNoContent.code, del_res.status_int) + + @contextlib.contextmanager + def l7policy(self, listener_id, fmt=None, + action=lb_const.L7_POLICY_ACTION_REJECT, + no_delete=False, **kwargs): + if not fmt: + fmt = self.fmt + + res = self._create_l7policy(fmt, + listener_id=listener_id, + action=action, + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + l7policy = self.deserialize(fmt or self.fmt, res) + yield l7policy + if not no_delete: + self.plugin.db.update_status(context.get_admin_context(), + models.L7Policy, + l7policy['l7policy']['id'], + constants.ACTIVE) + del_req = self.new_delete_request( + 'l7policies', + fmt=fmt, + id=l7policy['l7policy']['id']) + del_res = del_req.get_response(self.ext_api) + self.assertEqual(del_res.status_int, + webob.exc.HTTPNoContent.code) + + @contextlib.contextmanager + def l7policy_rule(self, l7policy_id, fmt=None, value='value1', + type=lb_const.L7_RULE_TYPE_HOST_NAME, + compare_type=lb_const.L7_RULE_COMPARE_TYPE_EQUAL_TO, + no_delete=False, **kwargs): + if not fmt: + fmt = self.fmt + res = self._create_l7policy_rule(fmt, + l7policy_id=l7policy_id, + type=type, + compare_type=compare_type, + value=value, + **kwargs) + if res.status_int >= webob.exc.HTTPClientError.code: + raise webob.exc.HTTPClientError( + explanation=_("Unexpected error code: %s") % res.status_int + ) + + rule = self.deserialize(fmt or self.fmt, res) + yield rule + if not no_delete: + self.plugin.db.update_status(context.get_admin_context(), + models.L7Rule, + rule['rule']['id'], + constants.ACTIVE) + del_req = self.new_delete_request( + 'l7policies', + fmt=fmt, + id=l7policy_id, + subresource='rules', + sub_id=rule['rule']['id']) + del_res = del_req.get_response(self.ext_api) + self.assertEqual(del_res.status_int, + webob.exc.HTTPNoContent.code) diff --git a/neutron_lbaas/tests/unit/test_agent_scheduler.py b/neutron_lbaas/tests/unit/test_agent_scheduler.py index 5d4abadd3..d8f4b2f3a 100644 --- a/neutron_lbaas/tests/unit/test_agent_scheduler.py +++ b/neutron_lbaas/tests/unit/test_agent_scheduler.py @@ -38,6 +38,7 @@ from neutron_lbaas.extensions import lbaas_agentschedulerv2 from neutron_lbaas.services.loadbalancer import constants as lb_const from neutron_lbaas.tests import base from neutron_lbaas.tests.unit.db.loadbalancer import test_db_loadbalancerv2 +from neutron_lbaas.tests.unit.db.loadbalancer import util LBAAS_HOSTA = 'hosta' extensions_path = ':'.join(neutron.tests.unit.extensions.__path__) @@ -65,7 +66,7 @@ class AgentSchedulerTestMixIn(test_agentschedulers_db.AgentSchedulerTestMixIn): class LBaaSAgentSchedulerTestCase(test_agent.AgentDBTestMixIn, AgentSchedulerTestMixIn, - test_db_loadbalancerv2.LbaasTestMixin, + util.LbaasTestMixin, base.NeutronDbPluginV2TestCase): fmt = 'json' plugin_str = 'neutron.plugins.ml2.plugin.Ml2Plugin' diff --git a/setup.cfg b/setup.cfg index c0dc0e9ca..0cf2e4c7c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,6 +36,7 @@ loadbalancer_schedulers = neutron_lbaas.agent_scheduler.ChanceScheduler = neutron_lbaas.agent_scheduler:ChanceScheduler neutron.service_plugins = lbaasv2 = neutron_lbaas.services.loadbalancer.plugin:LoadBalancerPluginv2 + lbaasv2-proxy = neutron_lbaas.services.loadbalancer.proxy_plugin:LoadBalancerProxyPluginv2 neutron.db.alembic_migrations = neutron-lbaas = neutron_lbaas.db.migration:alembic_migrations neutron_lbaas.cert_manager.backend =