Browse Source

Create scenario tests for load balancers

This patch implements the tempest plugin for
for testing load balancer creation in Octavia.

Co-Authored-By: Jude Cross <jcross@godaddy.com>
Co-Authored-By: Lingxian Kong <anlin.kong@gmail.com>

Depends-On: https://review.openstack.org/557856
Change-Id: I57064f8e0834efba8859a780394a1c69851cc917
changes/34/543034/31
Jude Cross 4 years ago
committed by Michael Johnson
parent
commit
986e3f543c
34 changed files with 2987 additions and 70 deletions
  1. +5
    -1
      .gitignore
  2. +9
    -0
      doc/requirements.txt
  3. +47
    -3
      doc/source/conf.py
  4. +26
    -0
      doc/source/configref.rst
  5. +6
    -1
      doc/source/index.rst
  6. +31
    -0
      octavia_tempest_plugin/clients.py
  7. +0
    -0
      octavia_tempest_plugin/common/__init__.py
  8. +62
    -0
      octavia_tempest_plugin/common/constants.py
  9. +146
    -0
      octavia_tempest_plugin/config.py
  10. +0
    -0
      octavia_tempest_plugin/contrib/__init__.py
  11. +0
    -0
      octavia_tempest_plugin/contrib/httpd/__init__.py
  12. +60
    -0
      octavia_tempest_plugin/plugin.py
  13. +0
    -0
      octavia_tempest_plugin/services/__init__.py
  14. +17
    -0
      octavia_tempest_plugin/services/load_balancer/__init__.py
  15. +18
    -0
      octavia_tempest_plugin/services/load_balancer/v2/__init__.py
  16. +526
    -0
      octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py
  17. +0
    -0
      octavia_tempest_plugin/tests/api/__init__.py
  18. +0
    -0
      octavia_tempest_plugin/tests/api/v2/__init__.py
  19. +834
    -0
      octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
  20. +0
    -23
      octavia_tempest_plugin/tests/base.py
  21. +0
    -0
      octavia_tempest_plugin/tests/scenario/__init__.py
  22. +0
    -0
      octavia_tempest_plugin/tests/scenario/v2/__init__.py
  23. +120
    -0
      octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
  24. +683
    -0
      octavia_tempest_plugin/tests/test_base.py
  25. +0
    -28
      octavia_tempest_plugin/tests/test_octavia_tempest_plugin.py
  26. +87
    -0
      octavia_tempest_plugin/tests/validators.py
  27. +174
    -0
      octavia_tempest_plugin/tests/waiters.py
  28. +11
    -0
      playbooks/Octavia-DSVM/pre.yaml
  29. +7
    -0
      requirements.txt
  30. +8
    -0
      setup.cfg
  31. +0
    -5
      test-requirements.txt
  32. +19
    -1
      tox.ini
  33. +75
    -8
      zuul.d/jobs.yaml
  34. +16
    -0
      zuul.d/projects.yaml

+ 5
- 1
.gitignore View File

@ -44,6 +44,7 @@ output/*/index.html
# Sphinx
doc/build
doc/source/_build
# pbr generates these
AUTHORS
@ -55,4 +56,7 @@ ChangeLog
.*sw?
# Files created by releasenotes build
releasenotes/build
releasenotes/build
# Oslo config generator
etc/octavia.tempest.conf.sample

+ 9
- 0
doc/requirements.txt View File

@ -0,0 +1,9 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
sphinx>=1.6.2,!=1.6.6,!=1.6.7 # BSD
openstackdocstheme>=1.18.1 # Apache-2.0
# releasenotes
reno>=2.5.0 # Apache-2.0

+ 47
- 3
doc/source/conf.py View File

@ -15,20 +15,27 @@
import os
import sys
import openstackdocstheme
from sphinx import apidoc
sys.path.insert(0, os.path.abspath('../..'))
sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'oslosphinx'
'sphinx.ext.viewcode',
'openstackdocstheme',
'oslo_config.sphinxext'
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
@ -45,11 +52,14 @@ add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
add_module_names = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['octavia_tempest_plugin.']
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
@ -58,9 +68,19 @@ pygments_style = 'sphinx'
# html_theme = '_theme'
# html_static_path = ['static']
html_theme = 'openstackdocs'
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# If false, no module index is generated.
html_domain_indices = True
# If false, no index is generated.
html_use_index = True
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
@ -73,3 +93,27 @@ latex_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}
repository_name = 'openstack/octavia-tempest-plugin'
bug_project = '910'
bug_tag = 'docs'
# TODO(mordred) We should extract this into a sphinx plugin
def run_apidoc(_):
cur_dir = os.path.abspath(os.path.dirname(__file__))
out_dir = os.path.join(cur_dir, '_build', 'modules')
module = os.path.join(cur_dir, '..', '..', 'octavia_tempest_plugin')
# Keep the order of arguments same as the sphinx-apidoc help, otherwise it
# would cause unexpected errors:
# sphinx-apidoc [options] -o <output_path> <module_path>
# [exclude_pattern, ...]
apidoc.main([
'--force',
'-o',
out_dir,
module,
])
def setup(app):
app.connect('builder-inited', run_apidoc)

+ 26
- 0
doc/source/configref.rst View File

@ -0,0 +1,26 @@
..
Copyright 2018 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.
Octavia Tempest Plugin Configuration Options
============================================
.. contents:: Table of Contents
:depth: 2
.. note:: Not all of these options are used by the Octavia tempest tests.
.. show-options::
tempest.config

+ 6
- 1
doc/source/index.rst View File

@ -14,11 +14,16 @@ Contents:
readme
installation
contributing
configref
Indices and tables
==================
.. toctree::
:hidden:
_build/modules/modules
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

+ 31
- 0
octavia_tempest_plugin/clients.py View File

@ -0,0 +1,31 @@
# Copyright 2017 GoDaddy
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from tempest import clients
from tempest import config
from octavia_tempest_plugin.services.load_balancer.v2 import (
loadbalancer_client)
CONF = config.CONF
SERVICE_TYPE = 'load-balancer'
class ManagerV2(clients.Manager):
def __init__(self, credentials):
super(ManagerV2, self).__init__(credentials)
self.loadbalancer_client = loadbalancer_client.LoadbalancerClient(
self.auth_provider, SERVICE_TYPE, CONF.identity.region)

octavia_tempest_plugin/tests/v2/api/__init__.py → octavia_tempest_plugin/common/__init__.py View File


+ 62
- 0
octavia_tempest_plugin/common/constants.py View File

@ -0,0 +1,62 @@
# Copyright 2018 Rackspace US Inc. All rights reserved.
#
# 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.
# API field names
ACTIVE_CONNECTIONS = 'active_connections'
ADMIN_STATE_UP = 'admin_state_up'
BYTES_IN = 'bytes_in'
BYTES_OUT = 'bytes_out'
CREATED_AT = 'created_at'
DESCRIPTION = 'description'
FLAVOR = 'flavor'
ID = 'id'
LISTENERS = 'listeners'
LOADBALANCER = 'loadbalancer'
NAME = 'name'
OPERATING_STATUS = 'operating_status'
POOLS = 'pools'
PROJECT_ID = 'project_id'
PROVIDER = 'provider'
PROVISIONING_STATUS = 'provisioning_status'
REQUEST_ERRORS = 'request_errors'
TOTAL_CONNECTIONS = 'total_connections'
UPDATED_AT = 'updated_at'
VIP_ADDRESS = 'vip_address'
VIP_NETWORK_ID = 'vip_network_id'
VIP_PORT_ID = 'vip_port_id'
VIP_SUBNET_ID = 'vip_subnet_id'
VIP_QOS_POLICY_ID = 'vip_qos_policy_id'
# API valid fields
SHOW_LOAD_BALANCER_RESPONSE_FIELDS = (
ADMIN_STATE_UP, CREATED_AT, DESCRIPTION, FLAVOR, ID, LISTENERS, NAME,
OPERATING_STATUS, POOLS, PROJECT_ID, PROVIDER, PROVISIONING_STATUS,
UPDATED_AT, VIP_ADDRESS, VIP_NETWORK_ID, VIP_PORT_ID, VIP_SUBNET_ID,
VIP_QOS_POLICY_ID)
# Other constants
ACTIVE = 'ACTIVE'
ADMIN_STATE_UP_TRUE = 'true'
ASC = 'asc'
DELETED = 'DELETED'
DESC = 'desc'
FIELDS = 'fields'
OFFLINE = 'OFFLINE'
ONLINE = 'ONLINE'
SORT = 'sort'
# RBAC options
ADVANCED = 'advanced'
OWNERADMIN = 'owner_or_admin'
NONE = 'none'

+ 146
- 0
octavia_tempest_plugin/config.py View File

@ -0,0 +1,146 @@
# Copyright 2016 Rackspace 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.
from oslo_config import cfg
from octavia_tempest_plugin.common import constants as const
service_available_group = cfg.OptGroup(name='service_available',
title='Available OpenStack Services')
ServiceAvailableGroup = [
cfg.BoolOpt('load_balancer',
default=True,
help="Whether or not the load-balancer service is expected "
"to be available."),
]
octavia_group = cfg.OptGroup(name='load_balancer',
title='load-balancer service options')
OctaviaGroup = [
# Tempest plugin common options
cfg.StrOpt("region",
default="",
help="The region name to use. If empty, the value "
"of identity.region is used instead. If no such region "
"is found in the service catalog, the first found one is "
"used."),
cfg.StrOpt('catalog_type',
default='load-balancer',
help='Catalog type of the Octavia service.'),
cfg.StrOpt('endpoint_type',
default='publicURL',
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the load-balancer service"),
cfg.IntOpt('build_interval',
default=5,
help='Time in seconds between build status checks for '
'non-load-balancer resources to build'),
cfg.IntOpt('build_timeout',
default=30,
help='Timeout in seconds to wait for non-load-balancer '
'resources to build'),
# load-balancer specific options
cfg.IntOpt('check_interval',
default=5,
help='Interval to check for status changes.'),
cfg.IntOpt('check_timeout',
default=60,
help='Timeout, in seconds, to wait for a status change.'),
cfg.BoolOpt('test_with_noop',
default=False,
help='Runs the tests assuming no-op drivers are being used. '
'Tests will assume no actual amphora are created.'),
cfg.IntOpt('lb_build_interval',
default=10,
help='Time in seconds between build status checks for a '
'load balancer.'),
cfg.IntOpt('lb_build_timeout',
default=900,
help='Timeout in seconds to wait for a '
'load balancer to build.'),
cfg.StrOpt('member_role',
default='load-balancer_member',
help='The load balancing member RBAC role.'),
cfg.StrOpt('admin_role',
default='load-balancer_admin',
help='The load balancing admin RBAC role.'),
cfg.IntOpt('scp_connection_timeout',
default=5,
help='Timeout in seconds to wait for a '
'scp connection to complete.'),
cfg.IntOpt('scp_connection_attempts',
default=20,
help='Retries for scp to attempt to connect.'),
cfg.StrOpt('provider',
default='octavia',
help='The provider driver to use for the tests.'),
cfg.StrOpt('RBAC_test_type', default=const.ADVANCED,
choices=[const.ADVANCED, const.OWNERADMIN, const.NONE],
help='Type of RBAC tests to run. "advanced" runs the octavia '
'default RBAC tests. "owner_or_admin" runs the legacy '
'owner or admin tests. "none" disables the RBAC tests.'),
# Networking
cfg.BoolOpt('test_with_ipv6',
default=True,
help='When true the IPv6 tests will be run.'),
cfg.BoolOpt('disable_boot_network', default=False,
help='True if your cloud does not allow creating networks or '
'specifying the boot network for instances.'),
cfg.BoolOpt('enable_security_groups', default=False,
help='When true, security groups will be created for the test '
'servers. When false, port security will be disabled on '
'the created networks.'),
cfg.StrOpt('test_network_override',
help='Overrides network creation and uses this network ID for '
'all tests (VIP, members, etc.). Required if '
'test_subnet_override is set.'),
cfg.StrOpt('test_subnet_override',
help='Overrides subnet creation and uses this subnet ID for '
'all IPv4 tests (VIP, members, etc.). Optional'),
cfg.StrOpt('test_ipv6_subnet_override',
help='Overrides subnet creation and uses this subnet ID for '
'all IPv6 tests (VIP, members, etc.). Optional and only '
'valid if test_network_override is set.'),
cfg.StrOpt('vip_subnet_cidr',
default='10.1.1.0/24',
help='CIDR format subnet to use for the vip subnet.'),
cfg.StrOpt('vip_ipv6_subnet_cidr',
default='fdde:1a92:7523:70a0::/64',
help='CIDR format subnet to use for the IPv6 vip subnet.'),
cfg.StrOpt('member_1_ipv4_subnet_cidr',
default='10.2.1.0/24',
help='CIDR format subnet to use for the member 1 subnet.'),
cfg.StrOpt('member_1_ipv6_subnet_cidr',
default='fd7b:f9f7:0fff:4eca::/64',
help='CIDR format subnet to use for the member 1 ipv6 subnet.'),
cfg.StrOpt('member_2_ipv4_subnet_cidr',
default='10.2.2.0/24',
help='CIDR format subnet to use for the member 2 subnet.'),
cfg.StrOpt('member_2_ipv6_subnet_cidr',
default='fd77:1457:4cf0:26a8::/64',
help='CIDR format subnet to use for the member 1 ipv6 subnet.'),
# Environment specific options
# These are used to accomidate clouds with specific limitations
cfg.IntOpt('random_server_name_length',
default=0,
help='If non-zero, generate a random name of the length '
'provided for each server, in the format "m[A-Z0-9]*". '),
cfg.StrOpt('availability_zone',
default=None,
help='Availability zone to use for creating servers.'),
]

+ 0
- 0
octavia_tempest_plugin/contrib/__init__.py View File


+ 0
- 0
octavia_tempest_plugin/contrib/httpd/__init__.py View File


+ 60
- 0
octavia_tempest_plugin/plugin.py View File

@ -0,0 +1,60 @@
# Copyright 2017 GoDaddy
#
# 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 os
from tempest import config
from tempest.test_discover import plugins
from octavia_tempest_plugin import config as project_config
class OctaviaTempestPlugin(plugins.TempestPlugin):
def load_tests(self):
base_path = os.path.split(os.path.dirname(
os.path.abspath(__file__)))[0]
test_dir = "octavia_tempest_plugin/tests"
full_test_dir = os.path.join(base_path, test_dir)
return full_test_dir, base_path
def register_opts(self, conf):
config.register_opt_group(conf, project_config.service_available_group,
project_config.ServiceAvailableGroup)
config.register_opt_group(conf, project_config.octavia_group,
project_config.OctaviaGroup)
def get_opt_lists(self):
return [
(project_config.service_available_group.name,
project_config.ServiceAvailableGroup),
(project_config.octavia_group.name,
project_config.OctaviaGroup),
]
def get_service_clients(self):
octavia_config = config.service_client_config(
project_config.octavia_group.name
)
params = {
'name': 'load-balancer_v2',
'service_version': 'load-balancer.v2',
'module_path': 'octavia_tempest_plugin.services.load_balancer.v2',
'client_names': ['LoadbalancerClient'],
}
params.update(octavia_config)
return [params]

+ 0
- 0
octavia_tempest_plugin/services/__init__.py View File


+ 17
- 0
octavia_tempest_plugin/services/load_balancer/__init__.py View File

@ -0,0 +1,17 @@
# Copyright 2018 Rackspace US Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from octavia_tempest_plugin.services.load_balancer import v2
__all__ = ['v2']

+ 18
- 0
octavia_tempest_plugin/services/load_balancer/v2/__init__.py View File

@ -0,0 +1,18 @@
# Copyright 2018 Rackspace US Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from octavia_tempest_plugin.services.load_balancer.v2.loadbalancer_client \
import LoadbalancerClient
__all__ = ['LoadbalancerClient']

+ 526
- 0
octavia_tempest_plugin/services/load_balancer/v2/loadbalancer_client.py View File

@ -0,0 +1,526 @@
# Copyright 2017 GoDaddy
#
# 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 json
from tempest import config
from tempest.lib.common import rest_client
CONF = config.CONF
class LoadbalancerClient(rest_client.RestClient):
_uri = '/v2.0/lbaas/loadbalancers'
def __init__(self, auth_provider, service, region, **kwargs):
super(LoadbalancerClient, self).__init__(auth_provider, service,
region, **kwargs)
self.timeout = CONF.load_balancer.lb_build_timeout
self.build_interval = CONF.load_balancer.lb_build_interval
self.resource_name = 'load balancer'
self.get_status = self.show_loadbalancer
def list_loadbalancers(self, query_params=None, return_object_only=True):
"""Get a list of load balancers.
:param query_params: The optional query parameters to append to the
request. Ex. fields=id&fields=name
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A list of load balancers object.
"""
if query_params:
request_uri = '{0}?{1}'.format(self._uri, query_params)
else:
request_uri = self._uri
response, body = self.get(request_uri)
self.expected_success(200, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['loadbalancers']
else:
return json.loads(body.decode('utf-8'))
def create_loadbalancer_dict(self, lb_dict, return_object_only=True):
"""Create a load balancer using a dictionary.
Example lb_dict::
lb_dict = {'loadbalancer': {
'vip_network_id': 'd0be73da-921a-4e03-9c49-f13f18f7e39f',
'name': 'TEMPEST_TEST_LB',
'description': 'LB for Tempest tests'}
}
:param lb_dict: A dictionary describing the load balancer.
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer object.
"""
response, body = self.post(self._uri, json.dumps(lb_dict))
self.expected_success(201, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['loadbalancer']
else:
return json.loads(body.decode('utf-8'))
def create_loadbalancer(self, admin_state_up=None, description=None,
flavor=None, listeners=None, name=None,
project_id=None, provider=None, vip_address=None,
vip_network_id=None, vip_port_id=None,
vip_qos_policy_id=None, vip_subnet_id=None,
return_object_only=True):
"""Create a load balancer.
:param admin_state_up: The administrative state of the resource, which
is up (true) or down (false).
:param description: A human-readable description for the resource.
:param flavor: The load balancer flavor ID.
:param listeners: A list of listner dictionaries.
:param name: Human-readable name of the resource.
:param project_id: The ID of the project owning this resource.
:param provider: Provider name for the load balancer.
:param vip_address: The IP address of the Virtual IP (VIP).
:param vip_network_id: The ID of the network for the Virtual IP (VIP).
:param vip_port_id: The ID of the Virtual IP (VIP) port.
:param vip_qos_policy_id: The ID of the QoS Policy which will apply to
the Virtual IP (VIP).
:param vip_subnet_id: The ID of the subnet for the Virtual IP (VIP).
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer object.
"""
method_args = locals()
lb_params = {}
for param, value in method_args.items():
if param not in ('self',
'return_object_only') and value is not None:
lb_params[param] = value
lb_dict = {'loadbalancer': lb_params}
return self.create_loadbalancer_dict(lb_dict, return_object_only)
def delete_loadbalancer(self, lb_id, cascade=False, ignore_errors=False):
"""Delete a load balancer.
:param lb_id: The load balancer ID to delete.
:param cascade: If true will delete all child objects of the
load balancer.
:param ignore_errors: True if errors should be ignored.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: None if ignore_errors is True, the response status code
if not.
"""
if cascade:
uri = '{0}/{1}?cascade=true'.format(self._uri, lb_id)
else:
uri = '{0}/{1}'.format(self._uri, lb_id)
if ignore_errors:
try:
response, body = self.delete(uri)
except ignore_errors:
return
else:
response, body = self.delete(uri)
self.expected_success(204, response.status)
return response.status
def failover_loadbalancer(self, lb_id):
"""Failover a load balancer.
:param lb_id: The load balancer ID to query.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: None
"""
uri = '{0}/{1}/failover'.format(self._uri, lb_id)
response, body = self.put(uri, '')
self.expected_success(202, response.status)
return
def show_loadbalancer(self, lb_id, query_params=None,
return_object_only=True):
"""Get load balancer details.
:param lb_id: The load balancer ID to query.
:param query_params: The optional query parameters to append to the
request. Ex. fields=id&fields=name
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer object.
"""
if query_params:
request_uri = '{0}/{1}?{2}'.format(self._uri, lb_id, query_params)
else:
request_uri = '{0}/{1}'.format(self._uri, lb_id)
response, body = self.get(request_uri)
self.expected_success(200, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['loadbalancer']
else:
return json.loads(body.decode('utf-8'))
def get_loadbalancer_stats(self, lb_id, query_params=None,
return_object_only=True):
"""Get load balancer statistics.
:param lb_id: The load balancer ID to query.
:param query_params: The optional query parameters to append to the
request. Ex. fields=id&fields=name
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer statistics object.
"""
if query_params:
request_uri = '{0}/{1}/stats?{2}'.format(self._uri, lb_id,
query_params)
else:
request_uri = '{0}/{1}/stats'.format(self._uri, lb_id)
response, body = self.get(request_uri)
self.expected_success(200, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['stats']
else:
return json.loads(body.decode('utf-8'))
def get_loadbalancer_status(self, lb_id, query_params=None,
return_object_only=True):
"""Get a load balancer status tree.
:param lb_id: The load balancer ID to query.
:param query_params: The optional query parameters to append to the
request. Ex. fields=id&fields=name
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer statuses object.
"""
if query_params:
request_uri = '{0}/{1}/status?{2}'.format(self._uri, lb_id,
query_params)
else:
request_uri = '{0}/{1}/status'.format(self._uri, lb_id)
response, body = self.get(request_uri)
self.expected_success(200, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['statuses']
else:
return json.loads(body.decode('utf-8'))
def update_loadbalancer_dict(self, lb_id, lb_dict,
return_object_only=True):
"""Update a load balancer using a dictionary.
Example lb_dict::
lb_dict = {'loadbalancer': {'name': 'TEMPEST_TEST_LB_UPDATED'} }
:param lb_id: The load balancer ID to update.
:param lb_dict: A dictionary of elements to update on the load
balancer.
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer object.
"""
uri = '{0}/{1}'.format(self._uri, lb_id)
response, body = self.put(uri, json.dumps(lb_dict))
self.expected_success(200, response.status)
if return_object_only:
return json.loads(body.decode('utf-8'))['loadbalancer']
else:
return json.loads(body.decode('utf-8'))
def update_loadbalancer(self, lb_id, admin_state_up=None, description=None,
name=None, vip_qos_policy_id=None,
return_object_only=True):
"""Update a load balancer.
:param lb_id: The load balancer ID to update.
:param admin_state_up: The administrative state of the resource, which
is up (true) or down (false).
:param description: A human-readable description for the resource.
:param name: Human-readable name of the resource.
:param vip_qos_policy_id: The ID of the QoS Policy which will apply to
the Virtual IP (VIP).
:param return_object_only: If True, the response returns the object
inside the root tag. False returns the full
response from the API.
:raises AssertionError: if the expected_code isn't a valid http success
response code
:raises BadRequest: If a 400 response code is received
:raises Conflict: If a 409 response code is received
:raises Forbidden: If a 403 response code is received
:raises Gone: If a 410 response code is received
:raises InvalidContentType: If a 415 response code is received
:raises InvalidHTTPResponseBody: The response body wasn't valid JSON
:raises InvalidHttpSuccessCode: if the read code isn't an expected
http success code
:raises NotFound: If a 404 response code is received
:raises NotImplemented: If a 501 response code is received
:raises OverLimit: If a 413 response code is received and over_limit is
not in the response body
:raises RateLimitExceeded: If a 413 response code is received and
over_limit is in the response body
:raises ServerFault: If a 500 response code is received
:raises Unauthorized: If a 401 response code is received
:raises UnexpectedContentType: If the content-type of the response
isn't an expect type
:raises UnexpectedResponseCode: If a response code above 400 is
received and it doesn't fall into any
of the handled checks
:raises UnprocessableEntity: If a 422 response code is received and
couldn't be parsed
:returns: A load balancer object.
"""
method_args = locals()
lb_params = {}
for param, value in method_args.items():
if param not in ('self', 'lb_id',
'return_object_only') and value is not None:
lb_params[param] = value
lb_dict = {'loadbalancer': lb_params}
return self.update_loadbalancer_dict(lb_id, lb_dict,
return_object_only)

+ 0
- 0
octavia_tempest_plugin/tests/api/__init__.py View File


+ 0
- 0
octavia_tempest_plugin/tests/api/v2/__init__.py View File


+ 834
- 0
octavia_tempest_plugin/tests/api/v2/test_load_balancer.py View File

@ -0,0 +1,834 @@
# Copyright 2017 GoDaddy
# Copyright 2017 Catalyst IT Ltd
# Copyright 2018 Rackspace US Inc. All rights reserved.
#
# 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 testtools
from uuid import UUID
from dateutil import parser
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions
from octavia_tempest_plugin.common import constants as const
from octavia_tempest_plugin.tests import test_base
from octavia_tempest_plugin.tests import waiters
CONF = config.CONF
class LoadBalancerAPITest(test_base.LoadBalancerBaseTest):
"""Test the load balancer object API."""
# Note: This test also covers basic load balancer show API
@decorators.idempotent_id('61c6343c-a5d2-4b9f-8c7d-34ea83f0596b')
def test_load_balancer_ipv4_create(self):
self._test_load_balancer_create(4)
# Note: This test also covers basic load balancer show API
@decorators.idempotent_id('fc9996de-4f55-4fc4-b8ef-a4b9170c7078')
@testtools.skipUnless(CONF.load_balancer.test_with_ipv6,
'IPv6 testing is disabled')
def test_load_balancer_ipv6_create(self):
self._test_load_balancer_create(6)
def _test_load_balancer_create(self, ip_version):
"""Tests load balancer create and basic show APIs.
* Tests that users without the load balancer member role cannot
* create load balancers.
* Create a fully populated load balancer.
* Show load balancer details.
* Validate the show reflects the requested values.
"""
lb_name = data_utils.rand_name("lb_member_lb1-create-"
"ipv{}".format(ip_version))
lb_description = data_utils.arbitrary_string(size=255)
lb_kwargs = {const.ADMIN_STATE_UP: True,
const.DESCRIPTION: lb_description,
const.PROVIDER: CONF.load_balancer.provider,
# TODO(johnsom) Fix test to use a real flavor
# flavor=lb_flavor,
# TODO(johnsom) Add QoS
# vip_qos_policy_id=lb_qos_policy_id)
const.NAME: lb_name}
self._setup_lb_network_kwargs(lb_kwargs, ip_version)
# Test that a user without the load balancer role cannot
# create a load balancer
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
self.assertRaises(
exceptions.Forbidden,
self.os_primary.loadbalancer_client.create_loadbalancer,
**lb_kwargs)
lb = self.mem_lb_client.create_loadbalancer(**lb_kwargs)
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID], const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
self.assertTrue(lb[const.ADMIN_STATE_UP])
parser.parse(lb[const.CREATED_AT])
parser.parse(lb[const.UPDATED_AT])
self.assertEqual(lb_description, lb[const.DESCRIPTION])
UUID(lb[const.ID])
self.assertEqual(lb_name, lb[const.NAME])
# Operating status is a measured status, so no-op will not go online
if CONF.load_balancer.test_with_noop:
self.assertEqual(const.OFFLINE, lb[const.OPERATING_STATUS])
else:
self.assertEqual(const.ONLINE, lb[const.OPERATING_STATUS])
self.assertEqual(self.os_roles_lb_member.credentials.project_id,
lb[const.PROJECT_ID])
self.assertEqual(CONF.load_balancer.provider, lb[const.PROVIDER])
self.assertEqual(self.lb_member_vip_net[const.ID],
lb[const.VIP_NETWORK_ID])
self.assertIsNotNone(lb[const.VIP_PORT_ID])
if lb_kwargs[const.VIP_SUBNET_ID]:
self.assertEqual(lb_kwargs[const.VIP_ADDRESS],
lb[const.VIP_ADDRESS])
self.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
lb[const.VIP_SUBNET_ID])
# Attempt to clean up so that one full test run doesn't start 10+
# amps before the cleanup phase fires
try:
self.mem_lb_client.delete_loadbalancer(lb[const.ID])
waiters.wait_for_deleted_status_or_not_found(
self.mem_lb_client.show_loadbalancer, lb[const.ID],
const.PROVISIONING_STATUS,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
except Exception:
pass
@decorators.idempotent_id('643ef031-c800-45f2-b229-3c8f8b37c829')
def test_load_balancer_delete(self):
"""Tests load balancer create and delete APIs.
* Creates a load balancer.
* Validates that other accounts cannot delete the load balancer
* Deletes the load balancer.
* Validates the load balancer is in the DELETED state.
"""
lb_name = data_utils.rand_name("lb_member_lb1-delete")
lb = self.mem_lb_client.create_loadbalancer(
name=lb_name, vip_network_id=self.lb_member_vip_net[const.ID])
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID], const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
# Test that a user without the load balancer role cannot
# delete this load balancer
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
self.assertRaises(
exceptions.Forbidden,
self.os_primary.loadbalancer_client.delete_loadbalancer,
lb[const.ID])
# Test that a different user, with the load balancer member role
# cannot delete this load balancer
if not CONF.load_balancer.RBAC_test_type == const.NONE:
member2_client = self.os_roles_lb_member2.loadbalancer_client
self.assertRaises(exceptions.Forbidden,
member2_client.delete_loadbalancer,
lb[const.ID])
self.mem_lb_client.delete_loadbalancer(lb[const.ID])
waiters.wait_for_deleted_status_or_not_found(
self.mem_lb_client.show_loadbalancer, lb[const.ID],
const.PROVISIONING_STATUS,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
@decorators.idempotent_id('643ef031-c800-45f2-b229-3c8f8b37c829')
def test_load_balancer_delete_cascade(self):
"""Tests load balancer create and cascade delete APIs.
* Creates a load balancer.
* Validates that other accounts cannot delete the load balancer
* Deletes the load balancer with the cascade parameter.
* Validates the load balancer is in the DELETED state.
"""
lb_name = data_utils.rand_name("lb_member_lb1-cascade_delete")
lb = self.mem_lb_client.create_loadbalancer(
name=lb_name, vip_network_id=self.lb_member_vip_net[const.ID])
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID], const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
# TODO(johnsom) Add other objects when we have clients for them
# Test that a user without the load balancer role cannot
# delete this load balancer
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
self.assertRaises(
exceptions.Forbidden,
self.os_primary.loadbalancer_client.delete_loadbalancer,
lb[const.ID], cascade=True)
# Test that a different user, with the load balancer member role
# cannot delete this load balancer
if not CONF.load_balancer.RBAC_test_type == const.NONE:
member2_client = self.os_roles_lb_member2.loadbalancer_client
self.assertRaises(exceptions.Forbidden,
member2_client.delete_loadbalancer,
lb[const.ID], cascade=True)
self.mem_lb_client.delete_loadbalancer(lb[const.ID], cascade=True)
waiters.wait_for_deleted_status_or_not_found(
self.mem_lb_client.show_loadbalancer, lb[const.ID],
const.PROVISIONING_STATUS,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
# Helper functions for test load balancer list
def _filter_lbs_by_id(self, lbs, ids):
return [lb for lb in lbs if lb['id'] not in ids]
def _filter_lbs_by_index(self, lbs, indexes):
return [lb for i, lb in enumerate(lbs) if i not in indexes]
@decorators.idempotent_id('6546ef3c-c0e2-46af-b892-f795f4d01119')
def test_load_balancer_list(self):
"""Tests load balancer list API and field filtering.
* Create three load balancers.
* Validates that other accounts cannot list the load balancers.
* List the load balancers using the default sort order.
* List the load balancers using descending sort order.
* List the load balancers using ascending sort order.
* List the load balancers returning one field at a time.
* List the load balancers returning two fields.
* List the load balancers filtering to one of the three.
* List the load balancers filtered, one field, and sorted.
"""
# Get a list of pre-existing LBs to filter from test data
pretest_lbs = self.mem_lb_client.list_loadbalancers()
# Store their IDs for easy access
pretest_lb_ids = [lb['id'] for lb in pretest_lbs]
lb_name = data_utils.rand_name("lb_member_lb2-list")
lb_description = 'B'
lb = self.mem_lb_client.create_loadbalancer(
admin_state_up=True,
description=lb_description,
# TODO(johnsom) Fix test to use a real flavor
# flavor=lb_flavor,
provider=CONF.load_balancer.provider,
name=lb_name,
# TODO(johnsom) Add QoS
# vip_qos_policy_id=lb_qos_policy_id)
vip_network_id=self.lb_member_vip_net[const.ID])
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb1 = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID],
const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
lb_name = data_utils.rand_name("lb_member_lb1-list")
lb_description = 'A'
lb = self.mem_lb_client.create_loadbalancer(
admin_state_up=True,
description=lb_description,
name=lb_name,
vip_network_id=self.lb_member_vip_net[const.ID])
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb2 = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID],
const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
lb_name = data_utils.rand_name("lb_member_lb3-list")
lb_description = 'C'
lb = self.mem_lb_client.create_loadbalancer(
admin_state_up=False,
description=lb_description,
name=lb_name,
vip_network_id=self.lb_member_vip_net[const.ID])
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb3 = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID],
const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
# Test that a different user cannot list load balancers
if not CONF.load_balancer.RBAC_test_type == const.NONE:
member2_client = self.os_roles_lb_member2.loadbalancer_client
primary = member2_client.list_loadbalancers()
self.assertEqual(0, len(primary))
# Test that a user without the lb member role cannot list load
# balancers
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
self.assertRaises(
exceptions.Forbidden,
self.os_primary.loadbalancer_client.list_loadbalancers)
# Check the default sort order, created_at
lbs = self.mem_lb_client.list_loadbalancers()
lbs = self._filter_lbs_by_id(lbs, pretest_lb_ids)
self.assertEqual(lb1[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
self.assertEqual(lb2[const.DESCRIPTION], lbs[1][const.DESCRIPTION])
self.assertEqual(lb3[const.DESCRIPTION], lbs[2][const.DESCRIPTION])
# Test sort descending by description
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{sort}={descr}:{desc}'.format(
sort=const.SORT, descr=const.DESCRIPTION, desc=const.DESC))
lbs = self._filter_lbs_by_id(lbs, pretest_lb_ids)
self.assertEqual(lb1[const.DESCRIPTION], lbs[1][const.DESCRIPTION])
self.assertEqual(lb2[const.DESCRIPTION], lbs[2][const.DESCRIPTION])
self.assertEqual(lb3[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
# Test sort ascending by description
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{sort}={descr}:{asc}'.format(sort=const.SORT,
descr=const.DESCRIPTION,
asc=const.ASC))
lbs = self._filter_lbs_by_id(lbs, pretest_lb_ids)
self.assertEqual(lb1[const.DESCRIPTION], lbs[1][const.DESCRIPTION])
self.assertEqual(lb2[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
self.assertEqual(lb3[const.DESCRIPTION], lbs[2][const.DESCRIPTION])
# Determine indexes of pretest LBs in default sort
pretest_lb_indexes = []
lbs = self.mem_lb_client.list_loadbalancers()
for i, lb in enumerate(lbs):
if lb['id'] in pretest_lb_ids:
pretest_lb_indexes.append(i)
# Test fields
for field in const.SHOW_LOAD_BALANCER_RESPONSE_FIELDS:
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{fields}={field}'.format(fields=const.FIELDS,
field=field))
lbs = self._filter_lbs_by_index(lbs, pretest_lb_indexes)
self.assertEqual(1, len(lbs[0]))
self.assertEqual(lb1[field], lbs[0][field])
self.assertEqual(lb2[field], lbs[1][field])
self.assertEqual(lb3[field], lbs[2][field])
# Test multiple fields at the same time
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{fields}={admin}&{fields}={created}'.format(
fields=const.FIELDS, admin=const.ADMIN_STATE_UP,
created=const.CREATED_AT))
lbs = self._filter_lbs_by_index(lbs, pretest_lb_indexes)
self.assertEqual(2, len(lbs[0]))
self.assertTrue(lbs[0][const.ADMIN_STATE_UP])
parser.parse(lbs[0][const.CREATED_AT])
self.assertTrue(lbs[1][const.ADMIN_STATE_UP])
parser.parse(lbs[1][const.CREATED_AT])
self.assertFalse(lbs[2][const.ADMIN_STATE_UP])
parser.parse(lbs[2][const.CREATED_AT])
# Test filtering
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{desc}={lb_desc}'.format(
desc=const.DESCRIPTION, lb_desc=lb2[const.DESCRIPTION]))
self.assertEqual(1, len(lbs))
self.assertEqual(lb2[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
# Test combined params
lbs = self.mem_lb_client.list_loadbalancers(
query_params='{admin}={true}&{fields}={descr}&{fields}={id}&'
'{sort}={descr}:{desc}'.format(
admin=const.ADMIN_STATE_UP,
true=const.ADMIN_STATE_UP_TRUE,
fields=const.FIELDS, descr=const.DESCRIPTION,
id=const.ID, sort=const.SORT, desc=const.DESC))
lbs = self._filter_lbs_by_id(lbs, pretest_lb_ids)
# Should get two load balancers
self.assertEqual(2, len(lbs))
# Load balancers should have two fields
self.assertEqual(2, len(lbs[0]))
# Should be in descending order
self.assertEqual(lb2[const.DESCRIPTION], lbs[1][const.DESCRIPTION])
self.assertEqual(lb1[const.DESCRIPTION], lbs[0][const.DESCRIPTION])
# Attempt to clean up so that one full test run doesn't start 10+
# amps before the cleanup phase fires
created_lb_ids = lb1[const.ID], lb2[const.ID], lb3[const.ID]
for lb_id in created_lb_ids:
try:
self.mem_lb_client.delete_loadbalancer(lb_id)
except Exception:
pass
for lb_id in created_lb_ids:
try:
waiters.wait_for_deleted_status_or_not_found(
self.mem_lb_client.show_loadbalancer, lb_id,
const.PROVISIONING_STATUS,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
except Exception:
pass
@decorators.idempotent_id('826ae612-8717-4c64-a8a7-cb9570a85870')
def test_load_balancer_show(self):
"""Tests load balancer show API.
* Create a fully populated load balancer.
* Show load balancer details.
* Validate the show reflects the requested values.
* Validates that other accounts cannot see the load balancer.
"""
lb_name = data_utils.rand_name("lb_member_lb1-show")
lb_description = data_utils.arbitrary_string(size=255)
lb_kwargs = {const.ADMIN_STATE_UP: False,
const.DESCRIPTION: lb_description,
# TODO(johnsom) Fix test to use a real flavor
# flavor=lb_flavor,
# TODO(johnsom) Add QoS
# vip_qos_policy_id=lb_qos_policy_id)
const.PROVIDER: CONF.load_balancer.provider,
const.NAME: lb_name}
self._setup_lb_network_kwargs(lb_kwargs, 4)
lb = self.mem_lb_client.create_loadbalancer(**lb_kwargs)
self.addClassResourceCleanup(
test_utils.call_and_ignore_notfound_exc,
self.mem_lb_client.delete_loadbalancer,
lb[const.ID])
lb = waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
lb[const.ID], const.PROVISIONING_STATUS,
const.ACTIVE,
CONF.load_balancer.lb_build_interval,
CONF.load_balancer.lb_build_timeout)
self.assertFalse(lb[const.ADMIN_STATE_UP])
parser.parse(lb[const.CREATED_AT])
parser.parse(lb[const.UPDATED_AT])
self.assertEqual(lb_description, lb[const.DESCRIPTION])
UUID(lb[const.ID])
self.assertEqual(lb_name, lb[const.NAME])
self.assertEqual(const.OFFLINE, lb[const.OPERATING_STATUS])
self.assertEqual(self.os_roles_lb_member.credentials.project_id,
lb[const.PROJECT_ID])
self.assertEqual(CONF.load_balancer.provider, lb[const.PROVIDER])
self.assertEqual(self.lb_member_vip_net[const.ID],
lb[const.VIP_NETWORK_ID])
self.assertIsNotNone(lb[const.VIP_PORT_ID])
if lb_kwargs[const.VIP_SUBNET_ID]:
self.assertEqual(lb_kwargs[const.VIP_ADDRESS],
lb[const.VIP_ADDRESS])
self