Introduce tempest-lib to functional tests
Functional test environment uses ironicclient api directly(client managers), this approach has some cons - it doesn't include cli code. So it would be better to use tempest-lib for this. It makes call to ironic with Popen, and has parsing tools. This change doesn't add new test cases, some helper methods have been added. blueprint ironicclient-functional-tests Change-Id: Iee426c65ee227e8b7c32ac94f6cfc1001f94e112
This commit is contained in:

committed by
Sergey Turivnyi

parent
5956624b8f
commit
1ced89b9e9
161
ironicclient/tests/functional/base.py
Normal file
161
ironicclient/tests/functional/base.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
import six
|
||||||
|
import six.moves.configparser as config_parser
|
||||||
|
from tempest_lib.cli import base
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'test.conf')
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionalTestBase(base.ClientTestBase):
|
||||||
|
"""Ironic base class, calls to ironicclient."""
|
||||||
|
def setUp(self):
|
||||||
|
super(FunctionalTestBase, self).setUp()
|
||||||
|
self.client = self._get_clients()
|
||||||
|
|
||||||
|
def _get_clients(self):
|
||||||
|
# NOTE(aarefiev): {toxinidir} is a current working directory, so
|
||||||
|
# the tox env path is {toxinidir}/.tox
|
||||||
|
cli_dir = os.path.join(os.path.abspath('.'), '.tox/functional/bin')
|
||||||
|
|
||||||
|
config = self._get_config()
|
||||||
|
if config.get('os_auth_url'):
|
||||||
|
client = base.CLIClient(cli_dir=cli_dir,
|
||||||
|
username=config['os_username'],
|
||||||
|
password=config['os_password'],
|
||||||
|
tenant_name=config['os_tenant_name'],
|
||||||
|
uri=config['os_auth_url'])
|
||||||
|
else:
|
||||||
|
self.ironic_url = config['ironic_url']
|
||||||
|
self.os_auth_token = config['os_auth_token']
|
||||||
|
client = base.CLIClient(cli_dir=cli_dir,
|
||||||
|
ironic_url=self.ironic_url,
|
||||||
|
os_auth_token=self.os_auth_token)
|
||||||
|
return client
|
||||||
|
|
||||||
|
def _get_config(self):
|
||||||
|
config_file = os.environ.get('IRONICCLIENT_TEST_CONFIG',
|
||||||
|
DEFAULT_CONFIG_FILE)
|
||||||
|
config = config_parser.SafeConfigParser()
|
||||||
|
if not config.read(config_file):
|
||||||
|
self.skipTest('Skipping, no test config found @ %s' % config_file)
|
||||||
|
try:
|
||||||
|
auth_strategy = config.get('functional', 'auth_strategy')
|
||||||
|
except config_parser.NoOptionError:
|
||||||
|
auth_strategy = 'keystone'
|
||||||
|
if auth_strategy not in ['keystone', 'noauth']:
|
||||||
|
raise self.fail(
|
||||||
|
'Invalid auth type specified: %s in functional must be '
|
||||||
|
'one of: [keystone, noauth]' % auth_strategy)
|
||||||
|
|
||||||
|
conf_settings = []
|
||||||
|
if auth_strategy == 'keystone':
|
||||||
|
conf_settings += ['os_auth_url', 'os_username',
|
||||||
|
'os_password', 'os_tenant_name']
|
||||||
|
else:
|
||||||
|
conf_settings += ['os_auth_token', 'ironic_url']
|
||||||
|
|
||||||
|
cli_flags = {}
|
||||||
|
missing = []
|
||||||
|
for c in conf_settings:
|
||||||
|
try:
|
||||||
|
cli_flags[c] = config.get('functional', c)
|
||||||
|
except config_parser.NoOptionError:
|
||||||
|
missing.append(c)
|
||||||
|
if missing:
|
||||||
|
self.fail('Missing required setting in test.conf (%(conf)s) for '
|
||||||
|
'auth_strategy=%(auth)s: %(missing)s' %
|
||||||
|
{'conf': config_file,
|
||||||
|
'auth': auth_strategy,
|
||||||
|
'missing': ','.join(missing)})
|
||||||
|
return cli_flags
|
||||||
|
|
||||||
|
def _cmd_no_auth(self, cmd, action, flags='', params=''):
|
||||||
|
"""Executes given command with noauth attributes.
|
||||||
|
|
||||||
|
:param cmd: command to be executed
|
||||||
|
:type cmd: string
|
||||||
|
:param action: command on cli to run
|
||||||
|
:type action: string
|
||||||
|
:param flags: optional cli flags to use
|
||||||
|
:type flags: string
|
||||||
|
:param params: optional positional args to use
|
||||||
|
:type params: string
|
||||||
|
"""
|
||||||
|
flags = ('--os_auth_token %(token)s --ironic_url %(url)s %(flags)s'
|
||||||
|
%
|
||||||
|
{'token': self.os_auth_token,
|
||||||
|
'url': self.ironic_url,
|
||||||
|
'flags': flags})
|
||||||
|
return base.execute(cmd, action, flags, params,
|
||||||
|
cli_dir=self.client.cli_dir)
|
||||||
|
|
||||||
|
def ironic(self, action, flags='', params=''):
|
||||||
|
"""Executes ironic command for the given action.
|
||||||
|
|
||||||
|
:param action: the cli command to run using Ironic
|
||||||
|
:type action: string
|
||||||
|
:param flags: any optional cli flags to use
|
||||||
|
:type flags: string
|
||||||
|
:param params: any optional positional args to use
|
||||||
|
:type params: string
|
||||||
|
"""
|
||||||
|
flags += ' --os-endpoint-type publicURL'
|
||||||
|
if hasattr(self, 'os_auth_token'):
|
||||||
|
return self._cmd_no_auth('ironic', action, flags, params)
|
||||||
|
else:
|
||||||
|
return self.client.cmd_with_auth('ironic', action, flags, params)
|
||||||
|
|
||||||
|
def _try_delete_node(self, node_id):
|
||||||
|
if node_id in self.ironic('node-list'):
|
||||||
|
self.ironic('node-delete', params=node_id)
|
||||||
|
|
||||||
|
def get_dict_from_output(self, output):
|
||||||
|
"""Create a dictionary from an output
|
||||||
|
|
||||||
|
:param output: the output of the cmd
|
||||||
|
"""
|
||||||
|
obj = {}
|
||||||
|
items = self.parser.listing(output)
|
||||||
|
for item in items:
|
||||||
|
obj[item['Property']] = six.text_type(item['Value'])
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def create_node(self, params=''):
|
||||||
|
if not any(dr in params for dr in ('--driver', '-d')):
|
||||||
|
params += '--driver fake'
|
||||||
|
node = self.ironic('node-create', params=params)
|
||||||
|
node = self.get_dict_from_output(node)
|
||||||
|
self.addCleanup(self._try_delete_node, node['uuid'])
|
||||||
|
return node
|
||||||
|
|
||||||
|
def assertTableHeaders(self, field_names, output_lines):
|
||||||
|
"""Verify that output table has headers item listed in field_names.
|
||||||
|
|
||||||
|
:param field_names: field names from the output table of the cmd
|
||||||
|
:param output_lines: output table from cmd
|
||||||
|
"""
|
||||||
|
table = self.parser.table(output_lines)
|
||||||
|
headers = table['headers']
|
||||||
|
for field in field_names:
|
||||||
|
self.assertIn(field, headers)
|
||||||
|
|
||||||
|
def assertNodeDeleted(self, node_id):
|
||||||
|
"""Verify that there isn't node with given id.
|
||||||
|
|
||||||
|
:param node_id: node id to verify
|
||||||
|
"""
|
||||||
|
self.assertNotIn(node_id, self.ironic('node-list'))
|
@@ -13,78 +13,20 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import os
|
from ironicclient.tests.functional import base
|
||||||
|
|
||||||
import six.moves.configparser as config_parser
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
import ironicclient
|
|
||||||
from ironicclient import exc
|
|
||||||
|
|
||||||
DEFAULT_CONFIG = os.path.join(os.path.dirname(__file__), 'test.conf')
|
|
||||||
|
|
||||||
|
|
||||||
class TestIronicClient(testtools.TestCase):
|
class TestIronicClient(base.FunctionalTestBase):
|
||||||
def setUp(self):
|
|
||||||
super(TestIronicClient, self).setUp()
|
|
||||||
self.client = ironicclient.client.get_client(**self._get_config())
|
|
||||||
|
|
||||||
def _get_config(self):
|
|
||||||
config_file = os.environ.get('IRONICCLIENT_TEST_CONFIG',
|
|
||||||
DEFAULT_CONFIG)
|
|
||||||
config = config_parser.SafeConfigParser()
|
|
||||||
if not config.read(config_file):
|
|
||||||
self.skipTest('Skipping, no test config found @ %s' % config_file)
|
|
||||||
try:
|
|
||||||
auth_strategy = config.get('functional', 'auth_strategy')
|
|
||||||
except config_parser.NoOptionError:
|
|
||||||
auth_strategy = 'keystone'
|
|
||||||
if auth_strategy not in ['keystone', 'noauth']:
|
|
||||||
raise self.fail(
|
|
||||||
'Invalid auth type specified in functional must be one of: '
|
|
||||||
'keystone, noauth')
|
|
||||||
out = {}
|
|
||||||
conf_settings = ['api_version']
|
|
||||||
if auth_strategy == 'keystone':
|
|
||||||
conf_settings += ['os_auth_url', 'os_username',
|
|
||||||
'os_password', 'os_tenant_name']
|
|
||||||
|
|
||||||
else:
|
|
||||||
conf_settings += ['os_auth_token', 'ironic_url']
|
|
||||||
|
|
||||||
for c in conf_settings:
|
|
||||||
try:
|
|
||||||
out[c] = config.get('functional', c)
|
|
||||||
except config_parser.NoOptionError:
|
|
||||||
out[c] = None
|
|
||||||
missing = [k for k, v in out.items() if not v]
|
|
||||||
if missing:
|
|
||||||
self.fail('Missing required setting in test.conf (%s) for '
|
|
||||||
'auth_strategy=%s: %s' %
|
|
||||||
(config_file, auth_strategy, ','.join(missing)))
|
|
||||||
return out
|
|
||||||
|
|
||||||
def _try_delete_resource(self, resource, id):
|
|
||||||
mgr = getattr(self.client, resource)
|
|
||||||
try:
|
|
||||||
mgr.delete(id)
|
|
||||||
except exc.NotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _create_node(self, **kwargs):
|
|
||||||
if 'driver' not in kwargs:
|
|
||||||
kwargs['driver'] = 'fake'
|
|
||||||
node = self.client.node.create(**kwargs)
|
|
||||||
self.addCleanup(self._try_delete_resource, 'node', node.uuid)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def test_node_list(self):
|
def test_node_list(self):
|
||||||
self.assertTrue(isinstance(self.client.node.list(), list))
|
list = self.ironic('node-list')
|
||||||
|
self.assertTableHeaders(['UUID', 'Name', 'Instance UUID',
|
||||||
|
'Power State', 'Provisioning State',
|
||||||
|
'Maintenance'], list)
|
||||||
|
|
||||||
def test_node_create_get_delete(self):
|
def test_node_create_get_delete(self):
|
||||||
node = self._create_node()
|
node = self.create_node()
|
||||||
self.assertTrue(isinstance(node, ironicclient.v1.node.Node))
|
got = self.ironic('node-show', params=node['uuid'])
|
||||||
got = self.client.node.get(node.uuid)
|
expected_node = self.get_dict_from_output(got)
|
||||||
self.assertTrue(isinstance(got, ironicclient.v1.node.Node))
|
self.assertEqual(expected_node['uuid'], node['uuid'])
|
||||||
self.client.node.delete(node.uuid)
|
self.ironic('node-delete', params=node['uuid'])
|
||||||
self.assertRaises(exc.NotFound, self.client.node.get, node.uuid)
|
self.assertNodeDeleted(node['uuid'])
|
||||||
|
@@ -14,3 +14,4 @@ python-subunit>=0.0.18
|
|||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testtools>=1.4.0
|
testtools>=1.4.0
|
||||||
|
tempest-lib>=0.10.0
|
||||||
|
Reference in New Issue
Block a user