diff --git a/ironicclient/tests/functional/base.py b/ironicclient/tests/functional/base.py index 1ea3d4dd1..a0250ab48 100644 --- a/ironicclient/tests/functional/base.py +++ b/ironicclient/tests/functional/base.py @@ -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) diff --git a/ironicclient/tests/functional/test_ironicclient.py b/ironicclient/tests/functional/test_ironicclient.py deleted file mode 100644 index 18d379bc2..000000000 --- a/ironicclient/tests/functional/test_ironicclient.py +++ /dev/null @@ -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']) diff --git a/ironicclient/tests/functional/test_node.py b/ironicclient/tests/functional/test_node.py new file mode 100644 index 000000000..3894b7da3 --- /dev/null +++ b/ironicclient/tests/functional/test_node.py @@ -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) diff --git a/ironicclient/tests/functional/test_table_structure.py b/ironicclient/tests/functional/test_table_structure.py index 6efef526e..a617cef16 100644 --- a/ironicclient/tests/functional/test_table_structure.py +++ b/ironicclient/tests/functional/test_table_structure.py @@ -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) diff --git a/ironicclient/tests/functional/utils.py b/ironicclient/tests/functional/utils.py new file mode 100644 index 000000000..d70a357bf --- /dev/null +++ b/ironicclient/tests/functional/utils.py @@ -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)