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
This commit is contained in:
parent
0c90ef6972
commit
986e3f543c
6
.gitignore
vendored
6
.gitignore
vendored
@ -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
doc/requirements.txt
Normal file
9
doc/requirements.txt
Normal 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
|
@ -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
doc/source/configref.rst
Normal file
26
doc/source/configref.rst
Normal 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
|
@ -14,11 +14,16 @@ Contents:
|
||||
readme
|
||||
installation
|
||||
contributing
|
||||
configref
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
_build/modules/modules
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
|
31
octavia_tempest_plugin/clients.py
Normal file
31
octavia_tempest_plugin/clients.py
Normal 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)
|
62
octavia_tempest_plugin/common/constants.py
Normal file
62
octavia_tempest_plugin/common/constants.py
Normal 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
octavia_tempest_plugin/config.py
Normal file
146
octavia_tempest_plugin/config.py
Normal 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
octavia_tempest_plugin/contrib/__init__.py
Normal file
0
octavia_tempest_plugin/contrib/__init__.py
Normal file
0
octavia_tempest_plugin/contrib/httpd/__init__.py
Normal file
0
octavia_tempest_plugin/contrib/httpd/__init__.py
Normal file
60
octavia_tempest_plugin/plugin.py
Normal file
60
octavia_tempest_plugin/plugin.py
Normal 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
octavia_tempest_plugin/services/__init__.py
Normal file
0
octavia_tempest_plugin/services/__init__.py
Normal file
17
octavia_tempest_plugin/services/load_balancer/__init__.py
Normal file
17
octavia_tempest_plugin/services/load_balancer/__init__.py
Normal 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
octavia_tempest_plugin/services/load_balancer/v2/__init__.py
Normal file
18
octavia_tempest_plugin/services/load_balancer/v2/__init__.py
Normal 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']
|
@ -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
octavia_tempest_plugin/tests/api/__init__.py
Normal file
0
octavia_tempest_plugin/tests/api/__init__.py
Normal file
0
octavia_tempest_plugin/tests/api/v2/__init__.py
Normal file
0
octavia_tempest_plugin/tests/api/v2/__init__.py
Normal file
834
octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
Normal file
834
octavia_tempest_plugin/tests/api/v2/test_load_balancer.py
Normal 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.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
|
||||
lb[const.VIP_SUBNET_ID])
|
||||
|
||||
# Test that a user with lb_admin role can see the load balanacer
|
||||
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
|
||||
lb_client = self.os_roles_lb_admin.loadbalancer_client
|
||||
lb_adm = lb_client.show_loadbalancer(lb[const.ID])
|
||||
self.assertEqual(lb_name, lb_adm[const.NAME])
|
||||
|
||||
# Test that a user with cloud admin role can see the load balanacer
|
||||
if not CONF.load_balancer.RBAC_test_type == const.NONE:
|
||||
adm = self.os_admin.loadbalancer_client.show_loadbalancer(
|
||||
lb[const.ID])
|
||||
self.assertEqual(lb_name, adm[const.NAME])
|
||||
|
||||
# Test that a different user, with load balancer member role, cannot
|
||||
# see 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.show_loadbalancer,
|
||||
lb[const.ID])
|
||||
|
||||
# Test that a user, without the load balancer member role, cannot
|
||||
# show load balancers
|
||||
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
|
||||
self.assertRaises(
|
||||
exceptions.Forbidden,
|
||||
self.os_primary.loadbalancer_client.show_loadbalancer,
|
||||
lb[const.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('b75a4d15-49d2-4149-a745-635eed1aacc3')
|
||||
def test_load_balancer_update(self):
|
||||
"""Tests load balancer show API and field filtering.
|
||||
|
||||
* Create a fully populated load balancer.
|
||||
* Show load balancer details.
|
||||
* Validate the show reflects the initial values.
|
||||
* Validates that other accounts cannot update the load balancer.
|
||||
* Update the load balancer details.
|
||||
* Show load balancer details.
|
||||
* Validate the show reflects the initial values.
|
||||
"""
|
||||
lb_name = data_utils.rand_name("lb_member_lb1-update")
|
||||
lb_description = data_utils.arbitrary_string(size=255)
|
||||
|
||||
lb_kwargs = {const.ADMIN_STATE_UP: False,
|
||||
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, 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.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
|
||||
lb[const.VIP_SUBNET_ID])
|
||||
|
||||
new_name = data_utils.rand_name("lb_member_lb1-update")
|
||||
new_description = data_utils.arbitrary_string(size=255,
|
||||
base_text='new')
|
||||
|
||||
# Test that a user, without the load balancer member role, cannot
|
||||
# use this command
|
||||
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
|
||||
self.assertRaises(
|
||||
exceptions.Forbidden,
|
||||
self.os_primary.loadbalancer_client.update_loadbalancer,
|
||||
lb[const.ID], admin_state_up=True)
|
||||
|
||||
# Assert we didn't go into PENDING_*
|
||||
lb_check = self.mem_lb_client.show_loadbalancer(lb[const.ID])
|
||||
self.assertEqual(const.ACTIVE, lb_check[const.PROVISIONING_STATUS])
|
||||
self.assertFalse(lb_check[const.ADMIN_STATE_UP])
|
||||
|
||||
# Test that a user, without the load balancer member role, cannot
|
||||
# update 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.update_loadbalancer,
|
||||
lb[const.ID], admin_state_up=True)
|
||||
|
||||
# Assert we didn't go into PENDING_*
|
||||
lb_check = self.mem_lb_client.show_loadbalancer(lb[const.ID])
|
||||
self.assertEqual(const.ACTIVE, lb_check[const.PROVISIONING_STATUS])
|
||||
self.assertFalse(lb_check[const.ADMIN_STATE_UP])
|
||||
|
||||
lb = self.mem_lb_client.update_loadbalancer(
|
||||
lb[const.ID],
|
||||
admin_state_up=True,
|
||||
description=new_description,
|
||||
# TODO(johnsom) Add QoS
|
||||
# vip_qos_policy_id=lb_qos_policy_id)
|
||||
name=new_name)
|
||||
|
||||
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])
|
||||
self.assertEqual(new_description, lb[const.DESCRIPTION])
|
||||
self.assertEqual(new_name, lb[const.NAME])
|
||||
# TODO(johnsom) Add QoS
|
||||
|
||||
# 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('105afcba-4dd6-46d6-8fa4-bd7330aa1259')
|
||||
def test_load_balancer_show_stats(self):
|
||||
"""Tests load balancer show statistics API.
|
||||
|
||||
* Create a load balancer.
|
||||
* Validates that other accounts cannot see the stats for the
|
||||
* load balancer.
|
||||
* Show load balancer statistics.
|
||||
* Validate the show reflects the expected values.
|
||||
"""
|
||||
lb_name = data_utils.rand_name("lb_member_lb1-show_stats")
|
||||
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 member role, cannot
|
||||
# use this command
|
||||
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
|
||||
self.assertRaises(
|
||||
exceptions.Forbidden,
|
||||
self.os_primary.loadbalancer_client.get_loadbalancer_stats,
|
||||
lb[const.ID])
|
||||
|
||||
# Test that a different user, with the load balancer role, cannot see
|
||||
# the load balancer stats
|
||||
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.get_loadbalancer_stats,
|
||||
lb[const.ID])
|
||||
|
||||
stats = self.mem_lb_client.get_loadbalancer_stats(lb[const.ID])
|
||||
|
||||
self.assertEqual(5, len(stats))
|
||||
self.assertEqual(0, stats[const.ACTIVE_CONNECTIONS])
|
||||
self.assertEqual(0, stats[const.BYTES_IN])
|
||||
self.assertEqual(0, stats[const.BYTES_OUT])
|
||||
self.assertEqual(0, stats[const.REQUEST_ERRORS])
|
||||
self.assertEqual(0, stats[const.TOTAL_CONNECTIONS])
|
||||
|
||||
# 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('60acc1b0-fa46-41f8-b526-c81ae2f42c30')
|
||||
def test_load_balancer_show_status(self):
|
||||
"""Tests load balancer show status tree API.
|
||||
|
||||
* Create a load balancer.
|
||||
* Validates that other accounts cannot see the status for the
|
||||
* load balancer.
|
||||
* Show load balancer status tree.
|
||||
* Validate the show reflects the expected values.
|
||||
"""
|
||||
lb_name = data_utils.rand_name("lb_member_lb1-status")
|
||||
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 member role, cannot
|
||||
# use this method
|
||||
if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
|
||||
self.assertRaises(
|
||||
exceptions.Forbidden,
|
||||
self.os_primary.loadbalancer_client.get_loadbalancer_status,
|
||||
lb[const.ID])
|
||||
|
||||
# Test that a different user, with load balancer role, cannot see
|
||||
# the load balancer status
|
||||
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.get_loadbalancer_status,
|
||||
lb[const.ID])
|
||||
|
||||
status = self.mem_lb_client.get_loadbalancer_status(lb[const.ID])
|
||||
|
||||
self.assertEqual(1, len(status))
|
||||
lb_status = status[const.LOADBALANCER]
|
||||
self.assertEqual(5, len(lb_status))
|
||||
self.assertEqual(lb[const.ID], lb_status[const.ID])
|
||||
self.assertEqual([], lb_status[const.LISTENERS])
|
||||
self.assertEqual(lb_name, lb_status[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_status[const.OPERATING_STATUS])
|
||||
else:
|
||||
self.assertEqual(const.ONLINE, lb_status[const.OPERATING_STATUS])
|
||||
self.assertEqual(const.ACTIVE, lb_status[const.PROVISIONING_STATUS])
|
||||
|
||||
# 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('fc2e07a6-9776-4559-90c9-141170d4c397')
|
||||
def test_load_balancer_failover(self):
|
||||
"""Tests load balancer failover API.
|
||||
|
||||
* Create a load balancer.
|
||||
* Validates that other accounts cannot failover the load balancer
|
||||
* Wait for the load balancer to go ACTIVE.
|
||||
* Failover the load balancer.
|
||||
* Wait for the load balancer to go ACTIVE.
|
||||
"""
|
||||
lb_name = data_utils.rand_name("lb_member_lb1-failover")
|
||||
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 RBAC not authorized for non-admin role
|
||||
if not CONF.load_balancer.RBAC_test_type == const.NONE:
|
||||
self.assertRaises(exceptions.Forbidden,
|
||||
self.mem_lb_client.failover_loadbalancer,
|
||||
lb[const.ID])
|
||||
|
||||
# Assert we didn't go into PENDING_*
|
||||
lb = self.mem_lb_client.show_loadbalancer(lb[const.ID])
|
||||
self.assertEqual(const.ACTIVE, lb[const.PROVISIONING_STATUS])
|
||||
|
||||
self.os_roles_lb_admin.loadbalancer_client.failover_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) Assert the amphora ID has changed when amp client
|
||||
# is available.
|
||||
|
||||
# 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
|
@ -1,23 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2010-2011 OpenStack Foundation
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# 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 oslotest import base
|
||||
|
||||
|
||||
class TestCase(base.BaseTestCase):
|
||||
|
||||
"""Test case base class for all unit tests."""
|
0
octavia_tempest_plugin/tests/scenario/__init__.py
Normal file
0
octavia_tempest_plugin/tests/scenario/__init__.py
Normal file
120
octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
Normal file
120
octavia_tempest_plugin/tests/scenario/v2/test_load_balancer.py
Normal file
@ -0,0 +1,120 @@
|
||||
# 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 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 LoadBalancerScenarioTest(test_base.LoadBalancerBaseTest):
|
||||
|
||||
@decorators.idempotent_id('a5e2e120-4f7e-4c8b-8aac-cf09cb56711c')
|
||||
def test_load_balancer_ipv4_CRUD(self):
|
||||
self._test_load_balancer_CRUD(4)
|
||||
|
||||
@decorators.idempotent_id('86ffecc4-dce8-46f9-936e-8a4c6bcf3959')
|
||||
@testtools.skipUnless(CONF.load_balancer.test_with_ipv6,
|
||||
'IPv6 testing is disabled')
|
||||
def test_load_balancer_ipv6_CRUD(self):
|
||||
self._test_load_balancer_CRUD(6)
|
||||
|
||||
def _test_load_balancer_CRUD(self, ip_version):
|
||||
"""Tests load balancer create, read, update, delete
|
||||
|
||||
* Create a fully populated load balancer.
|
||||
* Show load balancer details.
|
||||
* Update the load balancer.
|
||||
* Delete the load balancer.
|
||||
"""
|
||||
lb_name = data_utils.rand_name("lb_member_lb1-CRUD")
|
||||
lb_description = data_utils.arbitrary_string(size=255)
|
||||
|
||||
lb_kwargs = {const.ADMIN_STATE_UP: False,
|
||||
const.DESCRIPTION: lb_description,
|
||||
const.PROVIDER: CONF.load_balancer.provider,
|
||||
const.NAME: lb_name}
|
||||
|
||||
self._setup_lb_network_kwargs(lb_kwargs, ip_version)
|
||||
|
||||
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.assertEqual(lb_kwargs[const.VIP_SUBNET_ID],
|
||||
lb[const.VIP_SUBNET_ID])
|
||||
|
||||
# Load balancer update
|
||||
new_name = data_utils.rand_name("lb_member_lb1-update")
|
||||
new_description = data_utils.arbitrary_string(size=255,
|
||||
base_text='new')
|
||||
lb = self.mem_lb_client.update_loadbalancer(
|
||||
lb[const.ID],
|
||||
admin_state_up=True,
|
||||
description=new_description,
|
||||
name=new_name)
|
||||
|
||||
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])
|
||||
self.assertEqual(new_description, lb[const.DESCRIPTION])
|
||||
self.assertEqual(new_name, lb[const.NAME])
|
||||
|
||||
# Load balancer delete
|
||||
self.mem_lb_client.delete_loadbalancer(lb[const.ID], cascade=True)
|
||||
|
||||
waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
|
||||
lb[const.ID], const.PROVISIONING_STATUS,
|
||||
const.DELETED,
|
||||
CONF.load_balancer.lb_build_interval,
|
||||
CONF.load_balancer.lb_build_timeout)
|
683
octavia_tempest_plugin/tests/test_base.py
Normal file
683
octavia_tempest_plugin/tests/test_base.py
Normal file
@ -0,0 +1,683 @@
|
||||
# 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 ipaddress
|
||||
import pkg_resources
|
||||
import random
|
||||
import shlex
|
||||
import six
|
||||
import string
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import uuidutils
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import data_utils
|
||||
from tempest.lib.common.utils.linux import remote_client
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import exceptions
|
||||
from tempest import test
|
||||
|
||||
from octavia_tempest_plugin import clients
|
||||
from octavia_tempest_plugin.common import constants as const
|
||||
from octavia_tempest_plugin.tests import validators
|
||||
from octavia_tempest_plugin.tests import waiters
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadBalancerBaseTest(test.BaseTestCase):
|
||||
"""Base class for load balancer tests."""
|
||||
|
||||
# Setup cls.os_roles_lb_member. cls.os_primary, cls.os_roles_lb_member,
|
||||
# and cls.os_roles_lb_admin credentials.
|
||||
credentials = ['admin', 'primary',
|
||||
['lb_member', CONF.load_balancer.member_role],
|
||||
['lb_member2', CONF.load_balancer.member_role],
|
||||
['lb_admin', CONF.load_balancer.admin_role]]
|
||||
|
||||
client_manager = clients.ManagerV2
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
"""Check if we should skip all of the children tests."""
|
||||
super(LoadBalancerBaseTest, cls).skip_checks()
|
||||
|
||||
service_list = {
|
||||
'load_balancer': CONF.service_available.load_balancer,
|
||||
}
|
||||
|
||||
live_service_list = {
|
||||
'compute': CONF.service_available.nova,
|
||||
'image': CONF.service_available.glance,
|
||||
'neutron': CONF.service_available.neutron
|
||||
}
|
||||
|
||||
if not CONF.load_balancer.test_with_noop:
|
||||
service_list.update(live_service_list)
|
||||
|
||||
for service, available in service_list.items():
|
||||
if not available:
|
||||
skip_msg = ("{0} skipped as {1} serivce is not "
|
||||
"available.".format(cls.__name__, service))
|
||||
raise cls.skipException(skip_msg)
|
||||
|
||||
# We must be able to reach our VIP and instances
|
||||
if not (CONF.network.project_networks_reachable
|
||||
or CONF.network.public_network_id):
|
||||
msg = ('Either project_networks_reachable must be "true", or '
|
||||
'public_network_id must be defined.')
|
||||
raise cls.skipException(msg)
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
"""Setup test credentials and network resources."""
|
||||
# Do not auto create network resources
|
||||
cls.set_network_resources()
|
||||
super(LoadBalancerBaseTest, cls).setup_credentials()
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
"""Setup client aliases."""
|
||||
super(LoadBalancerBaseTest, cls).setup_clients()
|
||||
cls.lb_mem_float_ip_client = cls.os_roles_lb_member.floating_ips_client
|
||||
cls.lb_mem_keypairs_client = cls.os_roles_lb_member.keypairs_client
|
||||
cls.lb_mem_net_client = cls.os_roles_lb_member.networks_client
|
||||
cls.lb_mem_ports_client = cls.os_roles_lb_member.ports_client
|
||||
cls.lb_mem_routers_client = cls.os_roles_lb_member.routers_client
|
||||
cls.lb_mem_SG_client = cls.os_roles_lb_member.security_groups_client
|
||||
cls.lb_mem_SGr_client = (
|
||||
cls.os_roles_lb_member.security_group_rules_client)
|
||||
cls.lb_mem_servers_client = cls.os_roles_lb_member.servers_client
|
||||
cls.lb_mem_subnet_client = cls.os_roles_lb_member.subnets_client
|
||||
cls.mem_lb_client = cls.os_roles_lb_member.loadbalancer_client
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
"""Setup resources needed by the tests."""
|
||||
super(LoadBalancerBaseTest, cls).resource_setup()
|
||||
|
||||
conf_lb = CONF.load_balancer
|
||||
|
||||
if conf_lb.test_subnet_override and not conf_lb.test_network_override:
|
||||
raise exceptions.InvalidConfiguration(
|
||||
"Configuration value test_network_override must be "
|
||||
"specified if test_subnet_override is used.")
|
||||
|
||||
show_subnet = cls.lb_mem_subnet_client.show_subnet
|
||||
if CONF.load_balancer.test_with_noop:
|
||||
cls.lb_member_vip_net = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_vip_subnet = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_1_net = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_1_subnet = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_2_net = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_2_subnet = {'id': uuidutils.generate_uuid()}
|
||||
if CONF.load_balancer.test_with_ipv6:
|
||||
cls.lb_member_vip_ipv6_subnet = {'id':
|
||||
uuidutils.generate_uuid()}
|
||||
cls.lb_member_1_ipv6_subnet = {'id': uuidutils.generate_uuid()}
|
||||
cls.lb_member_2_ipv6_subnet = {'id': uuidutils.generate_uuid()}
|
||||
return
|
||||
elif CONF.load_balancer.test_network_override:
|
||||
if conf_lb.test_subnet_override:
|
||||
override_subnet = show_subnet(conf_lb.test_subnet_override)
|
||||
else:
|
||||
override_subnet = None
|
||||
|
||||
show_net = cls.lb_mem_net_client.show_network
|
||||
override_network = show_net(conf_lb.test_network_override)
|
||||
override_network = override_network.get('network')
|
||||
|
||||
cls.lb_member_vip_net = override_network
|
||||
cls.lb_member_vip_subnet = override_subnet
|
||||
cls.lb_member_1_net = override_network
|
||||
cls.lb_member_1_subnet = override_subnet
|
||||
cls.lb_member_2_net = override_network
|
||||
cls.lb_member_2_subnet = override_subnet
|
||||
|
||||
if (CONF.load_balancer.test_with_ipv6 and
|
||||
conf_lb.test_IPv6_subnet_override):
|
||||
override_ipv6_subnet = show_subnet(
|
||||
conf_lb.test_IPv6_subnet_override)
|
||||
cls.lb_member_vip_ipv6_subnet = override_ipv6_subnet
|
||||
cls.lb_member_1_ipv6_subnet = override_ipv6_subnet
|
||||
cls.lb_member_2_ipv6_subnet = override_ipv6_subnet
|
||||
else:
|
||||
cls.lb_member_vip_ipv6_subnet = None
|
||||
cls.lb_member_1_ipv6_subnet = None
|
||||
cls.lb_member_2_ipv6_subnet = None
|
||||
else:
|
||||
cls._create_networks()
|
||||
|
||||
LOG.debug('Octavia Setup: lb_member_vip_net = {}'.format(
|
||||
cls.lb_member_vip_net[const.ID]))
|
||||
if cls.lb_member_vip_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_vip_subnet = {}'.format(
|
||||
cls.lb_member_vip_subnet[const.ID]))
|
||||
LOG.debug('Octavia Setup: lb_member_1_net = {}'.format(
|
||||
cls.lb_member_1_net[const.ID]))
|
||||
if cls.lb_member_1_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_1_subnet = {}'.format(
|
||||
cls.lb_member_1_subnet[const.ID]))
|
||||
LOG.debug('Octavia Setup: lb_member_2_net = {}'.format(
|
||||
cls.lb_member_2_net[const.ID]))
|
||||
if cls.lb_member_2_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_2_subnet = {}'.format(
|
||||
cls.lb_member_2_subnet[const.ID]))
|
||||
if cls.lb_member_vip_ipv6_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_vip_ipv6_subnet = {}'.format(
|
||||
cls.lb_member_vip_ipv6_subnet[const.ID]))
|
||||
if cls.lb_member_1_ipv6_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_1_ipv6_subnet = {}'.format(
|
||||
cls.lb_member_1_ipv6_subnet[const.ID]))
|
||||
if cls.lb_member_2_ipv6_subnet:
|
||||
LOG.debug('Octavia Setup: lb_member_2_ipv6_subnet = {}'.format(
|
||||
cls.lb_member_2_ipv6_subnet[const.ID]))
|
||||
|
||||
# If validation is disabled in this cloud, we won't be able to
|
||||
# start the webservers, so don't even boot them.
|
||||
if not CONF.validation.run_validation:
|
||||
return
|
||||
|
||||
# Create a keypair for the webservers
|
||||
keypair_name = data_utils.rand_name('lb_member_keypair')
|
||||
result = cls.lb_mem_keypairs_client.create_keypair(
|
||||
name=keypair_name)
|
||||
cls.lb_member_keypair = result['keypair']
|
||||
LOG.info('lb_member_keypair: {}'.format(cls.lb_member_keypair))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_keypairs_client.delete_keypair,
|
||||
cls.lb_mem_keypairs_client.show_keypair,
|
||||
keypair_name)
|
||||
|
||||
if (CONF.load_balancer.enable_security_groups and
|
||||
CONF.network_feature_enabled.port_security):
|
||||
# Set up the security group for the webservers
|
||||
SG_name = data_utils.rand_name('lb_member_SG')
|
||||
cls.lb_member_sec_group = (
|
||||
cls.lb_mem_SG_client.create_security_group(
|
||||
name=SG_name)['security_group'])
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_SG_client.delete_security_group,
|
||||
cls.lb_mem_SG_client.show_security_group,
|
||||
cls.lb_member_sec_group['id'])
|
||||
|
||||
# Create a security group rule to allow 80-81 (test webservers)
|
||||
SGr = cls.lb_mem_SGr_client.create_security_group_rule(
|
||||
direction='ingress',
|
||||
security_group_id=cls.lb_member_sec_group['id'],
|
||||
protocol='tcp',
|
||||
ethertype='IPv4',
|
||||
port_range_min=80,
|
||||
port_range_max=81)['security_group_rule']
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_SGr_client.delete_security_group_rule,
|
||||
cls.lb_mem_SGr_client.show_security_group_rule,
|
||||
SGr['id'])
|
||||
# Create a security group rule to allow 22 (ssh)
|
||||
SGr = cls.lb_mem_SGr_client.create_security_group_rule(
|
||||
direction='ingress',
|
||||
security_group_id=cls.lb_member_sec_group['id'],
|
||||
protocol='tcp',
|
||||
ethertype='IPv4',
|
||||
port_range_min=22,
|
||||
port_range_max=22)['security_group_rule']
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_SGr_client.delete_security_group_rule,
|
||||
cls.lb_mem_SGr_client.show_security_group_rule,
|
||||
SGr['id'])
|
||||
if CONF.load_balancer.test_with_ipv6:
|
||||
# Create a security group rule to allow 80-81 (test webservers)
|
||||
SGr = cls.lb_mem_SGr_client.create_security_group_rule(
|
||||
direction='ingress',
|
||||
security_group_id=cls.lb_member_sec_group['id'],
|
||||
protocol='tcp',
|
||||
ethertype='IPv6',
|
||||
port_range_min=80,
|
||||
port_range_max=81)['security_group_rule']
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_SGr_client.delete_security_group_rule,
|
||||
cls.lb_mem_SGr_client.show_security_group_rule,
|
||||
SGr['id'])
|
||||
# Create a security group rule to allow 22 (ssh)
|
||||
SGr = cls.lb_mem_SGr_client.create_security_group_rule(
|
||||
direction='ingress',
|
||||
security_group_id=cls.lb_member_sec_group['id'],
|
||||
protocol='tcp',
|
||||
ethertype='IPv6',
|
||||
port_range_min=22,
|
||||
port_range_max=22)['security_group_rule']
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_SGr_client.delete_security_group_rule,
|
||||
cls.lb_mem_SGr_client.show_security_group_rule,
|
||||
SGr['id'])
|
||||
|
||||
LOG.info('lb_member_sec_group: {}'.format(cls.lb_member_sec_group))
|
||||
|
||||
# Create webserver 1 instance
|
||||
server_details = cls._create_webserver('lb_member_webserver1',
|
||||
cls.lb_member_1_net)
|
||||
|
||||
cls.lb_member_webserver1 = server_details['server']
|
||||
cls.webserver1_ip = server_details.get('ipv4_address')
|
||||
cls.webserver1_ipv6 = server_details.get('ipv6_address')
|
||||
cls.webserver1_public_ip = server_details['public_ipv4_address']
|
||||
|
||||
LOG.debug('Octavia Setup: lb_member_webserver1 = {}'.format(
|
||||
cls.lb_member_webserver1[const.ID]))
|
||||
LOG.debug('Octavia Setup: webserver1_ip = {}'.format(
|
||||
cls.webserver1_ip))
|
||||
LOG.debug('Octavia Setup: webserver1_ipv6 = {}'.format(
|
||||
cls.webserver1_ipv6))
|
||||
LOG.debug('Octavia Setup: webserver1_public_ip = {}'.format(
|
||||
cls.webserver1_public_ip))
|
||||
|
||||
cls._install_start_webserver(cls.webserver1_public_ip,
|
||||
cls.lb_member_keypair['private_key'], 1)
|
||||
|
||||
# Validate webserver 1
|
||||
cls._validate_webserver(cls.webserver1_public_ip, 1)
|
||||
|
||||
# Create webserver 2 instance
|
||||
server_details = cls._create_webserver('lb_member_webserver2',
|
||||
cls.lb_member_2_net)
|
||||
|
||||
cls.lb_member_webserver2 = server_details['server']
|
||||
cls.webserver2_ip = server_details.get('ipv4_address')
|
||||
cls.webserver2_ipv6 = server_details.get('ipv6_address')
|
||||
cls.webserver2_public_ip = server_details['public_ipv4_address']
|
||||
|
||||
LOG.debug('Octavia Setup: lb_member_webserver2 = {}'.format(
|
||||
cls.lb_member_webserver2[const.ID]))
|
||||
LOG.debug('Octavia Setup: webserver2_ip = {}'.format(
|
||||
cls.webserver2_ip))
|
||||
LOG.debug('Octavia Setup: webserver2_ipv6 = {}'.format(
|
||||
cls.webserver2_ipv6))
|
||||
LOG.debug('Octavia Setup: webserver2_public_ip = {}'.format(
|
||||
cls.webserver2_public_ip))
|
||||
|
||||
cls._install_start_webserver(cls.webserver2_public_ip,
|
||||
cls.lb_member_keypair['private_key'], 5)
|
||||
|
||||
# Validate webserver 2
|
||||
cls._validate_webserver(cls.webserver2_public_ip, 5)
|
||||
|
||||
@classmethod
|
||||
def _install_start_webserver(cls, ip_address, ssh_key, start_id):
|
||||
local_file = pkg_resources.resource_filename(
|
||||
'octavia_tempest_plugin.contrib.httpd', 'httpd.bin')
|
||||
dest_file = '/dev/shm/httpd.bin'
|
||||
|
||||
linux_client = remote_client.RemoteClient(
|
||||
ip_address, CONF.validation.image_ssh_user, pkey=ssh_key)
|
||||
linux_client.validate_authentication()
|
||||
|
||||
with tempfile.NamedTemporaryFile() as key:
|
||||
key.write(ssh_key.encode('utf-8'))
|
||||
key.flush()
|
||||
cmd = ("scp -v -o UserKnownHostsFile=/dev/null "
|
||||
"-o StrictHostKeyChecking=no "
|
||||
"-o ConnectTimeout={0} -o ConnectionAttempts={1} "
|
||||
"-i {2} {3} {4}@{5}:{6}").format(
|
||||
CONF.load_balancer.scp_connection_timeout,
|
||||
CONF.load_balancer.scp_connection_attempts,
|
||||
key.name, local_file, CONF.validation.image_ssh_user,
|
||||
ip_address, dest_file)
|
||||
args = shlex.split(cmd)
|
||||
subprocess_args = {'stdout': subprocess.PIPE,
|
||||
'stderr': subprocess.STDOUT,
|
||||
'cwd': None}
|
||||
proc = subprocess.Popen(args, **subprocess_args)
|
||||
stdout, stderr = proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
raise exceptions.CommandFailed(proc.returncode, cmd,
|
||||
stdout, stderr)
|
||||
linux_client.exec_command('sudo screen -d -m {0} -port 80 '
|
||||
'-id {1}'.format(dest_file, start_id))
|
||||
linux_client.exec_command('sudo screen -d -m {0} -port 81 '
|
||||
'-id {1}'.format(dest_file, start_id + 1))
|
||||
|
||||
@classmethod
|
||||
def _create_networks(cls):
|
||||
"""Creates networks, subnets, and routers used in tests.
|
||||
|
||||
The following are expected to be defined and available to the tests:
|
||||
cls.lb_member_vip_net
|
||||
cls.lb_member_vip_subnet
|
||||
cls.lb_member_vip_ipv6_subnet (optional)
|
||||
cls.lb_member_1_net
|
||||
cls.lb_member_1_subnet
|
||||
cls.lb_member_1_ipv6_subnet (optional)
|
||||
cls.lb_member_2_net
|
||||
cls.lb_member_2_subnet
|
||||
cls.lb_member_2_ipv6_subnet (optional)
|
||||
"""
|
||||
|
||||
# Create tenant VIP network
|
||||
network_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_vip_network")}
|
||||
if CONF.network_feature_enabled.port_security:
|
||||
# Note: Allowed Address Pairs requires port security
|
||||
network_kwargs['port_security_enabled'] = True
|
||||
result = cls.lb_mem_net_client.create_network(**network_kwargs)
|
||||
cls.lb_member_vip_net = result['network']
|
||||
LOG.info('lb_member_vip_net: {}'.format(cls.lb_member_vip_net))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_net_client.delete_network,
|
||||
cls.lb_mem_net_client.show_network,
|
||||
cls.lb_member_vip_net['id'])
|
||||
|
||||
# Create tenant VIP subnet
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_vip_subnet"),
|
||||
'network_id': cls.lb_member_vip_net['id'],
|
||||
'cidr': CONF.load_balancer.vip_subnet_cidr,
|
||||
'ip_version': 4}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_vip_subnet = result['subnet']
|
||||
LOG.info('lb_member_vip_subnet: {}'.format(cls.lb_member_vip_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_vip_subnet['id'])
|
||||
|
||||
# Create tenant VIP IPv6 subnet
|
||||
if CONF.load_balancer.test_with_ipv6:
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_vip_ipv6_subnet"),
|
||||
'network_id': cls.lb_member_vip_net['id'],
|
||||
'cidr': CONF.load_balancer.vip_ipv6_subnet_cidr,
|
||||
'ip_version': 6}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_vip_ipv6_subnet = result['subnet']
|
||||
LOG.info('lb_member_vip_ipv6_subnet: {}'.format(
|
||||
cls.lb_member_vip_ipv6_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_vip_ipv6_subnet['id'])
|
||||
|
||||
# Create tenant member 1 network
|
||||
network_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_1_network")}
|
||||
if CONF.network_feature_enabled.port_security:
|
||||
if CONF.load_balancer.enable_security_groups:
|
||||
network_kwargs['port_security_enabled'] = True
|
||||
else:
|
||||
network_kwargs['port_security_enabled'] = False
|
||||
result = cls.lb_mem_net_client.create_network(**network_kwargs)
|
||||
cls.lb_member_1_net = result['network']
|
||||
LOG.info('lb_member_1_net: {}'.format(cls.lb_member_1_net))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_net_client.delete_network,
|
||||
cls.lb_mem_net_client.show_network,
|
||||
cls.lb_member_1_net['id'])
|
||||
|
||||
# Create tenant member 1 subnet
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_1_subnet"),
|
||||
'network_id': cls.lb_member_1_net['id'],
|
||||
'cidr': CONF.load_balancer.member_1_ipv4_subnet_cidr,
|
||||
'ip_version': 4}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_1_subnet = result['subnet']
|
||||
LOG.info('lb_member_1_subnet: {}'.format(cls.lb_member_1_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_1_subnet['id'])
|
||||
|
||||
# Create tenant member 1 ipv6 subnet
|
||||
if CONF.load_balancer.test_with_ipv6:
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_1_ipv6_subnet"),
|
||||
'network_id': cls.lb_member_1_net['id'],
|
||||
'cidr': CONF.load_balancer.member_1_ipv6_subnet_cidr,
|
||||
'ip_version': 6}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_1_ipv6_subnet = result['subnet']
|
||||
LOG.info('lb_member_1_ipv6_subnet: {}'.format(
|
||||
cls.lb_member_1_ipv6_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_1_ipv6_subnet['id'])
|
||||
|
||||
# Create tenant member 2 network
|
||||
network_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_2_network")}
|
||||
if CONF.network_feature_enabled.port_security:
|
||||
if CONF.load_balancer.enable_security_groups:
|
||||
network_kwargs['port_security_enabled'] = True
|
||||
else:
|
||||
network_kwargs['port_security_enabled'] = False
|
||||
result = cls.lb_mem_net_client.create_network(**network_kwargs)
|
||||
cls.lb_member_2_net = result['network']
|
||||
LOG.info('lb_member_2_net: {}'.format(cls.lb_member_2_net))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_net_client.delete_network,
|
||||
cls.lb_mem_net_client.show_network,
|
||||
cls.lb_member_2_net['id'])
|
||||
|
||||
# Create tenant member 2 subnet
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_2_subnet"),
|
||||
'network_id': cls.lb_member_2_net['id'],
|
||||
'cidr': CONF.load_balancer.member_2_ipv4_subnet_cidr,
|
||||
'ip_version': 4}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_2_subnet = result['subnet']
|
||||
LOG.info('lb_member_2_subnet: {}'.format(cls.lb_member_2_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_2_subnet['id'])
|
||||
|
||||
# Create tenant member 2 ipv6 subnet
|
||||
if CONF.load_balancer.test_with_ipv6:
|
||||
subnet_kwargs = {
|
||||
'name': data_utils.rand_name("lb_member_2_ipv6_subnet"),
|
||||
'network_id': cls.lb_member_2_net['id'],
|
||||
'cidr': CONF.load_balancer.member_2_ipv6_subnet_cidr,
|
||||
'ip_version': 6}
|
||||
result = cls.lb_mem_subnet_client.create_subnet(**subnet_kwargs)
|
||||
cls.lb_member_2_ipv6_subnet = result['subnet']
|
||||
LOG.info('lb_member_2_ipv6_subnet: {}'.format(
|
||||
cls.lb_member_2_ipv6_subnet))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_subnet_client.delete_subnet,
|
||||
cls.lb_mem_subnet_client.show_subnet,
|
||||
cls.lb_member_2_ipv6_subnet['id'])
|
||||
|
||||
# Create a router for the subnets (required for the floating IP)
|
||||
router_name = data_utils.rand_name("lb_member_router")
|
||||
result = cls.lb_mem_routers_client.create_router(
|
||||
name=router_name, admin_state_up=True,
|
||||
external_gateway_info=dict(
|
||||
network_id=CONF.network.public_network_id))
|
||||
cls.lb_member_router = result['router']
|
||||
LOG.info('lb_member_router: {}'.format(cls.lb_member_router))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_routers_client.delete_router,
|
||||
cls.lb_mem_routers_client.show_router,
|
||||
cls.lb_member_router['id'])
|
||||
|
||||
# Add VIP subnet to router
|
||||
cls.lb_mem_routers_client.add_router_interface(
|
||||
cls.lb_member_router['id'],
|
||||
subnet_id=cls.lb_member_vip_subnet['id'])
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_member_router['id'],
|
||||
subnet_id=cls.lb_member_vip_subnet['id'])
|
||||
|
||||
# Add member subnet 1 to router
|
||||
cls.lb_mem_routers_client.add_router_interface(
|
||||
cls.lb_member_router['id'],
|
||||
subnet_id=cls.lb_member_1_subnet['id'])
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
test_utils.call_and_ignore_notfound_exc,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_member_router['id'], subnet_id=cls.lb_member_1_subnet['id'])
|
||||
|
||||
# Add member subnet 2 to router
|
||||
cls.lb_mem_routers_client.add_router_interface(
|
||||
cls.lb_member_router['id'],
|
||||
subnet_id=cls.lb_member_2_subnet['id'])
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_mem_routers_client.remove_router_interface,
|
||||
cls.lb_member_router['id'], subnet_id=cls.lb_member_2_subnet['id'])
|
||||
|
||||
@classmethod
|
||||
def _create_webserver(cls, name, network):
|
||||
"""Creates a webserver with two ports.
|
||||
|
||||
webserver_details dictionary contains:
|
||||
server - The compute server object
|
||||
ipv4_address - The IPv4 address for the server (optional)
|
||||
ipv6_address - The IPv6 address for the server (optional)
|
||||
public_ipv4_address - The publicly accessible IPv4 address for the
|
||||
server, this may be a floating IP (optional)
|
||||
|
||||
:param name: The name of the server to create.
|
||||
:param network: The network to boot the server on.
|
||||
:returns: webserver_details dictionary.
|
||||
"""
|
||||
server_kwargs = {
|
||||
'name': data_utils.rand_name(name),
|
||||
'flavorRef': CONF.compute.flavor_ref,
|
||||
'imageRef': CONF.compute.image_ref,
|
||||
'key_name': cls.lb_member_keypair['name']}
|
||||
if (CONF.load_balancer.enable_security_groups and
|
||||
CONF.network_feature_enabled.port_security):
|
||||
server_kwargs['security_groups'] = [
|
||||
{'name': cls.lb_member_sec_group['name']}]
|
||||
if not CONF.load_balancer.disable_boot_network:
|
||||
server_kwargs['networks'] = [{'uuid': network['id']}]
|
||||
|
||||
# Replace the name for clouds that have limitations
|
||||
if CONF.load_balancer.random_server_name_length:
|
||||
r = random.SystemRandom()
|
||||
server_kwargs['name'] = "m{}".format("".join(
|
||||
[r.choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(
|
||||
CONF.load_balancer.random_server_name_length - 1)]
|
||||
))
|
||||
if CONF.load_balancer.availability_zone:
|
||||
server_kwargs['availability_zone'] = (
|
||||
CONF.load_balancer.availability_zone)
|
||||
|
||||
server = cls.lb_mem_servers_client.create_server(
|
||||
**server_kwargs)['server']
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_servers_client.delete_server,
|
||||
cls.lb_mem_servers_client.show_server,
|
||||
server['id'])
|
||||
server = waiters.wait_for_status(
|
||||
cls.lb_mem_servers_client.show_server,
|
||||
server['id'], 'status', 'ACTIVE',
|
||||
CONF.load_balancer.build_interval,
|
||||
CONF.load_balancer.build_timeout,
|
||||
root_tag='server')
|
||||
webserver_details = {'server': server}
|
||||
LOG.info('Created server: {}'.format(server))
|
||||
|
||||
addresses = server['addresses']
|
||||
if CONF.load_balancer.disable_boot_network:
|
||||
instance_network = addresses.values()[0]
|
||||
else:
|
||||
instance_network = addresses[network['name']]
|
||||
for addr in instance_network:
|
||||
if addr['version'] == 4:
|
||||
webserver_details['ipv4_address'] = addr['addr']
|
||||
if addr['version'] == 6:
|
||||
webserver_details['ipv6_address'] = addr['addr']
|
||||
|
||||
if CONF.validation.connect_method == 'floating':
|
||||
result = cls.lb_mem_ports_client.list_ports(
|
||||
network_id=network['id'],
|
||||
mac_address=instance_network[0]['OS-EXT-IPS-MAC:mac_addr'])
|
||||
port_id = result['ports'][0]['id']
|
||||
result = cls.lb_mem_float_ip_client.create_floatingip(
|
||||
floating_network_id=CONF.network.public_network_id,
|
||||
port_id=port_id)
|
||||
floating_ip = result['floatingip']
|
||||
LOG.info('webserver1_floating_ip: {}'.format(floating_ip))
|
||||
cls.addClassResourceCleanup(
|
||||
waiters.wait_for_not_found,
|
||||
cls.lb_mem_float_ip_client.delete_floatingip,
|
||||
cls.lb_mem_float_ip_client.show_floatingip,
|
||||
floatingip_id=floating_ip['id'])
|
||||
webserver_details['public_ipv4_address'] = (
|
||||
floating_ip['floating_ip_address'])
|
||||
else:
|
||||
webserver_details['public_ipv4_address'] = (
|
||||
instance_network[0]['addr'])
|
||||
|
||||
return webserver_details
|
||||
|
||||
@classmethod
|
||||
def _validate_webserver(cls, ip_address, start_id):
|
||||
URL = 'http://{0}'.format(ip_address)
|
||||
validators.validate_URL_response(URL, expected_body=str(start_id))
|
||||
URL = 'http://{0}:81'.format(ip_address)
|
||||
validators.validate_URL_response(URL, expected_body=str(start_id + 1))
|
||||
|
||||
@classmethod
|
||||
def _setup_lb_network_kwargs(cls, lb_kwargs, ip_version):
|
||||
if cls.lb_member_vip_subnet:
|
||||
ip_index = data_utils.rand_int_id(start=10, end=100)
|
||||
if ip_version == 4:
|
||||
network = ipaddress.IPv4Network(
|
||||
six.u(CONF.load_balancer.vip_subnet_cidr))
|
||||
lb_vip_address = str(network[ip_index])
|
||||
subnet_id = cls.lb_member_vip_subnet[const.ID]
|
||||
else:
|
||||
network = ipaddress.IPv6Network(
|
||||
six.u(CONF.load_balancer.vip_ipv6_subnet_cidr))
|
||||
lb_vip_address = str(network[ip_index])
|
||||
subnet_id = cls.lb_member_vip_ipv6_subnet[const.ID]
|
||||
lb_kwargs[const.VIP_SUBNET_ID] = subnet_id
|
||||
lb_kwargs[const.VIP_ADDRESS] = lb_vip_address
|
||||
if CONF.load_balancer.test_with_noop:
|
||||
lb_kwargs[const.VIP_NETWORK_ID] = (
|
||||
cls.lb_member_vip_net[const.ID])
|
||||
else:
|
||||
lb_kwargs[const.VIP_NETWORK_ID] = cls.lb_member_vip_net[const.ID]
|
||||
lb_kwargs[const.VIP_SUBNET_ID] = None
|
@ -1,28 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
"""
|
||||
test_octavia_tempest_plugin
|
||||
----------------------------------
|
||||
|
||||
Tests for `octavia_tempest_plugin` module.
|
||||
"""
|
||||
|
||||
from octavia_tempest_plugin.tests import base
|
||||
|
||||
|
||||
class TestOctavia_tempest_plugin(base.TestCase):
|
||||
|
||||
def test_something(self):
|
||||
pass
|
87
octavia_tempest_plugin/tests/validators.py
Normal file
87
octavia_tempest_plugin/tests/validators.py
Normal file
@ -0,0 +1,87 @@
|
||||
# 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 requests
|
||||
import time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest import config
|
||||
from tempest.lib import exceptions
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def validate_URL_response(URL, expected_status_code=200,
|
||||
expected_body=None, HTTPS_verify=True,
|
||||
client_cert_path=None, CA_certs_path=None,
|
||||
request_interval=CONF.load_balancer.build_interval,
|
||||
request_timeout=CONF.load_balancer.build_timeout):
|
||||
"""Check a URL response (HTTP or HTTPS).
|
||||
|
||||
:param URL: The URL to query.
|
||||
:param expected_status_code: The expected HTTP status code.
|
||||
:param expected_body: The expected response text, None will not compare.
|
||||
:param HTTPS_verify: Should we verify the HTTPS server.
|
||||
:param client_cert_path: Filesystem path to a file with the client private
|
||||
key and certificate.
|
||||
:param CA_certs_path: Filesystem path to a file containing CA certificates
|
||||
to use for HTTPS validation.
|
||||
:param request_interval: Time, in seconds, to timeout a request.
|
||||
:param request_timeout: The maximum time, in seconds, to attempt requests.
|
||||
Failed validation of expected results does not
|
||||
result in a retry.
|
||||
:raises InvalidHttpSuccessCode: The expected_status_code did not match.
|
||||
:raises InvalidHTTPResponseBody: The response body did not match the
|
||||
expected content.
|
||||
:raises TimeoutException: The request timed out.
|
||||
:returns: None
|
||||
"""
|
||||
with requests.Session() as session:
|
||||
session_kwargs = {}
|
||||
if not HTTPS_verify:
|
||||
session_kwargs['verify'] = False
|
||||
if CA_certs_path:
|
||||
session_kwargs['verify'] = CA_certs_path
|
||||
if client_cert_path:
|
||||
session_kwargs['cert'] = client_cert_path
|
||||
session_kwargs['timeout'] = request_interval
|
||||
start = time.time()
|
||||
while time.time() - start < request_timeout:
|
||||
try:
|
||||
response = session.get(URL, **session_kwargs)
|
||||
if response.status_code != expected_status_code:
|
||||
raise exceptions.InvalidHttpSuccessCode(
|
||||
'{0} is not the expected code {1}'.format(
|
||||
response.status_code, expected_status_code))
|
||||
if expected_body and response.text != expected_body:
|
||||
details = '{} does not match expected {}'.format(
|
||||
response.text, expected_body)
|
||||
raise exceptions.InvalidHTTPResponseBody(
|
||||
resp_body=details)
|
||||
return
|
||||
except requests.exceptions.Timeout:
|
||||
# Don't sleep as we have already waited the interval.
|
||||
LOG.info('Request for () timed out. Retrying.'.format(URL))
|
||||
except (exceptions.InvalidHttpSuccessCode,
|
||||
exceptions.InvalidHTTPResponseBody,
|
||||
requests.exceptions.SSLError):
|
||||
raise
|
||||
except Exception as e:
|
||||
LOG.info('Validate URL got exception: {0}. '
|
||||
'Retrying.'.format(e))
|
||||
time.sleep(request_interval)
|
||||
raise exceptions.TimeoutException()
|
174
octavia_tempest_plugin/tests/waiters.py
Normal file
174
octavia_tempest_plugin/tests/waiters.py
Normal file
@ -0,0 +1,174 @@
|
||||
# Copyright 2017 GoDaddy
|
||||
# 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 time
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest import config
|
||||
from tempest.lib.common.utils import test_utils
|
||||
from tempest.lib import exceptions
|
||||
|
||||
from octavia_tempest_plugin.common import constants as const
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def wait_for_status(show_client, id, status_key, status,
|
||||
check_interval, check_timeout, root_tag=None):
|
||||
"""Waits for an object to reach a specific status.
|
||||
|
||||
:param show_client: The tempest service client show method.
|
||||
Ex. cls.os_primary.servers_client.show_server
|
||||
:param id: The id of the object to query.
|
||||
:param status_key: The key of the status field in the response.
|
||||
Ex. provisioning_status
|
||||
:param status: The status to wait for. Ex. "ACTIVE"
|
||||
:check_interval: How often to check the status, in seconds.
|
||||
:check_timeout: The maximum time, in seconds, to check the status.
|
||||
:root_tag: The root tag on the response to remove, if any.
|
||||
:raises CommandFailed: Raised if the object goes into ERROR and ERROR was
|
||||
not the desired status.
|
||||
:raises TimeoutException: The object did not achieve the status or ERROR in
|
||||
the check_timeout period.
|
||||
:returns: The object details from the show client.
|
||||
"""
|
||||
start = int(time.time())
|
||||
LOG.info('Waiting for {name} status to update to {status}'.format(
|
||||
name=show_client.__name__, status=status))
|
||||
while True:
|
||||
response = show_client(id)
|
||||
if root_tag:
|
||||
object_details = response[root_tag]
|
||||
else:
|
||||
object_details = response
|
||||
|
||||
if object_details[status_key] == status:
|
||||
LOG.info('{name}\'s status updated to {status}.'.format(
|
||||
name=show_client.__name__, status=status))
|
||||
return object_details
|
||||
elif object_details[status_key] == 'ERROR':
|
||||
message = ('{name} {field} updated to an invalid state of '
|
||||
'ERROR'.format(name=show_client.__name__,
|
||||
field=status_key))
|
||||
caller = test_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '({caller}) {message}'.format(caller=caller,
|
||||
message=message)
|
||||
raise exceptions.UnexpectedResponseCode(message)
|
||||
elif int(time.time()) - start >= check_timeout:
|
||||
message = (
|
||||
'{name} {field} failed to update to {expected_status} within '
|
||||
'the required time {timeout}. Current status of {name}: '
|
||||
'{status}'.format(
|
||||
name=show_client.__name__,
|
||||
timeout=check_timeout,
|
||||
status=object_details[status_key],
|
||||
expected_status=status,
|
||||
field=status_key
|
||||
))
|
||||
caller = test_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '({caller}) {message}'.format(caller=caller,
|
||||
message=message)
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
time.sleep(check_interval)
|
||||
|
||||
|
||||
def wait_for_not_found(delete_func, show_func, *args, **kwargs):
|
||||
"""Call the delete function, then wait for it to be 'NotFound'
|
||||
|
||||
:param delete_func: The delete function to call.
|
||||
:param show_func: The show function to call looking for 'NotFound'.
|
||||
:param ID: The ID of the object to delete/show.
|
||||
:raises TimeoutException: The object did not achieve the status or ERROR in
|
||||
the check_timeout period.
|
||||
:returns: None
|
||||
"""
|
||||
try:
|
||||
return delete_func(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
return
|
||||
start = int(time.time())
|
||||
LOG.info('Waiting for object to be NotFound')
|
||||
while True:
|
||||
try:
|
||||
show_func(*args, **kwargs)
|
||||
except exceptions.NotFound:
|
||||
return
|
||||
if int(time.time()) - start >= CONF.load_balancer.check_timeout:
|
||||
message = ('{name} did not raise NotFound in {timeout} '
|
||||
'seconds.'.format(
|
||||
name=show_func.__name__,
|
||||
timeout=CONF.load_balancer.check_timeout))
|
||||
raise exceptions.TimeoutException(message)
|
||||
time.sleep(CONF.load_balancer.check_interval)
|
||||
|
||||
|
||||
def wait_for_deleted_status_or_not_found(
|
||||
show_client, id, status_key, check_interval, check_timeout,
|
||||
root_tag=None):
|
||||
"""Waits for an object to reach a DELETED status or be not found (404).
|
||||
|
||||
:param show_client: The tempest service client show method.
|
||||
Ex. cls.os_primary.servers_client.show_server
|
||||
:param id: The id of the object to query.
|
||||
:param status_key: The key of the status field in the response.
|
||||
Ex. provisioning_status
|
||||
:check_interval: How often to check the status, in seconds.
|
||||
:check_timeout: The maximum time, in seconds, to check the status.
|
||||
:root_tag: The root tag on the response to remove, if any.
|
||||
:raises CommandFailed: Raised if the object goes into ERROR and ERROR was
|
||||
not the desired status.
|
||||
:raises TimeoutException: The object did not achieve the status or ERROR in
|
||||
the check_timeout period.
|
||||
:returns: None
|
||||
"""
|
||||
start = int(time.time())
|
||||
LOG.info('Waiting for {name} status to update to DELETED or be not '
|
||||
'found(404)'.format(name=show_client.__name__))
|
||||
while True:
|
||||
try:
|
||||
response = show_client(id)
|
||||
except exceptions.NotFound:
|
||||
return
|
||||
|
||||
if root_tag:
|
||||
object_details = response[root_tag]
|
||||
else:
|
||||
object_details = response
|
||||
|
||||
if object_details[status_key] == const.DELETED:
|
||||
LOG.info('{name}\'s status updated to DELETED.'.format(
|
||||
name=show_client.__name__))
|
||||
return
|
||||
elif int(time.time()) - start >= check_timeout:
|
||||
message = (
|
||||
'{name} {field} failed to update to DELETED or become not '
|
||||
'found (404) within the required time {timeout}. Current '
|
||||
'status of {name}: {status}'.format(
|
||||
name=show_client.__name__,
|
||||
timeout=check_timeout,
|
||||
status=object_details[status_key],
|
||||
field=status_key
|
||||
))
|
||||
caller = test_utils.find_test_caller()
|
||||
if caller:
|
||||
message = '({caller}) {message}'.format(caller=caller,
|
||||
message=message)
|
||||
raise exceptions.TimeoutException(message)
|
||||
|
||||
time.sleep(check_interval)
|
11
playbooks/Octavia-DSVM/pre.yaml
Normal file
11
playbooks/Octavia-DSVM/pre.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
- hosts: all
|
||||
name: Octavia DSVM jobs pre-run playbook
|
||||
tasks:
|
||||
- shell:
|
||||
cmd: |
|
||||
set -e
|
||||
set -x
|
||||
if $(egrep --quiet '(vmx|svm)' /proc/cpuinfo) && [[ ! $(hostname) =~ "ovh" ]]; then
|
||||
export DEVSTACK_GATE_LIBVIRT_TYPE=kvm
|
||||
fi
|
||||
|
@ -2,7 +2,14 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
|
||||
python-dateutil>=2.5.3 # BSD
|
||||
ipaddress>=1.0.17;python_version<'3.3' # PSF
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
oslo.config>=5.2.0 # Apache-2.0
|
||||
oslo.log>=3.36.0 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
requests>=2.14.2 # Apache-2.0
|
||||
six>=1.10.0 # MIT
|
||||
tempest>=17.1.0 # Apache-2.0
|
||||
tenacity>=3.2.1 # Apache-2.0
|
||||
|
@ -18,6 +18,10 @@ classifier =
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
|
||||
[global]
|
||||
setup-hooks =
|
||||
pbr.hooks.setup_hook
|
||||
|
||||
[files]
|
||||
packages =
|
||||
octavia_tempest_plugin
|
||||
@ -48,3 +52,7 @@ output_file = octavia_tempest_plugin/locale/octavia_tempest_plugin.pot
|
||||
all_files = 1
|
||||
build-dir = releasenotes/build
|
||||
source-dir = releasenotes/source
|
||||
|
||||
[entry_points]
|
||||
tempest.test_plugins =
|
||||
octavia-tempest-plugin = octavia_tempest_plugin.plugin:OctaviaTempestPlugin
|
||||
|
@ -6,12 +6,7 @@ hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
|
||||
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
python-subunit>=1.0.0 # Apache-2.0/BSD
|
||||
sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
|
||||
oslosphinx>=4.7.0 # Apache-2.0
|
||||
oslotest>=3.2.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
|
||||
# releasenotes
|
||||
reno>=2.5.0 # Apache-2.0
|
||||
|
20
tox.ini
20
tox.ini
@ -22,9 +22,20 @@ commands = {posargs}
|
||||
commands = python setup.py test --coverage --testr-args='{posargs}'
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
whitelist_externals = rm
|
||||
commands =
|
||||
rm -rf doc/build
|
||||
sphinx-build -W -b html doc/source doc/build/html
|
||||
|
||||
[testenv:releasenotes]
|
||||
deps =
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
|
||||
-r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/doc/requirements.txt
|
||||
commands =
|
||||
sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
||||
@ -38,3 +49,10 @@ show-source = True
|
||||
ignore = E123,E125
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build
|
||||
|
||||
[testenv:genconfig]
|
||||
whitelist_externals = mkdir
|
||||
commands =
|
||||
mkdir -p etc
|
||||
oslo-config-generator --output-file etc/octavia.tempest.conf.sample \
|
||||
--namespace tempest.config
|
||||
|
@ -1,23 +1,27 @@
|
||||
- job:
|
||||
name: octavia-v2-dsvm-scenario
|
||||
name: octavia-dsvm-base
|
||||
parent: devstack-tempest
|
||||
timeout: 7800
|
||||
required-projects:
|
||||
- openstack/barbican
|
||||
- openstack/diskimage-builder
|
||||
- openstack/octavia
|
||||
- openstack/octavia-tempest-plugin
|
||||
- openstack/python-barbicanclient
|
||||
- openstack/python-octaviaclient
|
||||
pre-run: playbooks/Octavia-DSVM/pre.yaml
|
||||
irrelevant-files:
|
||||
- ^.*\.rst$
|
||||
- ^api-ref/.*$
|
||||
- ^doc/.*$
|
||||
- ^etc/.*$
|
||||
- ^releasenotes/.*$
|
||||
vars:
|
||||
devstack_localrc:
|
||||
TEMPEST_PLUGINS: "'{{ ansible_user_dir }}/src/git.openstack.org/openstack/octavia-tempest-plugin'"
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$OCTAVIA_CONF:
|
||||
DEFAULT:
|
||||
debug: True
|
||||
devstack_services:
|
||||
barbican: true
|
||||
c-bak: false
|
||||
ceilometer-acentral: false
|
||||
ceilometer-acompute: false
|
||||
@ -41,19 +45,82 @@
|
||||
s-object: false
|
||||
s-proxy: false
|
||||
tempest: true
|
||||
devstack_plugins:
|
||||
octavia: https://github.com/openstack/octavia.git
|
||||
|
||||
- job:
|
||||
name: octavia-dsvm-live-base
|
||||
parent: octavia-dsvm-base
|
||||
required-projects:
|
||||
- openstack/barbican
|
||||
- openstack/diskimage-builder
|
||||
- openstack/python-barbicanclient
|
||||
vars:
|
||||
devstack_services:
|
||||
barbican: true
|
||||
neutron-qos: true
|
||||
devstack_plugins:
|
||||
barbican: https://github.com/openstack/barbican.git
|
||||
octavia: https://github.com/openstack/octavia.git
|
||||
neutron: https://github.com/openstack/neutron.git
|
||||
|
||||
- job:
|
||||
name: octavia-dsvm-noop-base
|
||||
parent: octavia-dsvm-base
|
||||
vars:
|
||||
devstack_localrc:
|
||||
DISABLE_AMP_IMAGE_BUILD: True
|
||||
devstack_local_conf:
|
||||
test-config:
|
||||
"$TEMPEST_CONFIG":
|
||||
load_balancer:
|
||||
test_with_noop: True
|
||||
post-config:
|
||||
$OCTAVIA_CONF:
|
||||
controller_worker:
|
||||
amphora_driver: amphora_noop_driver
|
||||
compute_driver: compute_noop_driver
|
||||
network_driver: network_noop_driver
|
||||
certificates:
|
||||
cert_manager: local_cert_manager
|
||||
devstack_services:
|
||||
barbican: false
|
||||
|
||||
- job:
|
||||
name: octavia-v2-dsvm-noop-api
|
||||
parent: octavia-dsvm-noop-base
|
||||
vars:
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$OCTAVIA_CONF:
|
||||
api_settings:
|
||||
api_v1_enabled: False
|
||||
tempest_concurrency: 2
|
||||
tempest_test_regex: ^octavia_tempest_plugin
|
||||
tempest_test_regex: ^octavia_tempest_plugin.tests.api.v2
|
||||
tox_envlist: all
|
||||
|
||||
- job:
|
||||
name: octavia-v2-dsvm-noop-py35-api
|
||||
parent: octavia-v2-dsvm-noop-api
|
||||
vars:
|
||||
devstack_localrc:
|
||||
USE_PYTHON3: true
|
||||
|
||||
- job:
|
||||
name: octavia-v2-dsvm-scenario
|
||||
parent: octavia-dsvm-base
|
||||
vars:
|
||||
devstack_local_conf:
|
||||
post-config:
|
||||
$OCTAVIA_CONF:
|
||||
api_settings:
|
||||
api_v1_enabled: False
|
||||
tempest_concurrency: 2
|
||||
tempest_test_regex: ^octavia_tempest_plugin.tests.scenario.v2
|
||||
tox_envlist: all
|
||||
|
||||
- job:
|
||||
name: octavia-v2-dsvm-py35-scenario
|
||||
parent: octavia-v2-dsvm-scenario
|
||||
timeout: 7800
|
||||
vars:
|
||||
devstack_localrc:
|
||||
USE_PYTHON3: true
|
||||
|
16
zuul.d/projects.yaml
Normal file
16
zuul.d/projects.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
# Note: Some official OpenStack wide jobs are still defined in the
|
||||
# project-config repository
|
||||
- project:
|
||||
check:
|
||||
jobs:
|
||||
- octavia-v2-dsvm-noop-api
|
||||
- octavia-v2-dsvm-noop-py35-api
|
||||
- octavia-v2-dsvm-scenario
|
||||
- octavia-v2-dsvm-py35-scenario
|
||||
gate:
|
||||
queue: octavia
|
||||
jobs:
|
||||
- octavia-v2-dsvm-noop-api
|
||||
- octavia-v2-dsvm-noop-py35-api
|
||||
- octavia-v2-dsvm-scenario
|
||||
- octavia-v2-dsvm-py35-scenario
|
Loading…
x
Reference in New Issue
Block a user