diff --git a/.gitignore b/.gitignore index 22606278..963e589a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,9 +38,6 @@ nosetests.xml .project .pydevproject -# PyCharm -.idea - # Complexity output/*.html output/*/index.html @@ -58,4 +55,4 @@ ChangeLog .*sw? # Files created by releasenotes build -releasenotes/build +releasenotes/build \ No newline at end of file diff --git a/octavia_tempest_plugin/config.py b/octavia_tempest_plugin/config.py deleted file mode 100644 index c722b97b..00000000 --- a/octavia_tempest_plugin/config.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2016 Rackspace Inc. -# Copyright 2017 Catalyst IT Ltd -# -# 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 - - -service_option = cfg.BoolOpt( - 'loadbalancer', - default=True, - help="Whether or not loadbalancing service is expected to be available" -) - -octavia_group = cfg.OptGroup(name='loadbalancer', - title='Loadbalancing Service Options') - -OctaviaGroup = [ - 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 Octavia service."), - cfg.IntOpt('build_interval', - default=5, - help='Time in seconds between build status checks for ' - 'non-loadbalancer resources to build'), - cfg.IntOpt('build_timeout', - default=30, - help='Timeout in seconds to wait for non-loadbalancer ' - 'resources to build'), - cfg.IntOpt('lb_build_interval', - default=10, - help='Time in seconds between build status checks for a ' - 'loadbalancer.'), - cfg.IntOpt('lb_build_timeout', - default=900, - help='Timeout in seconds to wait for a ' - 'loadbalancer to build.'), - cfg.StrOpt('premade_server_ip', - default=None, - help='IP of the premade server.'), - cfg.StrOpt('premade_server_subnet_id', - default=None, - help='Subnet ID of the premade server.'), - cfg.StrOpt('vip_network_id', - default=None, - help='Existing network ID to use for loadbalancer.'), - cfg.StrOpt('vip_subnet_id', - default=None, - help='Existing subnet ID to use for loadbalancer.'), - 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.'), - cfg.StrOpt('member_role', - default='load-balancer_member', - help="Role to add to users created for octavia tests."), - cfg.StrOpt('vip_qos_policy_id', - default=None, - help='Existing QoS Policy ID in neutron to use for' - 'loadbalancer.') -] diff --git a/octavia_tempest_plugin/contrib/__init__.py b/octavia_tempest_plugin/contrib/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/contrib/httpd/__init__.py b/octavia_tempest_plugin/contrib/httpd/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/plugin.py b/octavia_tempest_plugin/plugin.py deleted file mode 100644 index 4d5f6a28..00000000 --- a/octavia_tempest_plugin/plugin.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2017 GoDaddy -# Copyright 2017 Catalyst IT Ltd -# -# 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): - conf.register_opt( - project_config.service_option, group='service_available' - ) - conf.register_group(project_config.octavia_group) - conf.register_opts(project_config.OctaviaGroup, - group=project_config.octavia_group.name) - - def get_opt_lists(self): - return [ - ('service_available', [project_config.service_option]), - (project_config.octavia_group.name, project_config.OctaviaGroup) - ] - - def get_service_clients(self): - octavia_config = config.service_client_config( - project_config.octavia_group.name - ) - module_path = 'octavia_tempest_plugin.services.v2.loadbalancer_client' - - params = { - 'name': 'octavia_v2', - 'service_version': 'octavia.v2', - 'module_path': module_path, - 'client_names': ['LoadbalancerClient'], - } - params.update(octavia_config) - - return [params] diff --git a/octavia_tempest_plugin/services/__init__.py b/octavia_tempest_plugin/services/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/services/v2/__init__.py b/octavia_tempest_plugin/services/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/services/v2/base.py b/octavia_tempest_plugin/services/v2/base.py deleted file mode 100644 index 22ed5f2c..00000000 --- a/octavia_tempest_plugin/services/v2/base.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2017 Catalyst IT Ltd -# -# 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 oslo_serialization import jsonutils -from tempest.lib.common import rest_client - - -class LoadbalancerClientBase(rest_client.RestClient): - def get_list_objs(self, obj): - resp, body = self.get('/v2.0/lbaas/%s' % obj) - - return resp, jsonutils.loads(body) - - def delete_obj(self, obj, id, cascade=False): - url = '/v2.0/lbaas/{obj}/{id}'.format(obj=obj, id=id) - if cascade: - url += '?cascade=True' - return self.delete(url) - - def get_obj(self, obj, id): - resp, body = self.get('/v2.0/lbaas/{obj}/{id}'.format(obj=obj, id=id)) - - return resp, jsonutils.loads(body) - - def post_json(self, obj, req_body, extra_headers={}): - headers = {"Content-Type": "application/json"} - headers = dict(headers, **extra_headers) - url_path = '/v2.0/lbaas/%s' % obj - - resp, body = self.post(url_path, json.dumps(req_body), headers=headers) - - return resp, jsonutils.loads(body) - - def put_json(self, obj, id, req_body, extra_headers={}): - headers = {"Content-Type": "application/json"} - headers = dict(headers, **extra_headers) - url_path = '/v2.0/lbaas/%s/%s' % (obj, id) - - resp, body = self.put(url_path, json.dumps(req_body), headers=headers) - - return resp, jsonutils.loads(body) diff --git a/octavia_tempest_plugin/services/v2/loadbalancer_client.py b/octavia_tempest_plugin/services/v2/loadbalancer_client.py deleted file mode 100644 index 4af302a6..00000000 --- a/octavia_tempest_plugin/services/v2/loadbalancer_client.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2017 GoDaddy -# Copyright 2017 Catalyst IT Ltd -# -# 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.lib import exceptions - -from octavia_tempest_plugin.services.v2 import base as client_base - - -class LoadbalancerClient(client_base.LoadbalancerClientBase): - """Tempest REST client for Octavia V2 API.""" - - def delete_resource(self, res, id, ignore_error=False, cascade=False): - try: - resp, _ = self.delete_obj(res, id, cascade=cascade) - return resp - except (exceptions.NotFound, exceptions.Conflict): - if ignore_error: - return None diff --git a/octavia_tempest_plugin/tests/server_util.py b/octavia_tempest_plugin/tests/server_util.py deleted file mode 100644 index 54fa4f82..00000000 --- a/octavia_tempest_plugin/tests/server_util.py +++ /dev/null @@ -1,326 +0,0 @@ -# Copyright 2017 Catalyst IT Ltd -# -# 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 pkg_resources -import random -import shlex -import string -import subprocess -import tempfile -import time - -from oslo_log import log as logging -from oslo_utils import excutils -from tempest import config -from tempest.lib.common import fixed_network -from tempest.lib.common import rest_client -from tempest.lib.common.utils.linux import remote_client -from tempest.lib.common.utils import test_utils -from tempest.lib import exceptions - -CONF = config.CONF -LOG = logging.getLogger(__name__) - -SERVER_BINARY = pkg_resources.resource_filename( - 'octavia_tempest_plugin.contrib.httpd', 'httpd.bin') - - -class BuildErrorException(exceptions.TempestException): - message = "Server %(server_id)s failed to build and is in ERROR status" - - -def _get_task_state(body): - return body.get('OS-EXT-STS:task_state', None) - - -def wait_for_server_status(client, server_id, status, ready_wait=True, - extra_timeout=0, raise_on_error=True): - """Waits for a server to reach a given status.""" - - # NOTE(afazekas): UNKNOWN status possible on ERROR - # or in a very early stage. - body = client.show_server(server_id)['server'] - old_status = server_status = body['status'] - old_task_state = task_state = _get_task_state(body) - start_time = int(time.time()) - timeout = client.build_timeout + extra_timeout - while True: - # NOTE(afazekas): Now the BUILD status only reached - # between the UNKNOWN->ACTIVE transition. - # TODO(afazekas): enumerate and validate the stable status set - if status == 'BUILD' and server_status != 'UNKNOWN': - return - if server_status == status: - if ready_wait: - if status == 'BUILD': - return - # NOTE(afazekas): The instance is in "ready for action state" - # when no task in progress - if task_state is None: - # without state api extension 3 sec usually enough - time.sleep(CONF.compute.ready_wait) - return - else: - return - - time.sleep(client.build_interval) - body = client.show_server(server_id)['server'] - server_status = body['status'] - task_state = _get_task_state(body) - if (server_status != old_status) or (task_state != old_task_state): - LOG.info('State transition "%s" ==> "%s" after %d second wait', - '/'.join((old_status, str(old_task_state))), - '/'.join((server_status, str(task_state))), - time.time() - start_time) - if (server_status == 'ERROR') and raise_on_error: - if 'fault' in body: - raise BuildErrorException(body['fault'], - server_id=server_id) - else: - raise BuildErrorException(server_id=server_id) - - timed_out = int(time.time()) - start_time >= timeout - - if timed_out: - expected_task_state = 'None' if ready_wait else 'n/a' - message = ('Server %(server_id)s failed to reach %(status)s ' - 'status and task state "%(expected_task_state)s" ' - 'within the required time (%(timeout)s s).' % - {'server_id': server_id, - 'status': status, - 'expected_task_state': expected_task_state, - 'timeout': timeout}) - message += ' Current status: %s.' % server_status - message += ' Current task state: %s.' % task_state - caller = test_utils.find_test_caller() - if caller: - message = '(%s) %s' % (caller, message) - raise exceptions.TimeoutException(message) - old_status = server_status - old_task_state = task_state - - -def wait_for_server_termination(client, server_id, ignore_error=False): - """Waits for server to reach termination.""" - try: - body = client.show_server(server_id)['server'] - except exceptions.NotFound: - return - old_status = body['status'] - old_task_state = _get_task_state(body) - start_time = int(time.time()) - while True: - time.sleep(client.build_interval) - try: - body = client.show_server(server_id)['server'] - except exceptions.NotFound: - return - server_status = body['status'] - task_state = _get_task_state(body) - if (server_status != old_status) or (task_state != old_task_state): - LOG.info('State transition "%s" ==> "%s" after %d second wait', - '/'.join((old_status, str(old_task_state))), - '/'.join((server_status, str(task_state))), - time.time() - start_time) - if server_status == 'ERROR' and not ignore_error: - raise exceptions.DeleteErrorException(resource_id=server_id) - - if int(time.time()) - start_time >= client.build_timeout: - raise exceptions.TimeoutException - old_status = server_status - old_task_state = task_state - - -def create_server(clients, name=None, flavor=None, image_id=None, - validatable=False, validation_resources=None, - tenant_network=None, wait_until=None, availability_zone=None, - **kwargs): - """Common wrapper utility returning a test server. - - This method is a common wrapper returning a test server that can be - pingable or sshable. - - :param name: Name of the server to be provisioned. If not defined a random - string ending with '-instance' will be generated. - :param flavor: Flavor of the server to be provisioned. If not defined, - CONF.compute.flavor_ref will be used instead. - :param image_id: ID of the image to be used to provision the server. If not - defined, CONF.compute.image_ref will be used instead. - :param clients: Client manager which provides OpenStack Tempest clients. - :param validatable: Whether the server will be pingable or sshable. - :param validation_resources: Resources created for the connection to the - server. Include a keypair, a security group and an IP. - :param tenant_network: Tenant network to be used for creating a server. - :param wait_until: Server status to wait for the server to reach after - its creation. - :returns: a tuple - """ - if name is None: - r = random.SystemRandom() - name = "m{}".format("".join( - [r.choice(string.ascii_uppercase + string.digits) - for i in range( - CONF.loadbalancer.random_server_name_length - 1)] - )) - if flavor is None: - flavor = CONF.compute.flavor_ref - if image_id is None: - image_id = CONF.compute.image_ref - if availability_zone is None: - availability_zone = CONF.loadbalancer.availability_zone - - kwargs = fixed_network.set_networks_kwarg( - tenant_network, kwargs) or {} - - if availability_zone: - kwargs.update({'availability_zone': availability_zone}) - - if CONF.validation.run_validation and validatable: - LOG.debug("Provisioning test server with validation resources %s", - validation_resources) - if 'security_groups' in kwargs: - kwargs['security_groups'].append( - {'name': validation_resources['security_group']['name']}) - else: - try: - kwargs['security_groups'] = [ - {'name': validation_resources['security_group']['name']}] - except KeyError: - LOG.debug("No security group provided.") - - if 'key_name' not in kwargs: - try: - kwargs['key_name'] = validation_resources['keypair']['name'] - except KeyError: - LOG.debug("No key provided.") - - if CONF.validation.connect_method == 'floating': - if wait_until is None: - wait_until = 'ACTIVE' - - body = clients.servers_client.create_server( - name=name, - imageRef=image_id, - flavorRef=flavor, - config_drive=True, - **kwargs - ) - server = rest_client.ResponseBody(body.response, body['server']) - - def _setup_validation_fip(): - if CONF.service_available.neutron: - ifaces = clients.interfaces_client.list_interfaces(server['id']) - validation_port = None - for iface in ifaces['interfaceAttachments']: - if not tenant_network or (iface['net_id'] == - tenant_network['id']): - validation_port = iface['port_id'] - break - if not validation_port: - # NOTE(artom) This will get caught by the catch-all clause in - # the wait_until loop below - raise ValueError('Unable to setup floating IP for validation: ' - 'port not found on tenant network') - clients.floating_ips_client.update_floatingip( - validation_resources['floating_ip']['id'], - port_id=validation_port) - else: - fip_client = clients.compute_floating_ips_client - fip_client.associate_floating_ip_to_server( - floating_ip=validation_resources['floating_ip']['ip'], - server_id=server['id']) - - if wait_until: - try: - wait_for_server_status( - clients.servers_client, server['id'], wait_until) - - # Multiple validatable servers are not supported for now. Their - # creation will fail with the condition above (l.58). - if CONF.validation.run_validation and validatable: - if CONF.validation.connect_method == 'floating': - _setup_validation_fip() - - except Exception: - with excutils.save_and_reraise_exception(): - try: - clients.servers_client.delete_server(server['id']) - except Exception: - LOG.exception('Deleting server %s failed', server['id']) - try: - wait_for_server_termination(clients.servers_client, - server['id']) - except Exception: - LOG.exception('Server %s failed to delete in time', - server['id']) - - return server - - -def clear_server(servers_client, id): - try: - servers_client.delete_server(id) - except exceptions.NotFound: - pass - wait_for_server_termination(servers_client, id) - - -def _execute(cmd, cwd=None): - args = shlex.split(cmd) - subprocess_args = {'stdout': subprocess.PIPE, - 'stderr': subprocess.STDOUT, - 'cwd': cwd} - proc = subprocess.Popen(args, **subprocess_args) - stdout, stderr = proc.communicate() - if proc.returncode != 0: - LOG.error('Command %s returned with exit status %s, output %s, ' - 'error %s', cmd, proc.returncode, stdout, stderr) - raise exceptions.CommandFailed(proc.returncode, cmd, stdout, stderr) - return stdout - - -def copy_file(floating_ip, private_key, local_file, remote_file): - """Copy web server script to instance.""" - with tempfile.NamedTemporaryFile() as key: - key.write(private_key.encode('utf-8')) - key.flush() - dest = ( - "%s@%s:%s" % - (CONF.validation.image_ssh_user, floating_ip, remote_file) - ) - cmd = ("scp -v -o UserKnownHostsFile=/dev/null " - "-o StrictHostKeyChecking=no " - "-i %(key_file)s %(file)s %(dest)s" % {'key_file': key.name, - 'file': local_file, - 'dest': dest}) - return _execute(cmd) - - -def run_webserver(connect_ip, private_key): - httpd = "/dev/shm/httpd.bin" - - linux_client = remote_client.RemoteClient( - connect_ip, - CONF.validation.image_ssh_user, - pkey=private_key, - ) - linux_client.validate_authentication() - - # TODO(kong): We may figure out an elegant way to copy file to instance - # in future. - LOG.debug("Copying the webserver binary to the server.") - copy_file(connect_ip, private_key, SERVER_BINARY, httpd) - - LOG.debug("Starting services on the server.") - linux_client.exec_command('sudo screen -d -m %s -port 80 -id 1' % httpd) - linux_client.exec_command('sudo screen -d -m %s -port 81 -id 2' % httpd) diff --git a/octavia_tempest_plugin/tests/test_octavia_tempest_plugin.py b/octavia_tempest_plugin/tests/test_octavia_tempest_plugin.py new file mode 100644 index 00000000..7805347c --- /dev/null +++ b/octavia_tempest_plugin/tests/test_octavia_tempest_plugin.py @@ -0,0 +1,28 @@ +# -*- 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 diff --git a/octavia_tempest_plugin/tests/v2/__init__.py b/octavia_tempest_plugin/tests/v2/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/tests/v2/api/test_loadbalancer.py b/octavia_tempest_plugin/tests/v2/api/test_loadbalancer.py deleted file mode 100644 index 4325321e..00000000 --- a/octavia_tempest_plugin/tests/v2/api/test_loadbalancer.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2017 GoDaddy -# Copyright 2017 Catalyst IT Ltd -# -# 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.lib.common.utils import data_utils -from tempest.lib import decorators - -from octavia_tempest_plugin.tests.v2 import base - - -class LoadbalancerTest(base.BaseLoadbalancerTest): - name_prefix = 'Tempest-LoadbalancerTest' - - @decorators.idempotent_id('94c66b04-1ab3-4375-a921-89e48d833c1d') - @decorators.attr(type='slow') - def test_crud_loadbalancer(self): - # Create loadbalancer - params = {} - if self.vip_network_id: - params['vip_network_id'] = self.vip_network_id - if self.vip_subnet_id: - params['vip_subnet_id'] = self.vip_subnet_id - lb_id = self.create_loadbalancer(**params)['id'] - - # Get loadbalancers - resp, body = self.lb_client.get_list_objs('loadbalancers') - self.assertEqual(200, resp.status) - self.assertIn( - lb_id, - [item['id'] for item in body['loadbalancers']] - ) - - # Update loadbalancer - new_name = data_utils.rand_name('lb', prefix=self.name_prefix) - self.update_loadbalancer(lb_id, name=new_name) - - # Get loadbalancer - resp, body = self.lb_client.get_obj('loadbalancers', lb_id) - self.assertEqual(200, resp.status) - self.assertEqual(new_name, body['loadbalancer']['name']) - - # Delete loadbalancer - self.delete_loadbalancer(lb_id) - - # Get loadbalancers - resp, body = self.lb_client.get_list_objs('loadbalancers') - self.assertEqual(200, resp.status) - self.assertNotIn( - lb_id, - [item['id'] for item in body['loadbalancers']] - ) diff --git a/octavia_tempest_plugin/tests/v2/base.py b/octavia_tempest_plugin/tests/v2/base.py deleted file mode 100644 index fccca0cc..00000000 --- a/octavia_tempest_plugin/tests/v2/base.py +++ /dev/null @@ -1,523 +0,0 @@ -# Copyright 2017 GoDaddy -# Copyright 2017 Catalyst IT Ltd -# -# 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 -import requests -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib.common.utils import test_utils -from tempest.lib import exceptions as lib_exc -from tempest import test -import tenacity - -from octavia_tempest_plugin.tests import server_util - -CONF = config.CONF -LOG = logging.getLogger(__name__) - - -class BaseLoadbalancerTest(test.BaseTestCase): - credentials = (['lbmember', CONF.loadbalancer.member_role], 'admin') - name_prefix = 'Tempest-BaseLoadbalancerTest' - vip_network_id = None - vip_subnet_id = None - vip_address = None - member_subnet_id = None - member_network_id = None - vm_ip = None - - @classmethod - def skip_checks(cls): - super(BaseLoadbalancerTest, cls).skip_checks() - - if not CONF.service_available.loadbalancer: - raise cls.skipException("Loadbalancing service is not available.") - - service_list = { - 'loadbalancing': CONF.service_available.loadbalancer, - 'compute': CONF.service_available.nova, - 'image': CONF.service_available.glance, - 'neutron': CONF.service_available.neutron - } - for srv, available in service_list.items(): - if not available: - raise cls.skipException("Service %s is not available." % srv) - - @classmethod - def setup_clients(cls): - super(BaseLoadbalancerTest, cls).setup_clients() - - cls.lb_client = cls.os_roles_lbmember.octavia_v2.LoadbalancerClient() - cls.servers_client = cls.os_roles_lbmember.servers_client - cls.networks_client = cls.os_roles_lbmember.networks_client - cls.subnets_client = cls.os_roles_lbmember.subnets_client - cls.interfaces_client = cls.os_roles_lbmember.interfaces_client - cls.sg_rule_client = cls.os_roles_lbmember.security_group_rules_client - cls.floatingip_client = cls.os_roles_lbmember.floating_ips_client - cls.routers_adm_client = cls.os_admin.routers_client - - if CONF.identity.auth_version == 'v3': - project_id = cls.os_roles_lbmember.auth_provider.auth_data[1][ - 'project']['id'] - else: - project_id = cls.os_roles_lbmember.auth_provider.auth_data[ - 1]['token']['tenant']['id'] - - cls.tenant_id = project_id - cls.user_id = cls.os_roles_lbmember.auth_provider.auth_data[1][ - 'user']['id'] - - @classmethod - def resource_setup(cls): - """Creates network resources.""" - super(BaseLoadbalancerTest, cls).resource_setup() - if not CONF.loadbalancer.vip_network_id: - network_name = data_utils.rand_name( - 'network', - prefix=cls.name_prefix - ) - body = cls.networks_client.create_network(name=network_name) - cls.vip_network_id = body['network']['id'] - cls.addClassResourceCleanup( - test_utils.call_and_ignore_notfound_exc, - cls.networks_client.delete_network, - cls.vip_network_id - ) - - subnet_name = data_utils.rand_name( - 'subnet', - prefix=cls.name_prefix - ) - body = cls.subnets_client.create_subnet( - name=subnet_name, - network_id=cls.vip_network_id, - cidr='10.100.1.0/24', - ip_version=4, - gateway_ip='10.100.1.1', - ) - cls.vip_subnet_id = body['subnet']['id'] - cls.addClassResourceCleanup( - test_utils.call_and_ignore_notfound_exc, - cls.subnets_client.delete_subnet, - cls.vip_subnet_id - ) - cls.member_network_id = cls.vip_network_id - cls.member_subnet_id = cls.vip_subnet_id - - if CONF.validation.connect_method == 'floating': - router_name = data_utils.rand_name( - 'router', - prefix=cls.name_prefix - ) - kwargs = { - 'name': router_name, - 'tenant_id': cls.tenant_id - } - if CONF.network.public_network_id: - kwargs['external_gateway_info'] = dict( - network_id=CONF.network.public_network_id - ) - body = cls.routers_adm_client.create_router(**kwargs) - cls.router_id = body['router']['id'] - cls.addClassResourceCleanup( - test_utils.call_and_ignore_notfound_exc, - cls.routers_adm_client.delete_router, - cls.router_id, - ) - - cls.routers_adm_client.add_router_interface( - cls.router_id, subnet_id=cls.member_subnet_id - ) - cls.addClassResourceCleanup( - test_utils.call_and_ignore_notfound_exc, - cls.routers_adm_client.remove_router_interface, - cls.router_id, - subnet_id=cls.member_subnet_id - ) - else: - cls.vip_network_id = CONF.loadbalancer.vip_network_id - cls.vip_subnet_id = CONF.loadbalancer.vip_subnet_id - cls.member_subnet_id = CONF.loadbalancer.premade_server_subnet_id - - @tenacity.retry( - wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), - stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), - retry=tenacity.retry_if_exception_type(AssertionError) - ) - def await_loadbalancer_active(self, id, name=None): - resp, body = self.lb_client.get_obj('loadbalancers', id) - self.assertEqual(200, resp.status) - - lb = body['loadbalancer'] - - if lb['provisioning_status'] == 'ERROR': - raise Exception('Failed to wait for loadbalancer to be active, ' - 'actual provisioning_status: ERROR') - - self.assertEqual('ACTIVE', lb['provisioning_status']) - - if name: - self.assertEqual(name, lb['name']) - - @tenacity.retry( - wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), - stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), - retry=tenacity.retry_if_exception_type(AssertionError) - ) - def await_loadbalancer_deleted(self, id): - resp, body = self.lb_client.get_obj('loadbalancers', id) - self.assertEqual(200, resp.status) - - lb = body['loadbalancer'] - self.assertEqual('DELETED', lb['provisioning_status']) - - @tenacity.retry( - wait=tenacity.wait_fixed(CONF.loadbalancer.lb_build_interval), - stop=tenacity.stop_after_delay(CONF.loadbalancer.lb_build_timeout), - retry=tenacity.retry_if_exception_type(AssertionError) - ) - def await_listener_active(self, id, name=None): - resp, body = self.lb_client.get_obj('listeners', id) - self.assertEqual(200, resp.status) - - listener = body['listener'] - - if listener['provisioning_status'] == 'ERROR': - raise Exception('Failed to wait for listener to be active, actual ' - 'provisioning_status: ERROR') - - self.assertEqual('ACTIVE', listener['provisioning_status']) - self.assertEqual('ONLINE', listener['operating_status']) - - if name: - self.assertEqual(name, listener['name']) - - def create_loadbalancer(self, **kwargs): - name = data_utils.rand_name('lb', prefix=self.name_prefix) - payload = {'loadbalancer': {'name': name}} - payload['loadbalancer'].update(kwargs) - - resp, body = self.lb_client.post_json('loadbalancers', payload) - self.assertEqual(201, resp.status) - - lb = body['loadbalancer'] - lb_id = lb['id'] - - self.addCleanup(self.delete_loadbalancer, lb_id, ignore_error=True) - LOG.info('Waiting for loadbalancer %s to be active', lb_id) - self.await_loadbalancer_active( - lb_id, - name=payload['loadbalancer']['name'] - ) - - self.lb_id = lb['id'] - self.vip_port = lb['vip_port_id'] - if CONF.validation.connect_method == 'floating': - self.vip_address = self._associate_floatingip() - else: - self.vip_address = lb['vip_address'] - - return lb - - def update_loadbalancer(self, lb_id, **kwargs): - new_name = data_utils.rand_name('lb', prefix=self.name_prefix) - payload = {'loadbalancer': {'name': new_name}} - payload['loadbalancer'].update(kwargs) - - resp, _ = self.lb_client.put_json('loadbalancers', lb_id, payload) - self.assertEqual(200, resp.status) - - # Wait for loadbalancer to be active - LOG.info( - 'Waiting for loadbalancer %s to be active after update', lb_id - ) - self.await_loadbalancer_active(lb_id) - - def delete_loadbalancer(self, id, ignore_error=False): - """Delete loadbalancer and wait for it to be deleted. - - Only if loadbalancer is deleted completely can other network resources - be deleted. - """ - resp = self.lb_client.delete_resource('loadbalancers', id, - ignore_error=ignore_error, - cascade=True) - if resp: - self.assertEqual(204, resp.status) - - LOG.info('Waiting for loadbalancer %s to be deleted', id) - self.await_loadbalancer_deleted(id) - - def create_listener(self, lb_id, **kwargs): - name = data_utils.rand_name('listener', prefix=self.name_prefix) - payload = { - 'listener': { - 'protocol': 'HTTP', - 'protocol_port': '80', - 'loadbalancer_id': lb_id, - 'name': name - } - } - payload['listener'].update(kwargs) - - resp, body = self.lb_client.post_json('listeners', payload) - self.assertEqual(201, resp.status) - - listener_id = body['listener']['id'] - - LOG.info( - 'Waiting for loadbalancer %s to be active after listener %s ' - 'creation', lb_id, listener_id - ) - self.addCleanup(self.delete_listener, listener_id, lb_id, - ignore_error=True) - self.await_loadbalancer_active(lb_id) - - return body['listener'] - - def update_listener(self, listener_id, lb_id, **kwargs): - new_name = data_utils.rand_name('listener', prefix=self.name_prefix) - payload = {'listener': {'name': new_name}} - payload['listener'].update(kwargs) - - resp, _ = self.lb_client.put_json('listeners', listener_id, payload) - self.assertEqual(200, resp.status) - - # Wait for loadbalancer to be active - LOG.info( - 'Waiting for loadbalancer %s to be active after listener %s ' - 'update', lb_id, listener_id - ) - self.await_loadbalancer_active(lb_id) - - def delete_listener(self, id, lb_id, ignore_error=False): - resp = self.lb_client.delete_resource('listeners', id, - ignore_error=ignore_error) - if resp: - self.assertEqual(204, resp.status) - - LOG.info( - 'Waiting for loadbalancer %s to be active after deleting ' - 'listener %s', lb_id, id - ) - self.await_loadbalancer_active(lb_id) - - def create_pool(self, lb_id, **kwargs): - name = data_utils.rand_name('pool', prefix=self.name_prefix) - payload = { - 'pool': { - 'name': name, - 'loadbalancer_id': lb_id, - 'lb_algorithm': 'ROUND_ROBIN', - 'protocol': 'HTTP' - } - } - payload['pool'].update(kwargs) - - resp, body = self.lb_client.post_json('pools', payload) - self.assertEqual(201, resp.status) - - pool_id = body['pool']['id'] - - LOG.info( - 'Waiting for loadbalancer %s to be active after pool %s creation', - lb_id, pool_id - ) - self.addCleanup(self.delete_pool, pool_id, lb_id, ignore_error=True) - self.await_loadbalancer_active(lb_id) - - return body['pool'] - - def update_pool(self, pool_id, lb_id, **kwargs): - new_name = data_utils.rand_name('pool', prefix=self.name_prefix) - payload = {'pool': {'name': new_name}} - payload['pool'].update(kwargs) - - resp, _ = self.lb_client.put_json('pools', pool_id, payload) - self.assertEqual(200, resp.status) - - # Wait for loadbalancer to be active - LOG.info( - 'Waiting for loadbalancer %s to be active after pool %s update', - lb_id, pool_id - ) - self.await_loadbalancer_active(lb_id) - - def delete_pool(self, id, lb_id, ignore_error=False): - resp = self.lb_client.delete_resource('pools', id, - ignore_error=ignore_error) - if resp: - self.assertEqual(204, resp.status) - - LOG.info( - 'Waiting for loadbalancer %s to be active after deleting ' - 'pool %s', lb_id, id - ) - self.await_loadbalancer_active(lb_id) - - def create_member(self, pool_id, lb_id, **kwargs): - name = data_utils.rand_name('member', prefix=self.name_prefix) - payload = {'member': {'name': name}} - payload['member'].update(kwargs) - - resp, body = self.lb_client.post_json( - 'pools/%s/members' % pool_id, payload - ) - self.assertEqual(201, resp.status) - - member_id = body['member']['id'] - - LOG.info( - 'Waiting for loadbalancer %s to be active after adding ' - 'member %s', lb_id, member_id - ) - self.addCleanup(self.delete_member, member_id, pool_id, - lb_id, ignore_error=True) - self.await_loadbalancer_active(lb_id) - - return body['member'] - - def delete_member(self, id, pool_id, lb_id, ignore_error=False): - resp = self.lb_client.delete_resource( - 'pools/%s/members' % pool_id, - id, - ignore_error=ignore_error - ) - if resp: - self.assertEqual(204, resp.status) - - LOG.info( - 'Waiting for loadbalancer %s to be active after deleting ' - 'member %s', lb_id, id - ) - self.await_loadbalancer_active(lb_id) - - def _wait_for_lb_functional(self, vip_address): - session = requests.Session() - start = time.time() - - while time.time() - start < CONF.loadbalancer.lb_build_timeout: - try: - session.get("http://{0}".format(vip_address), timeout=2) - time.sleep(1) - return - except Exception: - LOG.warning('Server is not passing initial traffic. Waiting.') - time.sleep(1) - LOG.error('Server did not begin passing traffic within the timeout ' - 'period. Failing test.') - raise lib_exc.ServerFault() - - def check_members_balanced(self): - session = requests.Session() - response_counts = {} - - self._wait_for_lb_functional(self.vip_address) - - # Send a number requests to lb vip - for i in range(20): - try: - r = session.get('http://{0}'.format(self.vip_address), - timeout=2) - LOG.debug('Loadbalancer response: %s', r.content) - - if r.content in response_counts: - response_counts[r.content] += 1 - else: - response_counts[r.content] = 1 - - except Exception: - LOG.exception('Failed to send request to loadbalancer vip') - raise lib_exc.BadRequest(message='Failed to connect to lb') - - # Ensure the correct number of members - self.assertEqual(2, len(response_counts)) - - # Ensure both members got the same number of responses - self.assertEqual(1, len(set(response_counts.values()))) - - def _delete_floatingip(self, floating_ip): - self.floatingip_client.update_floatingip( - floating_ip, - port_id=None - ) - test_utils.call_and_ignore_notfound_exc( - self.floatingip_client.delete_floatingip, floating_ip - ) - - def _associate_floatingip(self): - # Associate floatingip with loadbalancer vip - floatingip = self.floatingip_client.create_floatingip( - floating_network_id=CONF.network.public_network_id - )['floatingip'] - floatip_vip = floatingip['floating_ip_address'] - self.addCleanup(self._delete_floatingip, floatingip['id']) - - LOG.debug('Floating ip %s created.', floatip_vip) - - self.floatingip_client.update_floatingip( - floatingip['id'], - port_id=self.vip_port - ) - - LOG.debug('Floating ip %s associated with vip.', floatip_vip) - return floatip_vip - - def create_backend(self): - if CONF.loadbalancer.premade_server_ip: - self.vm_ip = CONF.loadbalancer.premade_server_ip - return - - vr_resources = self.vr.resources - vm = server_util.create_server( - self.os_roles_lbmember, - validatable=True, - validation_resources=vr_resources, - wait_until='ACTIVE', - tenant_network=({'id': self.member_network_id} - if self.member_network_id else None), - ) - self.addCleanup( - server_util.clear_server, - self.os_roles_lbmember.servers_client, - vm['id'] - ) - - # Get vm private ip address. - ifaces = self.interfaces_client.list_interfaces(vm['id']) - for iface in ifaces['interfaceAttachments']: - if not self.member_network_id or (iface['net_id'] == - self.vip_network_id): - for ip_info in iface['fixed_ips']: - if not self.vip_subnet_id or (ip_info['subnet_id'] == - self.vip_subnet_id): - self.vm_ip = ip_info['ip_address'] - break - if self.vm_ip: - break - - self.assertIsNotNone(self.vm_ip) - - if CONF.validation.connect_method == 'floating': - connect_ip = vr_resources['floating_ip']['floating_ip_address'] - else: - connect_ip = self.vm_ip - - server_util.run_webserver( - connect_ip, - vr_resources['keypair']['private_key'] - ) - LOG.debug('Web servers are running inside %s', vm['id']) diff --git a/octavia_tempest_plugin/tests/v2/scenario/__init__.py b/octavia_tempest_plugin/tests/v2/scenario/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/octavia_tempest_plugin/tests/v2/scenario/test_basic_ops.py b/octavia_tempest_plugin/tests/v2/scenario/test_basic_ops.py deleted file mode 100644 index 25d16ff1..00000000 --- a/octavia_tempest_plugin/tests/v2/scenario/test_basic_ops.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2017 Catalyst IT Ltd -# -# 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_log import log as logging - -from tempest import config -from tempest.lib.common import validation_resources as vr -from tempest.lib import decorators - -from octavia_tempest_plugin.tests.v2 import base - -LOG = logging.getLogger(__name__) -CONF = config.CONF - - -class BasicOpsTest(base.BaseLoadbalancerTest): - name_prefix = 'Tempest-BasicOpsTest' - - def setUp(self): - super(BasicOpsTest, self).setUp() - - # Setup network resources for instance - resources = dict( - keypair=True, - security_group=True, - security_group_rules=True, - floating_ip=CONF.validation.connect_method == 'floating' - ) - self.vr = self.useFixture( - vr.ValidationResourcesFixture( - self.os_roles_lbmember, - use_neutron=True, - floating_network_id=CONF.network.public_network_id, - **resources - ) - ) - - # Add security group rule to allow http request - self.sg_rule_client.create_security_group_rule( - security_group_id=self.vr.resources['security_group']['id'], - protocol='tcp', - ethertype='IPv4', - port_range_min=80, - port_range_max=81, - direction='ingress' - ) - - self.create_backend() - - @decorators.idempotent_id('250ebc41-645e-43fb-a79a-e3035f338e2a') - @decorators.attr(type='slow') - def test_basic_ops(self): - # Create loadbalancer - params = {} - if self.vip_network_id: - params['vip_network_id'] = self.vip_network_id - if self.vip_subnet_id: - params['vip_subnet_id'] = self.vip_subnet_id - - self.create_loadbalancer(**params) - - # Create pool - pool = self.create_pool(self.lb_id) - self.pool_id = pool['id'] - - # Create listener - params = {'default_pool_id': self.pool_id} - listener = self.create_listener(self.lb_id, **params) - self.listener_id = listener['id'] - - # Add members to the pool - for port in [80, 81]: - params = { - 'address': self.vm_ip, - 'protocol_port': port, - } - if self.member_subnet_id: - params['subnet_id'] = self.member_subnet_id - - self.create_member(self.pool_id, self.lb_id, **params) - - self.check_members_balanced() diff --git a/setup.cfg b/setup.cfg index b4526143..993c4f7f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,10 +18,6 @@ classifier = Programming Language :: Python :: 3 Programming Language :: Python :: 3.5 -[global] -setup-hooks = - pbr.hooks.setup_hook - [files] packages = octavia_tempest_plugin @@ -52,7 +48,3 @@ 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 diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml deleted file mode 100644 index e7db7ce7..00000000 --- a/zuul.d/projects.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Note: Some official OpenStack wide jobs are still defined in the -# project-config repository -- project: - check: - jobs: - - octavia-v2-dsvm-scenario - - octavia-v2-dsvm-py35-scenario - gate: - queue: octavia - jobs: - - octavia-v2-dsvm-scenario - - octavia-v2-dsvm-py35-scenario