Remove CLI testing once and for all
The cli tests have been marked for removal for ~6 months and the framework was the first thing included in tempest lib. There has been more than enough time for all the projects to pick this up in the client repos. So let's remove it all! As part of this a couple of missing entries for tempest's requirements.txt were found. These dependencies were being installed by the clients so the fact they were missing was never noticed prior to this. This commit also adds these missing entries back into the requirements file. Change-Id: I4f8638f1c048bbdb598dd181f4af272ef9923806
This commit is contained in:
parent
7af67608af
commit
464d287f5b
@ -18,7 +18,7 @@ Tempest Design Principles that we strive to live by.
|
||||
incorrect assessment of their cloud. Explicit is always better.
|
||||
- Tempest uses OpenStack public interfaces. Tests in Tempest should
|
||||
only touch public interfaces, API calls (native or 3rd party),
|
||||
public CLI or libraries.
|
||||
or libraries.
|
||||
- Tempest should not touch private or implementation specific
|
||||
interfaces. This means not directly going to the database, not
|
||||
directly hitting the hypervisors, not testing extensions not
|
||||
|
@ -1 +0,0 @@
|
||||
../../../tempest/cli/README.rst
|
@ -201,27 +201,6 @@
|
||||
#build_interval = 1
|
||||
|
||||
|
||||
[cli]
|
||||
|
||||
#
|
||||
# From tempest.config
|
||||
#
|
||||
|
||||
# enable cli tests (boolean value)
|
||||
#enabled = true
|
||||
|
||||
# directory where python client binaries are located (string value)
|
||||
#cli_dir = /usr/local/bin
|
||||
|
||||
# Whether the tempest run location has access to the *-manage
|
||||
# commands. In a pure blackbox environment it will not. (boolean
|
||||
# value)
|
||||
#has_manage = true
|
||||
|
||||
# Number of seconds to wait on a CLI timeout (integer value)
|
||||
#timeout = 15
|
||||
|
||||
|
||||
[compute]
|
||||
|
||||
#
|
||||
|
@ -9,10 +9,8 @@ testtools>=0.9.36,!=1.2.0
|
||||
boto>=2.32.1
|
||||
paramiko>=1.13.0
|
||||
netaddr>=0.7.12
|
||||
python-glanceclient>=0.15.0
|
||||
python-cinderclient>=1.1.0
|
||||
python-heatclient>=0.3.0
|
||||
testrepository>=0.0.18
|
||||
pyOpenSSL>=0.11
|
||||
oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0
|
||||
oslo.config>=1.9.3,<1.10.0 # Apache-2.0
|
||||
oslo.i18n>=1.5.0,<1.6.0 # Apache-2.0
|
||||
@ -24,3 +22,4 @@ iso8601>=0.1.9
|
||||
fixtures>=0.3.14
|
||||
testscenarios>=0.4
|
||||
tempest-lib>=0.5.0
|
||||
PyYAML>=3.1.0
|
||||
|
@ -14,7 +14,6 @@ to make this clear.
|
||||
|
||||
| tempest/
|
||||
| api/ - API tests
|
||||
| cli/ - CLI tests
|
||||
| scenario/ - complex scenario tests
|
||||
| stress/ - stress tests
|
||||
| thirdparty/ - 3rd party api tests
|
||||
@ -38,16 +37,6 @@ projects themselves, possibly as functional tests in their unit test
|
||||
frameworks.
|
||||
|
||||
|
||||
:ref:`cli_field_guide`
|
||||
----------------------
|
||||
|
||||
CLI tests use the openstack CLI to interact with the OpenStack
|
||||
cloud. CLI testing in unit tests is somewhat difficult because unlike
|
||||
server testing, there is no access to server code to
|
||||
instantiate. Tempest seems like a logical place for this, as it
|
||||
prereqs having a running OpenStack cloud.
|
||||
|
||||
|
||||
:ref:`scenario_field_guide`
|
||||
---------------------------
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
.. _cli_field_guide:
|
||||
|
||||
Tempest Field Guide to CLI tests
|
||||
================================
|
||||
|
||||
|
||||
What are these tests?
|
||||
---------------------
|
||||
The cli tests test the various OpenStack command line interface tools
|
||||
to ensure that they minimally function. The current scope is read only
|
||||
operations on a cloud that are hard to test via unit tests.
|
||||
|
||||
|
||||
Why are these tests in tempest?
|
||||
-------------------------------
|
||||
These tests exist here because it is extremely difficult to build a
|
||||
functional enough environment in the python-\*client unit tests to
|
||||
provide this kind of testing. Because we already put up a cloud in the
|
||||
gate with devstack + tempest it was decided it was better to have
|
||||
these as a side tree in tempest instead of another QA effort which
|
||||
would split review time.
|
||||
|
||||
|
||||
Scope of these tests
|
||||
--------------------
|
||||
This should stay limited to the scope of testing the cli. Functional
|
||||
testing of the cloud should be elsewhere, this is about exercising the
|
||||
cli code.
|
||||
|
||||
|
||||
Example of a good test
|
||||
----------------------
|
||||
Tests should be isolated to a single command in one of the python
|
||||
clients.
|
||||
|
||||
Tests should not modify the cloud.
|
||||
|
||||
If a test is validating the cli for bad data, it should do it with
|
||||
assertRaises.
|
||||
|
||||
A reasonable example of an existing test is as follows::
|
||||
|
||||
def test_admin_list(self):
|
||||
self.nova('list')
|
||||
self.nova('list', params='--all-tenants 1')
|
||||
self.nova('list', params='--all-tenants 0')
|
||||
self.assertRaises(subprocess.CalledProcessError,
|
||||
self.nova,
|
||||
'list',
|
||||
params='--all-tenants bad')
|
@ -1,126 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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 functools
|
||||
|
||||
from tempest_lib.cli import base
|
||||
from tempest_lib.cli import output_parser
|
||||
import testtools
|
||||
|
||||
from tempest.common import credentials
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.openstack.common import versionutils
|
||||
from tempest import test
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
def check_client_version(client, version):
|
||||
"""Checks if the client's version is compatible with the given version
|
||||
|
||||
@param client: The client to check.
|
||||
@param version: The version to compare against.
|
||||
@return: True if the client version is compatible with the given version
|
||||
parameter, False otherwise.
|
||||
"""
|
||||
current_version = base.execute(client, '', params='--version',
|
||||
merge_stderr=True, cli_dir=CONF.cli.cli_dir)
|
||||
|
||||
if not current_version.strip():
|
||||
raise exceptions.TempestException('"%s --version" output was empty' %
|
||||
client)
|
||||
|
||||
return versionutils.is_compatible(version, current_version,
|
||||
same_major=False)
|
||||
|
||||
|
||||
def min_client_version(*args, **kwargs):
|
||||
"""A decorator to skip tests if the client used isn't of the right version.
|
||||
|
||||
@param client: The client command to run. For python-novaclient, this is
|
||||
'nova', for python-cinderclient this is 'cinder', etc.
|
||||
@param version: The minimum version required to run the CLI test.
|
||||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*func_args, **func_kwargs):
|
||||
if not check_client_version(kwargs['client'], kwargs['version']):
|
||||
msg = "requires %s client version >= %s" % (kwargs['client'],
|
||||
kwargs['version'])
|
||||
raise testtools.TestCase.skipException(msg)
|
||||
return func(*func_args, **func_kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class ClientTestBase(test.BaseTestCase):
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(ClientTestBase, cls).skip_checks()
|
||||
if not CONF.identity_feature_enabled.api_v2:
|
||||
raise cls.skipException("CLI clients rely on identity v2 API, "
|
||||
"which is configured as not available")
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
if not CONF.cli.enabled:
|
||||
msg = "cli testing disabled"
|
||||
raise cls.skipException(msg)
|
||||
super(ClientTestBase, cls).resource_setup()
|
||||
cls.isolated_creds = credentials.get_isolated_credentials(cls.__name__)
|
||||
cls.creds = cls.isolated_creds.get_admin_creds()
|
||||
|
||||
def _get_clients(self):
|
||||
clients = base.CLIClient(self.creds.username,
|
||||
self.creds.password,
|
||||
self.creds.tenant_name,
|
||||
CONF.identity.uri, CONF.cli.cli_dir)
|
||||
return clients
|
||||
|
||||
# TODO(mtreinish): The following code is basically copied from tempest-lib.
|
||||
# The base cli test class in tempest-lib 0.0.1 doesn't work as a mixin like
|
||||
# is needed here. The code below should be removed when tempest-lib
|
||||
# provides a way to provide this functionality
|
||||
def setUp(self):
|
||||
super(ClientTestBase, self).setUp()
|
||||
self.clients = self._get_clients()
|
||||
self.parser = output_parser
|
||||
|
||||
def assertTableStruct(self, items, field_names):
|
||||
"""Verify that all items has keys listed in field_names.
|
||||
|
||||
:param items: items to assert are field names in the output table
|
||||
:type items: list
|
||||
:param field_names: field names from the output table of the cmd
|
||||
:type field_names: list
|
||||
"""
|
||||
for item in items:
|
||||
for field in field_names:
|
||||
self.assertIn(field, item)
|
||||
|
||||
def assertFirstLineStartsWith(self, lines, beginning):
|
||||
"""Verify that the first line starts with a string
|
||||
|
||||
:param lines: strings for each line of output
|
||||
:type lines: list
|
||||
:param beginning: verify this is at the beginning of the first line
|
||||
:type beginning: string
|
||||
"""
|
||||
self.assertTrue(lines[0].startswith(beginning),
|
||||
msg=('Beginning of first line has invalid content: %s'
|
||||
% lines[:3]))
|
@ -1 +0,0 @@
|
||||
This directory consists of simple read only python client tests.
|
@ -1,18 +0,0 @@
|
||||
HeatTemplateFormatVersion: '2012-12-12'
|
||||
Description: Minimal template to test validation
|
||||
Parameters:
|
||||
InstanceImage:
|
||||
Description: Glance image name
|
||||
Type: String
|
||||
InstanceType:
|
||||
Description: Nova instance type
|
||||
Type: String
|
||||
Default: m1.small
|
||||
AllowedValues: [m1.tiny, m1.small, m1.medium, m1.large, m1.nano, m1.xlarge, m1.micro]
|
||||
ConstraintDescription: must be a valid nova instance type.
|
||||
Resources:
|
||||
InstanceResource:
|
||||
Type: OS::Nova::Server
|
||||
Properties:
|
||||
flavor: {Ref: InstanceType}
|
||||
image: {Ref: InstanceImage}
|
@ -1,19 +0,0 @@
|
||||
heat_template_version: 2013-05-23
|
||||
description: A minimal HOT test template
|
||||
parameters:
|
||||
instance_image:
|
||||
description: Glance image name
|
||||
type: string
|
||||
instance_type:
|
||||
description: Nova instance type
|
||||
type: string
|
||||
default: m1.small
|
||||
constraints:
|
||||
- allowed_values: [m1.small, m1.medium, m1.large]
|
||||
description: instance_type must be one of m1.small, m1.medium or m1.large
|
||||
resources:
|
||||
instance:
|
||||
type: OS::Nova::Server
|
||||
properties:
|
||||
image: { get_param: instance_image }
|
||||
flavor: { get_param: instance_type }
|
@ -1,106 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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 re
|
||||
|
||||
from oslo_log import log as logging
|
||||
from tempest_lib import exceptions
|
||||
|
||||
from tempest import cli
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimpleReadOnlyGlanceClientTest(cli.ClientTestBase):
|
||||
"""Basic, read-only tests for Glance CLI client.
|
||||
|
||||
Checks return values and output of read-only commands.
|
||||
These tests do not presume any content, nor do they create
|
||||
their own. They only verify the structure of output if present.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
if not CONF.service_available.glance:
|
||||
msg = ("%s skipped as Glance is not available" % cls.__name__)
|
||||
raise cls.skipException(msg)
|
||||
super(SimpleReadOnlyGlanceClientTest, cls).resource_setup()
|
||||
|
||||
def glance(self, *args, **kwargs):
|
||||
return self.clients.glance(*args,
|
||||
endpoint_type=CONF.image.endpoint_type,
|
||||
**kwargs)
|
||||
|
||||
@test.idempotent_id('c6bd9bf9-717f-4458-8d74-05b682ea7adf')
|
||||
def test_glance_fake_action(self):
|
||||
self.assertRaises(exceptions.CommandFailed,
|
||||
self.glance,
|
||||
'this-does-not-exist')
|
||||
|
||||
@test.idempotent_id('72bcdaf3-11cd-48cb-bb8e-62b329acc1ef')
|
||||
def test_glance_image_list(self):
|
||||
out = self.glance('image-list')
|
||||
endpoints = self.parser.listing(out)
|
||||
self.assertTableStruct(endpoints, [
|
||||
'ID', 'Name', 'Disk Format', 'Container Format',
|
||||
'Size', 'Status'])
|
||||
|
||||
@test.idempotent_id('965d294c-8772-4899-ba33-26ee23406135')
|
||||
def test_glance_member_list(self):
|
||||
tenant_name = '--tenant-id %s' % CONF.identity.admin_tenant_name
|
||||
out = self.glance('member-list',
|
||||
params=tenant_name)
|
||||
endpoints = self.parser.listing(out)
|
||||
self.assertTableStruct(endpoints,
|
||||
['Image ID', 'Member ID', 'Can Share'])
|
||||
|
||||
@test.idempotent_id('43b80ee5-4297-47f3-ab4c-6f81b9c6edb3')
|
||||
def test_glance_help(self):
|
||||
help_text = self.glance('help')
|
||||
lines = help_text.split('\n')
|
||||
self.assertFirstLineStartsWith(lines, 'usage: glance')
|
||||
|
||||
commands = []
|
||||
cmds_start = lines.index('Positional arguments:')
|
||||
cmds_end = lines.index('Optional arguments:')
|
||||
command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)')
|
||||
for line in lines[cmds_start:cmds_end]:
|
||||
match = command_pattern.match(line)
|
||||
if match:
|
||||
commands.append(match.group(1))
|
||||
commands = set(commands)
|
||||
wanted_commands = set(('image-create', 'image-delete', 'help',
|
||||
'image-download', 'image-show', 'image-update',
|
||||
'member-create', 'member-delete',
|
||||
'member-list', 'image-list'))
|
||||
self.assertFalse(wanted_commands - commands)
|
||||
|
||||
# Optional arguments:
|
||||
|
||||
@test.idempotent_id('3b2359ea-3719-4b47-81e5-44a042572b11')
|
||||
def test_glance_version(self):
|
||||
self.glance('', flags='--version')
|
||||
|
||||
@test.idempotent_id('1a52d3bd-3edf-4d67-b3da-999a5d9e0c5e')
|
||||
def test_glance_debug_list(self):
|
||||
self.glance('image-list', flags='--debug')
|
||||
|
||||
@test.idempotent_id('6f42b076-f9a7-4e2b-a729-579f53e7814e')
|
||||
def test_glance_timeout(self):
|
||||
self.glance('image-list', flags='--timeout %d' % CONF.cli.timeout)
|
@ -1,117 +0,0 @@
|
||||
# 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
|
||||
import os
|
||||
|
||||
from oslo_log import log as logging
|
||||
import yaml
|
||||
|
||||
import tempest.cli
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimpleReadOnlyHeatClientTest(tempest.cli.ClientTestBase):
|
||||
"""Basic, read-only tests for Heat CLI client.
|
||||
|
||||
Basic smoke test for the heat CLI commands which do not require
|
||||
creating or modifying stacks.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
if (not CONF.service_available.heat):
|
||||
msg = ("Skipping all Heat cli tests because it is "
|
||||
"not available")
|
||||
raise cls.skipException(msg)
|
||||
super(SimpleReadOnlyHeatClientTest, cls).resource_setup()
|
||||
cls.heat_template_path = os.path.join(os.path.dirname(
|
||||
os.path.dirname(os.path.realpath(__file__))),
|
||||
'heat_templates/heat_minimal.yaml')
|
||||
|
||||
def heat(self, *args, **kwargs):
|
||||
return self.clients.heat(
|
||||
*args, endpoint_type=CONF.orchestration.endpoint_type, **kwargs)
|
||||
|
||||
@test.idempotent_id('0ae034bb-ce35-45e8-b7aa-3e339cd3140f')
|
||||
def test_heat_stack_list(self):
|
||||
self.heat('stack-list')
|
||||
|
||||
@test.idempotent_id('a360d069-7250-4aed-9721-0a6f2db7c3fa')
|
||||
def test_heat_stack_list_debug(self):
|
||||
self.heat('stack-list', flags='--debug')
|
||||
|
||||
@test.idempotent_id('e1b7c177-5ab4-4d3f-8a26-ea01ebbd2b8c')
|
||||
def test_heat_resource_template_fmt_default(self):
|
||||
ret = self.heat('resource-template OS::Nova::Server')
|
||||
self.assertIn('Type: OS::Nova::Server', ret)
|
||||
|
||||
@test.idempotent_id('93f82f76-aab2-4910-9359-11cf48f2a46b')
|
||||
def test_heat_resource_template_fmt_arg_short_yaml(self):
|
||||
ret = self.heat('resource-template -F yaml OS::Nova::Server')
|
||||
self.assertIn('Type: OS::Nova::Server', ret)
|
||||
self.assertIsInstance(yaml.safe_load(ret), dict)
|
||||
|
||||
@test.idempotent_id('7356a98c-e14d-43f0-8c25-c9f7daa0aafa')
|
||||
def test_heat_resource_template_fmt_arg_long_json(self):
|
||||
ret = self.heat('resource-template --format json OS::Nova::Server')
|
||||
self.assertIn('"Type": "OS::Nova::Server"', ret)
|
||||
self.assertIsInstance(json.loads(ret), dict)
|
||||
|
||||
@test.idempotent_id('2fd99d20-beff-4667-b42e-de9095f671d7')
|
||||
def test_heat_resource_type_list(self):
|
||||
ret = self.heat('resource-type-list')
|
||||
rsrc_types = self.parser.listing(ret)
|
||||
self.assertTableStruct(rsrc_types, ['resource_type'])
|
||||
|
||||
@test.idempotent_id('62f60dbf-d139-4698-b230-a09fb531d643')
|
||||
def test_heat_resource_type_show(self):
|
||||
rsrc_schema = self.heat('resource-type-show OS::Nova::Server')
|
||||
# resource-type-show returns a json resource schema
|
||||
self.assertIsInstance(json.loads(rsrc_schema), dict)
|
||||
|
||||
@test.idempotent_id('6ca16ff7-9d5f-4448-a8c2-4cecc7b5ba3a')
|
||||
def test_heat_template_validate_yaml(self):
|
||||
ret = self.heat('template-validate -f %s' % self.heat_template_path)
|
||||
# On success template-validate returns a json representation
|
||||
# of the template parameters
|
||||
self.assertIsInstance(json.loads(ret), dict)
|
||||
|
||||
@test.idempotent_id('35241014-16ea-4cb6-ad3e-4ee5f41446de')
|
||||
def test_heat_template_validate_hot(self):
|
||||
ret = self.heat('template-validate -f %s' % self.heat_template_path)
|
||||
self.assertIsInstance(json.loads(ret), dict)
|
||||
|
||||
@test.idempotent_id('34d43e0a-36dc-4ea8-9b85-0189e3de89d8')
|
||||
def test_heat_help(self):
|
||||
self.heat('help')
|
||||
|
||||
@tempest.cli.min_client_version(client='heat', version='0.2.7')
|
||||
@test.idempotent_id('c122c08b-839d-49d1-afd1-bc546b2d18d3')
|
||||
def test_heat_bash_completion(self):
|
||||
self.heat('bash-completion')
|
||||
|
||||
@test.idempotent_id('1b045e12-2fa0-4895-9282-00668428dfbe')
|
||||
def test_heat_help_cmd(self):
|
||||
# Check requesting help for a specific command works
|
||||
help_text = self.heat('help resource-template')
|
||||
lines = help_text.split('\n')
|
||||
self.assertFirstLineStartsWith(lines, 'usage: heat resource-template')
|
||||
|
||||
@test.idempotent_id('c7837f8f-d0a8-47fd-b75b-14ba3e3fa9a2')
|
||||
def test_heat_version(self):
|
||||
self.heat('', flags='--version')
|
@ -1,220 +0,0 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# 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 logging
|
||||
import re
|
||||
|
||||
from tempest_lib import exceptions
|
||||
import testtools
|
||||
|
||||
from tempest import cli
|
||||
from tempest import clients
|
||||
from tempest import config
|
||||
from tempest import test
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimpleReadOnlyCinderClientTest(cli.ClientTestBase):
|
||||
"""Basic, read-only tests for Cinder CLI client.
|
||||
|
||||
Checks return values and output of read-only commands.
|
||||
These tests do not presume any content, nor do they create
|
||||
their own. They only verify the structure of output if present.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
if not CONF.service_available.cinder:
|
||||
msg = ("%s skipped as Cinder is not available" % cls.__name__)
|
||||
raise cls.skipException(msg)
|
||||
super(SimpleReadOnlyCinderClientTest, cls).resource_setup()
|
||||
id_cl = clients.AdminManager().identity_client
|
||||
tenant = id_cl.get_tenant_by_name(CONF.identity.admin_tenant_name)
|
||||
cls.admin_tenant_id = tenant['id']
|
||||
|
||||
def cinder(self, *args, **kwargs):
|
||||
return self.clients.cinder(*args,
|
||||
endpoint_type=CONF.volume.endpoint_type,
|
||||
**kwargs)
|
||||
|
||||
@test.idempotent_id('229bc6dc-d804-4668-b753-b590caf63061')
|
||||
def test_cinder_fake_action(self):
|
||||
self.assertRaises(exceptions.CommandFailed,
|
||||
self.cinder,
|
||||
'this-does-not-exist')
|
||||
|
||||
@test.idempotent_id('77140216-14db-4fc5-a246-e2a587e9e99b')
|
||||
def test_cinder_absolute_limit_list(self):
|
||||
roles = self.parser.listing(self.cinder('absolute-limits'))
|
||||
self.assertTableStruct(roles, ['Name', 'Value'])
|
||||
|
||||
@test.idempotent_id('2206b9ce-1a36-4a0a-a129-e5afc7cee1dd')
|
||||
def test_cinder_backup_list(self):
|
||||
backup_list = self.parser.listing(self.cinder('backup-list'))
|
||||
self.assertTableStruct(backup_list, ['ID', 'Volume ID', 'Status',
|
||||
'Name', 'Size', 'Object Count',
|
||||
'Container'])
|
||||
|
||||
@test.idempotent_id('c7f50346-cd99-4e0b-953f-796ff5f47295')
|
||||
def test_cinder_extra_specs_list(self):
|
||||
extra_specs_list = self.parser.listing(self.cinder('extra-specs-list'))
|
||||
self.assertTableStruct(extra_specs_list, ['ID', 'Name', 'extra_specs'])
|
||||
|
||||
@test.idempotent_id('9de694cb-b40b-442c-a30c-5f9873e144f7')
|
||||
def test_cinder_volumes_list(self):
|
||||
list = self.parser.listing(self.cinder('list'))
|
||||
self.assertTableStruct(list, ['ID', 'Status', 'Name', 'Size',
|
||||
'Volume Type', 'Bootable',
|
||||
'Attached to'])
|
||||
self.cinder('list', params='--all-tenants 1')
|
||||
self.cinder('list', params='--all-tenants 0')
|
||||
self.assertRaises(exceptions.CommandFailed,
|
||||
self.cinder,
|
||||
'list',
|
||||
params='--all-tenants bad')
|
||||
|
||||
@test.idempotent_id('56f7c15c-ee82-4f23-bbe8-ce99b66da493')
|
||||
def test_cinder_quota_class_show(self):
|
||||
"""This CLI can accept and string as param."""
|
||||
roles = self.parser.listing(self.cinder('quota-class-show',
|
||||
params='abc'))
|
||||
self.assertTableStruct(roles, ['Property', 'Value'])
|
||||
|
||||
@test.idempotent_id('a919a811-b7f0-47a7-b4e5-f3eb674dd200')
|
||||
def test_cinder_quota_defaults(self):
|
||||
"""This CLI can accept and string as param."""
|
||||
roles = self.parser.listing(self.cinder('quota-defaults',
|
||||
params=self.admin_tenant_id))
|
||||
self.assertTableStruct(roles, ['Property', 'Value'])
|
||||
|
||||
@test.idempotent_id('18166673-ffa8-4df3-b60c-6375532288bc')
|
||||
def test_cinder_quota_show(self):
|
||||
"""This CLI can accept and string as param."""
|
||||
roles = self.parser.listing(self.cinder('quota-show',
|
||||
params=self.admin_tenant_id))
|
||||
self.assertTableStruct(roles, ['Property', 'Value'])
|
||||
|
||||
@test.idempotent_id('b2c66ed9-ca96-4dc4-94cc-8083e664e516')
|
||||
def test_cinder_rate_limits(self):
|
||||
rate_limits = self.parser.listing(self.cinder('rate-limits'))
|
||||
self.assertTableStruct(rate_limits, ['Verb', 'URI', 'Value', 'Remain',
|
||||
'Unit', 'Next_Available'])
|
||||
|
||||
@test.idempotent_id('7a19955b-807c-481a-a2ee-9d76733eac28')
|
||||
@testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
|
||||
'Volume snapshot not available.')
|
||||
def test_cinder_snapshot_list(self):
|
||||
snapshot_list = self.parser.listing(self.cinder('snapshot-list'))
|
||||
self.assertTableStruct(snapshot_list, ['ID', 'Volume ID', 'Status',
|
||||
'Name', 'Size'])
|
||||
|
||||
@test.idempotent_id('6e54ecd9-7ba9-490d-8e3b-294b67139e73')
|
||||
def test_cinder_type_list(self):
|
||||
type_list = self.parser.listing(self.cinder('type-list'))
|
||||
self.assertTableStruct(type_list, ['ID', 'Name'])
|
||||
|
||||
@test.idempotent_id('2c363583-24a0-4980-b9cb-b50c0d241e82')
|
||||
def test_cinder_list_extensions(self):
|
||||
roles = self.parser.listing(self.cinder('list-extensions'))
|
||||
self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated'])
|
||||
|
||||
@test.idempotent_id('691bd6df-30ad-4be7-927b-a02d62aaa38a')
|
||||
def test_cinder_credentials(self):
|
||||
credentials = self.parser.listing(self.cinder('credentials'))
|
||||
self.assertTableStruct(credentials, ['User Credentials', 'Value'])
|
||||
|
||||
@test.idempotent_id('5c6d71a3-4904-4a3a-aec9-7fd4aa830e95')
|
||||
def test_cinder_availability_zone_list(self):
|
||||
zone_list = self.parser.listing(self.cinder('availability-zone-list'))
|
||||
self.assertTableStruct(zone_list, ['Name', 'Status'])
|
||||
|
||||
@test.idempotent_id('9b0fd5a6-f955-42b9-a42f-6f542a80b9a3')
|
||||
def test_cinder_endpoints(self):
|
||||
out = self.cinder('endpoints')
|
||||
tables = self.parser.tables(out)
|
||||
for table in tables:
|
||||
headers = table['headers']
|
||||
self.assertTrue(2 >= len(headers))
|
||||
self.assertEqual('Value', headers[1])
|
||||
|
||||
@test.idempotent_id('301b5ae1-9591-4e9f-999c-d525a9bdf822')
|
||||
def test_cinder_service_list(self):
|
||||
service_list = self.parser.listing(self.cinder('service-list'))
|
||||
self.assertTableStruct(service_list, ['Binary', 'Host', 'Zone',
|
||||
'Status', 'State', 'Updated_at'])
|
||||
|
||||
@test.idempotent_id('7260ae52-b462-461e-9048-36d0bccf92c6')
|
||||
def test_cinder_transfer_list(self):
|
||||
transfer_list = self.parser.listing(self.cinder('transfer-list'))
|
||||
self.assertTableStruct(transfer_list, ['ID', 'Volume ID', 'Name'])
|
||||
|
||||
@test.idempotent_id('0976dea8-14f3-45a9-8495-3617fc4fbb13')
|
||||
def test_cinder_bash_completion(self):
|
||||
self.cinder('bash-completion')
|
||||
|
||||
@test.idempotent_id('b7c00361-be80-4512-8735-5f98fc54f2a9')
|
||||
def test_cinder_qos_list(self):
|
||||
qos_list = self.parser.listing(self.cinder('qos-list'))
|
||||
self.assertTableStruct(qos_list, ['ID', 'Name', 'Consumer', 'specs'])
|
||||
|
||||
@test.idempotent_id('2e92dc6e-22b5-4d94-abfc-b543b0c50a89')
|
||||
def test_cinder_encryption_type_list(self):
|
||||
encrypt_list = self.parser.listing(self.cinder('encryption-type-list'))
|
||||
self.assertTableStruct(encrypt_list, ['Volume Type ID', 'Provider',
|
||||
'Cipher', 'Key Size',
|
||||
'Control Location'])
|
||||
|
||||
@test.idempotent_id('0ee6cb4c-8de6-4811-a7be-7f4bb75b80cc')
|
||||
def test_admin_help(self):
|
||||
help_text = self.cinder('help')
|
||||
lines = help_text.split('\n')
|
||||
self.assertFirstLineStartsWith(lines, 'usage: cinder')
|
||||
|
||||
commands = []
|
||||
cmds_start = lines.index('Positional arguments:')
|
||||
cmds_end = lines.index('Optional arguments:')
|
||||
command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)')
|
||||
for line in lines[cmds_start:cmds_end]:
|
||||
match = command_pattern.match(line)
|
||||
if match:
|
||||
commands.append(match.group(1))
|
||||
commands = set(commands)
|
||||
wanted_commands = set(('absolute-limits', 'list', 'help',
|
||||
'quota-show', 'type-list', 'snapshot-list'))
|
||||
self.assertFalse(wanted_commands - commands)
|
||||
|
||||
# Optional arguments:
|
||||
|
||||
@test.idempotent_id('2fd6f530-183c-4bda-8918-1e59e36c26b9')
|
||||
def test_cinder_version(self):
|
||||
self.cinder('', flags='--version')
|
||||
|
||||
@test.idempotent_id('306bac51-c443-4426-a6cf-583a953fcd68')
|
||||
def test_cinder_debug_list(self):
|
||||
self.cinder('list', flags='--debug')
|
||||
|
||||
@test.idempotent_id('6d97fcd2-5dd1-429d-af70-030c949d86cd')
|
||||
def test_cinder_retries_list(self):
|
||||
self.cinder('list', flags='--retries 3')
|
||||
|
||||
@test.idempotent_id('95a2850c-35b4-4159-bb93-51647a5ad232')
|
||||
def test_cinder_region_list(self):
|
||||
region = CONF.volume.region
|
||||
if not region:
|
||||
region = CONF.identity.region
|
||||
self.cinder('list', flags='--os-region-name ' + region)
|
@ -1091,25 +1091,6 @@ BaremetalGroup = [
|
||||
help="Timeout for unprovisioning an Ironic node.")
|
||||
]
|
||||
|
||||
cli_group = cfg.OptGroup(name='cli', title="cli Configuration Options")
|
||||
|
||||
CLIGroup = [
|
||||
cfg.BoolOpt('enabled',
|
||||
default=True,
|
||||
help="enable cli tests"),
|
||||
cfg.StrOpt('cli_dir',
|
||||
default='/usr/local/bin',
|
||||
help="directory where python client binaries are located"),
|
||||
cfg.BoolOpt('has_manage',
|
||||
default=True,
|
||||
help=("Whether the tempest run location has access to the "
|
||||
"*-manage commands. In a pure blackbox environment "
|
||||
"it will not.")),
|
||||
cfg.IntOpt('timeout',
|
||||
default=15,
|
||||
help="Number of seconds to wait on a CLI timeout"),
|
||||
]
|
||||
|
||||
negative_group = cfg.OptGroup(name='negative', title="Negative Test Options")
|
||||
|
||||
NegativeGroup = [
|
||||
@ -1148,7 +1129,6 @@ _opts = [
|
||||
(debug_group, DebugGroup),
|
||||
(baremetal_group, BaremetalGroup),
|
||||
(input_scenario_group, InputScenarioGroup),
|
||||
(cli_group, CLIGroup),
|
||||
(negative_group, NegativeGroup)
|
||||
]
|
||||
|
||||
@ -1212,7 +1192,6 @@ class TempestConfigPrivate(object):
|
||||
self.debug = _CONF.debug
|
||||
self.baremetal = _CONF.baremetal
|
||||
self.input_scenario = _CONF['input-scenario']
|
||||
self.cli = _CONF.cli
|
||||
self.negative = _CONF.negative
|
||||
_CONF.set_default('domain_name', self.identity.admin_domain_name,
|
||||
group='identity')
|
||||
|
@ -25,7 +25,7 @@ def load_tests(loader, tests, pattern):
|
||||
suite = unittest.TestSuite()
|
||||
base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
|
||||
base_path = os.path.split(base_path)[0]
|
||||
for test_dir in ['./tempest/api', './tempest/cli', './tempest/scenario',
|
||||
for test_dir in ['./tempest/api', './tempest/scenario',
|
||||
'./tempest/thirdparty']:
|
||||
if not pattern:
|
||||
suite.addTests(loader.discover(test_dir, top_level_dir=base_path))
|
||||
|
@ -1,68 +0,0 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 mock
|
||||
from tempest_lib.cli import base as cli_base
|
||||
import testtools
|
||||
|
||||
from tempest import cli
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.tests import base
|
||||
from tempest.tests import fake_config
|
||||
|
||||
|
||||
class TestMinClientVersion(base.TestCase):
|
||||
"""Tests for the min_client_version decorator.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestMinClientVersion, self).setUp()
|
||||
self.useFixture(fake_config.ConfigFixture())
|
||||
self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
|
||||
|
||||
def _test_min_version(self, required, installed, expect_skip):
|
||||
|
||||
@cli.min_client_version(client='nova', version=required)
|
||||
def fake(self, expect_skip):
|
||||
if expect_skip:
|
||||
# If we got here, the decorator didn't raise a skipException as
|
||||
# expected so we need to fail.
|
||||
self.fail('Should not have gotten past the decorator.')
|
||||
|
||||
with mock.patch.object(cli_base, 'execute',
|
||||
return_value=installed) as mock_cmd:
|
||||
if expect_skip:
|
||||
self.assertRaises(testtools.TestCase.skipException, fake,
|
||||
self, expect_skip)
|
||||
else:
|
||||
fake(self, expect_skip)
|
||||
mock_cmd.assert_called_once_with('nova', '', params='--version',
|
||||
cli_dir='/usr/local/bin',
|
||||
merge_stderr=True)
|
||||
|
||||
def test_min_client_version(self):
|
||||
# required, installed, expect_skip
|
||||
cases = (('2.17.0', '2.17.0', False),
|
||||
('2.17.0', '2.18.0', False),
|
||||
('2.18.0', '2.17.0', True))
|
||||
|
||||
for case in cases:
|
||||
self._test_min_version(*case)
|
||||
|
||||
@mock.patch.object(cli_base, 'execute', return_value=' ')
|
||||
def test_check_client_version_empty_output(self, mock_execute):
|
||||
# Tests that an exception is raised if the command output is empty.
|
||||
self.assertRaises(exceptions.TempestException,
|
||||
cli.check_client_version, 'nova', '2.18.0')
|
4
tox.ini
4
tox.ini
@ -47,7 +47,7 @@ deps = {[tempestenv]deps}
|
||||
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
|
||||
bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
|
||||
|
||||
[testenv:full-serial]
|
||||
sitepackages = {[tempestenv]sitepackages}
|
||||
@ -57,7 +57,7 @@ deps = {[tempestenv]deps}
|
||||
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
|
||||
commands =
|
||||
find . -type f -name "*.pyc" -delete
|
||||
bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
|
||||
bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
|
||||
|
||||
[testenv:heat-slow]
|
||||
sitepackages = {[tempestenv]sitepackages}
|
||||
|
Loading…
Reference in New Issue
Block a user