Add sanity tests for testing actions with Node
Smoke tests for the Ironic CLI commands which checks basic actions with node command like create, update, delete etc. Add get_table_headers() method that allows to get table header from cmd output. Add 'utils' module Table structure tests have been updated. Change-Id: If60b3720e9ce9a6a6c7e35880bf3f9c661d6f259 Implements: blueprint ironicclient-functional-tests
This commit is contained in:
parent
65f2b18c33
commit
41d0f8ff83
@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2015 Mirantis, 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
|
||||
@ -12,16 +14,18 @@
|
||||
|
||||
import os
|
||||
|
||||
import six
|
||||
import six.moves.configparser as config_parser
|
||||
from tempest_lib.cli import base
|
||||
from tempest_lib import exceptions
|
||||
|
||||
import ironicclient.tests.functional.utils as utils
|
||||
|
||||
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()
|
||||
@ -84,7 +88,7 @@ class FunctionalTestBase(base.ClientTestBase):
|
||||
return cli_flags
|
||||
|
||||
def _cmd_no_auth(self, cmd, action, flags='', params=''):
|
||||
"""Executes given command with noauth attributes.
|
||||
"""Execute given command with noauth attributes.
|
||||
|
||||
:param cmd: command to be executed
|
||||
:type cmd: string
|
||||
@ -103,8 +107,8 @@ class FunctionalTestBase(base.ClientTestBase):
|
||||
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.
|
||||
def _ironic(self, action, flags='', params=''):
|
||||
"""Execute ironic command for the given action.
|
||||
|
||||
:param action: the cli command to run using Ironic
|
||||
:type action: string
|
||||
@ -114,48 +118,124 @@ class FunctionalTestBase(base.ClientTestBase):
|
||||
: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)
|
||||
try:
|
||||
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)
|
||||
except exceptions.CommandFailed as e:
|
||||
self.fail(e)
|
||||
|
||||
def _try_delete_node(self, node_id):
|
||||
if node_id in self.ironic('node-list'):
|
||||
self.ironic('node-delete', params=node_id)
|
||||
def ironic(self, action, flags='', params=''):
|
||||
"""Return parsed list of dicts with basic item info.
|
||||
|
||||
def get_dict_from_output(self, output):
|
||||
"""Create a dictionary from an output
|
||||
|
||||
:param output: the output of the cmd
|
||||
: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
|
||||
"""
|
||||
obj = {}
|
||||
items = self.parser.listing(output)
|
||||
for item in items:
|
||||
obj[item['Property']] = six.text_type(item['Value'])
|
||||
return obj
|
||||
output = self._ironic(action=action, flags=flags, params=params)
|
||||
return self.parser.listing(output)
|
||||
|
||||
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 get_table_headers(self, action, flags='', params=''):
|
||||
output = self._ironic(action=action, flags=flags, params=params)
|
||||
table = self.parser.table(output)
|
||||
return table['headers']
|
||||
|
||||
def assertTableHeaders(self, field_names, output_lines):
|
||||
"""Verify that output table has headers item listed in field_names.
|
||||
def assertTableHeaders(self, field_names, table_headers):
|
||||
"""Assert that field_names and table_headers are equal.
|
||||
|
||||
:param field_names: field names from the output table of the cmd
|
||||
:param output_lines: output table from cmd
|
||||
:param table_heades: table headers output from cmd
|
||||
"""
|
||||
table = self.parser.table(output_lines)
|
||||
headers = table['headers']
|
||||
for field in field_names:
|
||||
self.assertIn(field, headers)
|
||||
self.assertEqual(sorted(field_names), sorted(table_headers))
|
||||
|
||||
def assertNodeDeleted(self, node_id):
|
||||
"""Verify that there isn't node with given id.
|
||||
def assertNodeStates(self, node_show, node_show_states):
|
||||
"""Assert that node_show_states output corresponds to node_show output.
|
||||
|
||||
:param node_id: node id to verify
|
||||
:param node_show: output from node-show cmd
|
||||
:param node_show_states: output from node-show-states cmd
|
||||
"""
|
||||
self.assertNotIn(node_id, self.ironic('node-list'))
|
||||
for key in node_show_states.keys():
|
||||
self.assertEqual(node_show_states[key], node_show[key])
|
||||
|
||||
def assertNodeValidate(self, node_validate):
|
||||
"""Assert that node_validate is able to validate all interfaces present.
|
||||
|
||||
:param node_validate: output from node-validate cmd
|
||||
"""
|
||||
self.assertNotIn('False', [x['Result'] for x in node_validate])
|
||||
|
||||
def delete_node(self, node_id):
|
||||
"""Delete node method works only with fake driver.
|
||||
|
||||
:param node_id: node uuid
|
||||
"""
|
||||
node_list = self.list_nodes()
|
||||
|
||||
if utils.get_object(node_list, node_id):
|
||||
node_show = self.show_node(node_id)
|
||||
if node_show['provision_state'] != 'available':
|
||||
self.ironic('node-set-provision-state',
|
||||
params='{0} deleted'.format(node_id))
|
||||
if node_show['power_state'] not in ('None', 'off'):
|
||||
self.ironic('node-set-power-state',
|
||||
params='{0} off'.format(node_id))
|
||||
self.ironic('node-delete', params=node_id)
|
||||
|
||||
node_list_uuid = self.get_nodes_uuids_from_node_list()
|
||||
if node_id in node_list_uuid:
|
||||
self.fail('Ironic node {0} has not been deleted!'
|
||||
.format(node_id))
|
||||
|
||||
def create_node(self, driver='fake', params=''):
|
||||
node = self.ironic('node-create',
|
||||
params='--driver {0} {1}'.format(driver, params))
|
||||
|
||||
if not node:
|
||||
self.fail('Ironic node has not been created!')
|
||||
|
||||
node = utils.get_dict_from_output(node)
|
||||
self.addCleanup(self.delete_node, node['uuid'])
|
||||
return node
|
||||
|
||||
def show_node(self, node_id, params=''):
|
||||
node_show = self.ironic('node-show',
|
||||
params='{0} {1}'.format(node_id, params))
|
||||
return utils.get_dict_from_output(node_show)
|
||||
|
||||
def list_nodes(self, params=''):
|
||||
return self.ironic('node-list', params=params)
|
||||
|
||||
def update_node(self, node_id, params):
|
||||
updated_node = self.ironic('node-update',
|
||||
params='{0} {1}'.format(node_id, params))
|
||||
return utils.get_dict_from_output(updated_node)
|
||||
|
||||
def get_nodes_uuids_from_node_list(self):
|
||||
node_list = self.list_nodes()
|
||||
return [x['UUID'] for x in node_list]
|
||||
|
||||
def show_node_states(self, node_id):
|
||||
show_node_states = self.ironic('node-show-states', params=node_id)
|
||||
return utils.get_dict_from_output(show_node_states)
|
||||
|
||||
def set_node_maintenance(self, node_id, maintenance_mode, params=''):
|
||||
self.ironic(
|
||||
'node-set-maintenance',
|
||||
params='{0} {1} {2}'.format(node_id, maintenance_mode, params))
|
||||
|
||||
def set_node_power_state(self, node_id, power_state):
|
||||
self.ironic('node-set-power-state',
|
||||
params='{0} {1}'.format(node_id, power_state))
|
||||
|
||||
def set_node_provision_state(self, node_id, provision_state, params=''):
|
||||
self.ironic('node-set-provision-state',
|
||||
params='{0} {1} {2}'
|
||||
.format(node_id, provision_state, params))
|
||||
|
||||
def validate_node(self, node_id):
|
||||
return self.ironic('node-validate', params=node_id)
|
||||
|
@ -1,27 +0,0 @@
|
||||
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||
# 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 ironicclient.tests.functional import base
|
||||
|
||||
|
||||
class TestIronicClient(base.FunctionalTestBase):
|
||||
|
||||
def test_node_create_get_delete(self):
|
||||
node = self.create_node()
|
||||
got = self.ironic('node-show', params=node['uuid'])
|
||||
expected_node = self.get_dict_from_output(got)
|
||||
self.assertEqual(expected_node['uuid'], node['uuid'])
|
||||
self.ironic('node-delete', params=node['uuid'])
|
||||
self.assertNodeDeleted(node['uuid'])
|
180
ironicclient/tests/functional/test_node.py
Normal file
180
ironicclient/tests/functional/test_node.py
Normal file
@ -0,0 +1,180 @@
|
||||
# Copyright (c) 2015 Mirantis, 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 ironicclient.tests.functional import base
|
||||
import ironicclient.tests.functional.utils as utils
|
||||
|
||||
|
||||
class NodeSanityTestIronicClient(base.FunctionalTestBase):
|
||||
"""Sanity tests for testing actions with Node.
|
||||
|
||||
Smoke test for the Ironic CLI commands which checks basic actions with
|
||||
node command like create, delete etc.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(NodeSanityTestIronicClient, self).setUp()
|
||||
self.node = self.create_node()
|
||||
|
||||
def test_node_create(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that node has been successfully created
|
||||
"""
|
||||
self.assertIn(self.node['uuid'], self.get_nodes_uuids_from_node_list())
|
||||
|
||||
def test_node_show(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that created node UUID equals to the one present
|
||||
in node-show output
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
self.assertEqual(self.node['uuid'], node_show['uuid'])
|
||||
|
||||
def test_node_delete(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that it was created
|
||||
3) delete node
|
||||
4) check that node has been successfully deleted
|
||||
"""
|
||||
self.assertIn(self.node['uuid'], self.get_nodes_uuids_from_node_list())
|
||||
self.delete_node(self.node['uuid'])
|
||||
self.assertNotIn(self.node['uuid'],
|
||||
self.get_nodes_uuids_from_node_list())
|
||||
|
||||
def test_node_update(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) update node name
|
||||
3) check that node name has been successfully updated
|
||||
"""
|
||||
node_name = utils.generate_name('test')
|
||||
updated_node = self.update_node(self.node['uuid'],
|
||||
'add name={0}'.format(node_name))
|
||||
self.assertEqual(node_name, updated_node['name'])
|
||||
|
||||
def test_node_set_console_mode(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that console_enabled is False
|
||||
3) set node console mode to True
|
||||
4) check that node console mode has been successfully updated
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('False', node_show['console_enabled'])
|
||||
|
||||
self.ironic('node-set-console-mode',
|
||||
params='{0} true'.format(self.node['uuid']))
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('True', node_show['console_enabled'])
|
||||
|
||||
def test_node_get_console(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check console mode using node-show
|
||||
3) get console mode using node-get-console
|
||||
4) check that node-get-console value equals node-show value
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
node_get = self.ironic('node-get-console', params=self.node['uuid'])
|
||||
node_get = utils.get_dict_from_output(node_get)
|
||||
|
||||
self.assertEqual(node_show['console_enabled'],
|
||||
node_get['console_enabled'])
|
||||
|
||||
def test_node_set_maintenance(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that maintenance is False
|
||||
3) put node to maintenance
|
||||
4) check that node is in maintenance
|
||||
5) check that maintenance reason has been successfully updated
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('False', node_show['maintenance'])
|
||||
|
||||
self.set_node_maintenance(
|
||||
self.node['uuid'],
|
||||
"true --reason 'Testing node-set power state command'")
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('True', node_show['maintenance'])
|
||||
self.assertEqual('Testing node-set power state command',
|
||||
node_show['maintenance_reason'])
|
||||
|
||||
def test_node_set_power_state(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that power state is None
|
||||
3) set power state to On
|
||||
4) check that power state has been changed successfully
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('None', node_show['power_state'])
|
||||
|
||||
self.set_node_power_state(self.node['uuid'], "off")
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('power off', node_show['power_state'])
|
||||
|
||||
def test_node_set_provision_state(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that provision state is 'available'
|
||||
3) set new provision state to the node
|
||||
4) check that provision state has been updated successfully
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('available', node_show['provision_state'])
|
||||
|
||||
self.set_node_provision_state(self.node['uuid'], 'active')
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
|
||||
self.assertEqual('active', node_show['provision_state'])
|
||||
|
||||
def test_node_validate(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) validate node
|
||||
"""
|
||||
node_validate = self.validate_node(self.node['uuid'])
|
||||
self.assertNodeValidate(node_validate)
|
||||
|
||||
def test_show_node_states(self):
|
||||
"""Test steps:
|
||||
|
||||
1) create node
|
||||
2) check that states returned by node-show and node-show-states
|
||||
are the same
|
||||
"""
|
||||
node_show = self.show_node(self.node['uuid'])
|
||||
show_node_states = self.show_node_states(self.node['uuid'])
|
||||
self.assertNodeStates(node_show, show_node_states)
|
@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2015 Mirantis, 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
|
||||
@ -21,40 +23,40 @@ class TableStructureIronicCLITests(base.FunctionalTestBase):
|
||||
"""
|
||||
|
||||
def test_chassis_list_table_structure(self):
|
||||
"""Test scenario:
|
||||
"""Test steps:
|
||||
|
||||
1) get chassis-list
|
||||
2) check table structure
|
||||
"""
|
||||
chassis_list = self.ironic('chassis-list')
|
||||
self.assertTableHeaders(['Description', 'UUID'], chassis_list)
|
||||
chassis_list_header = self.get_table_headers('chassis-list')
|
||||
self.assertTableHeaders(['Description', 'UUID'], chassis_list_header)
|
||||
|
||||
def test_node_list_table_structure(self):
|
||||
"""Test scenario:
|
||||
"""Test steps:
|
||||
|
||||
1) get node-list
|
||||
2) check table structure
|
||||
"""
|
||||
node_list = self.ironic('node-list')
|
||||
node_list_header = self.get_table_headers('node-list')
|
||||
self.assertTableHeaders(['UUID', 'Name', 'Instance UUID',
|
||||
'Power State', 'Provisioning State',
|
||||
'Maintenance'], node_list)
|
||||
'Maintenance'], node_list_header)
|
||||
|
||||
def test_port_list_table_structure(self):
|
||||
"""Test scenario:
|
||||
"""Test steps:
|
||||
|
||||
1) get port-list
|
||||
2) check table structure
|
||||
"""
|
||||
port_list = self.ironic('port-list')
|
||||
self.assertTableHeaders(['UUID', 'Address'], port_list)
|
||||
port_list_header = self.get_table_headers('port-list')
|
||||
self.assertTableHeaders(['UUID', 'Address'], port_list_header)
|
||||
|
||||
def test_driver_list_table_structure(self):
|
||||
"""Test scenario:
|
||||
"""Test steps:
|
||||
|
||||
1) get driver-list
|
||||
2) check table structure
|
||||
"""
|
||||
driver_list = self.ironic('driver-list')
|
||||
driver_list_header = self.get_table_headers('driver-list')
|
||||
self.assertTableHeaders(['Supported driver(s)', 'Active host(s)'],
|
||||
driver_list)
|
||||
driver_list_header)
|
||||
|
48
ironicclient/tests/functional/utils.py
Normal file
48
ironicclient/tests/functional/utils.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Copyright (c) 2015 Mirantis, 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.
|
||||
|
||||
import uuid
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def get_dict_from_output(output):
|
||||
"""Parce list of dictonaries, return dictonary.
|
||||
|
||||
:param output: list of dictonaries
|
||||
"""
|
||||
obj = {}
|
||||
for item in output:
|
||||
obj[item['Property']] = six.text_type(item['Value'])
|
||||
return obj
|
||||
|
||||
|
||||
def get_object(object_list, object_value):
|
||||
""""Get Ironic object by value from list of Ironic objects.
|
||||
|
||||
:param object_list: the output of the cmd
|
||||
:param object_value: value to get
|
||||
"""
|
||||
for obj in object_list:
|
||||
if object_value in obj.values():
|
||||
return obj
|
||||
|
||||
|
||||
def generate_name(prefix='test'):
|
||||
"""Generate name for objects.
|
||||
|
||||
:param prefix: prefix of the generated name
|
||||
"""
|
||||
suffix = uuid.uuid4().hex
|
||||
return "{0}-{1}".format(prefix, suffix)
|
Loading…
x
Reference in New Issue
Block a user