diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py index 26c37e0..3b27615 100644 --- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py +++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py @@ -601,6 +601,19 @@ class BaremetalClient(base.BaremetalClient): return self._put_request('nodes/%s/states/power' % node_uuid, target) + @base.handle_errors + def set_node_state(self, node_uuid, state, target): + """Set state for the specified node. + + :param node_uuid: The unique identifier of the node. + :param state: The desired state to set. + :param target: The target state + + """ + target = {'target': target} + return self._put_request('nodes/%s/states/%s' % (node_uuid, state), + target) + @base.handle_errors def set_node_provision_state(self, node_uuid, state, configdrive=None, clean_steps=None, rescue_password=None): @@ -680,6 +693,38 @@ class BaremetalClient(base.BaremetalClient): self.expected_success(http_client.OK, resp.status) return body + @base.handle_errors + def set_node_indicator_state(self, node_uuid, component, ind_ident, state): + """Get the current indicator state + + :param node_uuid: The unique identifier of the node. + :param component: The Bare Metal node component. + :param ind_ident: The indicator of a Bare Metal component. + :param state: The state of an indicator of the component of the node. + Possible values are: OFF, ON, BLINKING or UNKNOWN. + + """ + resp, body = self._put_request('nodes/%s/management/indicators/%s/%s' + % (node_uuid, component, ind_ident), + state) + self.expected_success(http_client.OK, resp.status) + return body + + @base.handle_errors + def get_node_indicator_state(self, node_uuid, component, ind_ident): + """Get the current indicator state + + :param node_uuid: The unique identifier of the node. + :param component: The Bare Metal node component. + :param ind_ident: The indicator of a Bare Metal component. + + """ + path = 'nodes/%s/management/indicators/%s/%s' % (node_uuid, component, + ind_ident) + resp, body = self._list_request(path) + self.expected_success(http_client.OK, resp.status) + return body + @base.handle_errors def get_node_supported_boot_devices(self, node_uuid): """Get the supported boot devices of the specified node. @@ -864,3 +909,39 @@ class BaremetalClient(base.BaremetalClient): """ return self._delete_request('allocations', allocation_ident) + + @base.handle_errors + def list_node_history(self, node_uuid): + """List history entries for a node. + + :param node_uuid: The unique identifier of the node. + """ + return self._list_request('/nodes/%s/history' % node_uuid) + + @base.handle_errors + def list_vendor_passthru_methods(self, node_uuid): + """List vendor-specific extensions (passthru) methods for a node + + :param node_uuid: The unique identifier of the node. + """ + return self._list_request('/nodes/%s/vendor_passthru/methods' + % node_uuid) + + @base.handle_errors + def ipa_heartbeat(self, node_uuid, callback_url, agent_token, + agent_version): + """Create a IPA heartbeat from the given body. + + :param node_uuid: The unique identifier of the node. + :param callback_url: The URL of an active ironic-python-agent ramdisk + :param agent_token: The token of the ironic-python-agent ramdisk + :param agent_version: The version of the ironic-python-agent ramdisk + """ + kwargs = { + 'node_ident': node_uuid, + 'callback_url': callback_url, + 'agent_version': agent_version, + 'agent_token': agent_token, + } + + return self._create_request_no_response_body('heartbeat', kwargs) diff --git a/ironic_tempest_plugin/tests/api/base.py b/ironic_tempest_plugin/tests/api/base.py index 5469579..6ebb162 100644 --- a/ironic_tempest_plugin/tests/api/base.py +++ b/ironic_tempest_plugin/tests/api/base.py @@ -482,3 +482,16 @@ class BaseBaremetalTest(api_version_utils.BaseMicroversionTest, """ resp, body = cls.client.create_allocation(resource_class, **kwargs) return resp, body + + +class BaseBaremetalRBACTest(BaseBaremetalTest): + + # Unless otherwise superceeded by a version, RBAC tests generally start at + # version 1.70 as that is when System scope and the delineation occured. + min_microversion = '1.70' + + @classmethod + def skip_checks(cls): + super(BaseBaremetalRBACTest, cls).skip_checks() + if not CONF.enforce_scope.ironic: + raise cls.skipException('RBAC tests for Ironic are not enabled.') diff --git a/ironic_tempest_plugin/tests/api/rbac_defaults/__init__.py b/ironic_tempest_plugin/tests/api/rbac_defaults/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ironic_tempest_plugin/tests/api/rbac_defaults/test_nodes.py b/ironic_tempest_plugin/tests/api/rbac_defaults/test_nodes.py new file mode 100644 index 0000000..eecbc51 --- /dev/null +++ b/ironic_tempest_plugin/tests/api/rbac_defaults/test_nodes.py @@ -0,0 +1,1284 @@ +# 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_utils import uuidutils +from tempest import config +# from tempest.lib.common import rest_client +# from tempest.lib.common.utils import data_utils +# from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from ironic_tempest_plugin.common import waiters +from ironic_tempest_plugin.tests.api import base + +CONF = config.CONF + + +class TestNodeProjectReader(base.BaseBaremetalRBACTest): + """Tests for baremetal nodes with a tempest project reader.""" + + credentials = ['system_admin', 'project_reader'] + + def setUp(self): + super(TestNodeProjectReader, self).setUp() + + self.client = self.os_system_admin.baremetal.BaremetalClient() + self.reader_client = self.os_project_reader.baremetal.BaremetalClient() + _, self.chassis = self.create_chassis() + # Bare node, no inherent permissions by default for project readers. + _, self.node = self.create_node(self.chassis['uuid']) + + # Default policy is: + # ('role:reader and ' + # '(project_id:%(node.owner)s or project_id:%(node.lessee)s)') + + def test_reader_cannot_create_node(self): + try: + resp, body = self.reader_client.create_node(self.chassis['uuid']) + except lib_exc.ServerFault as e: + resp = e.resp + + self.assertEqual(500, resp.status) + + def test_reader_cannot_get_node(self): + """Reader cannot get node + + baremetal:node:list + """ + try: + resp, body = self.reader_client.show_node(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_list_is_empty(self): + """Expected default for no lessee or owner rights is an empty list. + + baremetal:node:list and baremetal:node:list_all + """ + resp, body = self.reader_client.list_nodes() + self.assertEqual(0, len(body['nodes'])) + + def test_reader_cannot_update_node(self): + """Reader cannot update node + + baremetal:node:update + """ + try: + resp, body = self.reader_client.update_node(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_driver_info(self): + """Reader cannot update node driver_info + + baremetal:node:update:driver_info + """ + patch = [{'path': '/driver_info/ipmi_username', 'op': 'replace', + 'value': 'foo_user'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_properties(self): + """Reader cannot update node properties + + baremetal:node:update:properties + """ + new_p = {'cpu_arch': 'arm64', 'cpus': '1', 'local_gb': '10000', + 'memory_mb': '12300'} + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + properties=new_p) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_chassis_uuid(self): + """Reader cannot update node chassis uuid + + baremetal:node:update:chassis_uuid + """ + patch = [{'path': '/chassis_uuid', 'op': 'replace', + 'value': 'new_chassis_uuid'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_instance_uuid(self): + """Reader cannot update node instance uuid + + baremetal:node:update:instance_uuid + """ + patch = [{'path': '/instance_uuid', 'op': 'replace', + 'value': 'new_instance_uuid'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_lessee(self): + """Reader cannot update node lessee + + baremetal:node:update:lessee + """ + patch = [{'path': '/lessee', 'op': 'replace', + 'value': 'new_lessee'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_owner(self): + """Reader cannot update node owner + + baremetal:node:update:owner + """ + patch = [{'path': '/owner', 'op': 'replace', + 'value': 'new_owner'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_driver_interfaces(self): + """Reader cannot update node driver interfaces + + baremetal:node:update:driver_interfaces + """ + patch = [{'path': '/driver', 'op': 'replace', 'value': 'ipmi'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_network_data(self): + """Reader cannot update node network data + + baremetal:node:update:network_data + """ + new_net_data = {'networks': [], 'services': [], 'links': []} + patch = [{'path': '/network_data', 'op': 'replace', + 'value': new_net_data}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_conductor_group(self): + """Reader cannot update node conductor group + + baremetal:node:update:conductor_group + """ + patch = [{'path': '/conductor_group', 'op': 'replace', + 'value': 'new_group'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_name(self): + """Reader cannot update node name + + baremetal:node:update:name + """ + patch = [{'path': '/name', 'op': 'replace', 'value': 'new_name'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_retired(self): + """Reader cannot update node retired + + baremetal:node:update:retired + """ + patch = [{'path': '/retired', 'op': 'replace', 'value': True}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_node_extra(self): + """Reader cannot update node extra + + baremetal:node:update:extra + """ + patch = [{'path': '/extra', 'op': 'replace', + 'value': {'extra': 'extra'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_instance_info(self): + """Reader cannot update node instance info + + baremetal:node:update:instance_info + """ + patch = [{'path': '/instance_info', 'op': 'replace', + 'value': {'display_name': 'new_display_name'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_update_owner_provisioned(self): + """Reader cannot update node owner provisioned + + baremetal:node:update_owner_provisioned + """ + provision_states_list = ['manage', 'provide', 'active'] + target_states_list = ['manageable', 'available', 'active'] + for (provision_state, target_state) in zip(provision_states_list, + target_states_list): + self.client.set_node_provision_state(self.node['uuid'], + provision_state) + waiters.wait_for_bm_node_status(self.client, self.node['uuid'], + attr='provision_state', + status=target_state, timeout=10) + + patch = [{'path': '/owner', 'op': 'replace', + 'value': {'display_name': 'new_owner'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + finally: + self.client.set_node_provision_state(self.node['uuid'], + 'deleted') + + self.assertEqual(404, resp.status) + + def test_reader_cannot_delete_node(self): + """Reader cannot delete node + + baremetal:node:delete + """ + try: + resp, body = self.reader_client.delete_node(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_validate_node(self): + """Reader cannot validate node + + baremetal:node:validate + """ + try: + resp, body = self.reader_client.validate_driver_interface( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_maintenance(self): + """Reader cannot set maintenance mode + + baremetal:node:set_maintenance + """ + patch = [{'path': '/maintenance', 'op': 'replace', 'value': True}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_unset_maintenance(self): + """Reader cannot unset maintenance mode + + baremetal:node:clear_maintenance + """ + patch = [{'path': '/maintenance', 'op': 'replace', 'value': False}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_get_boot_device(self): + """Reader cannot get boot device + + baremetal:node:get_boot_device + """ + try: + resp, body = self.reader_client.get_node_boot_device( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_boot_device(self): + """Reader cannot set boot device + + baremetal:node:set_boot_device + """ + try: + resp, body = self.reader_client.set_node_boot_device( + self.node['uuid'], 'pxe') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_get_indicator_state(self): + """Reader cannot get indicator state + + baremetal:node:get_indicator_state + """ + try: + resp, body = self.reader_client.get_node_indicator_state( + self.node['uuid'], 'system', 'alert') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_indicator_state(self): + """Reader cannot set indicator state + + baremetal:node:set_indicator_state + """ + try: + resp, body = self.reader_client.set_node_indicator_state( + self.node['uuid'], 'system', 'alert', 'BLINKING') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_inject_nmi(self): + """Reader cannot inject NMI + + baremetal:node:inject_nmi + """ + try: + resp, body = self.reader_client._put_request( + '/v1/nodes/%s/management/inject_nmi' % self.node['uuid'], {}) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_get_states(self): + """Reader cannot list the states of the node + + baremetal:node:get_states + """ + try: + resp, body = self.reader_client.list_nodestates(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_power_state(self): + """Reader cannot set power state + + baremetal:node:set_power_state + """ + try: + resp, body = self.reader_client.set_node_power_state( + self.node['uuid'], 'off') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_boot_mode(self): + """Reader cannot set boot mode + + baremetal:node:set_boot_mode + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'boot_mode', 'uefi') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_secure_boot(self): + """Reader cannot set secure boot + + baremetal:node:set_secure_boot + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'secure_boot', True) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_provision_state(self): + """Reader cannot set provision state + + baremetal:node:set_provision_state + """ + try: + resp, body = self.reader_client.set_node_provision_state( + self.node['uuid'], 'manage') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_raid_state(self): + """Reader cannot set raid state + + baremetal:node:set_raid_state + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'target_raid_config', {'raid': 'config'}) + except lib_exc.UnexpectedResponseCode as e: + resp = e.resp + + self.assertEqual(405, resp.status) + + def test_reader_cannot_get_console(self): + """Reader cannot get console + + baremetal:node:get_console + """ + try: + resp, body = self.reader_client.get_console(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_set_console_state(self): + """Reader cannot set console state + + baremetal:node:set_console_state + """ + try: + resp, body = self.reader_client.set_console_mode( + self.node['uuid'], enabled=True) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_vif_list(self): + """Reader cannot list vifs + + baremetal:node:vif:list + """ + try: + resp, body = self.reader_client.vif_list(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_vif_attach(self): + """Reader cannot attach vif + + baremetal:node:vif:attach + """ + try: + resp, body = self.reader_client.vif_attach( + self.node['uuid'], 'vifid') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_vif_detach(self): + """Reader cannot detach vif + + baremetal:node:vif:detach + """ + try: + resp, body = self.reader_client.vif_detach( + self.node['uuid'], 'vifid') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_traits_list(self): + """Reader cannot list traits + + baremetal:node:traits:list + """ + try: + resp, body = self.reader_client.list_node_traits(self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_traits_set(self): + """Reader cannot set traits + + baremetal:node:traits:set + """ + try: + resp, body = self.reader_client.set_node_traits( + self.node['uuid'], ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_traits_delete(self): + """Reader cannot delete traits + + baremetal:node:traits:delete + """ + try: + resp, body = self.reader_client.remove_node_traits( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_bios_get(self): + """Reader cannot get bios settings + + baremetal:node:bios:get + """ + try: + resp, body = self.reader_client.list_node_bios_settings( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_disable_cleaning(self): + """Reader cannot disable automated node cleaning + + baremetal:node:disable_cleaning + """ + patch = [{'path': '/automated_clean', 'op': 'replace', 'value': False}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_history_get(self): + """Reader cannot list history entries for a node + + baremetal:node:history:get + """ + try: + resp, body = self.reader_client.list_node_history( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_vendor_passthru(self): + """Reader cannot list vendor-specific extensions + + baremetal:node:vendor_passthru + """ + try: + resp, body = self.reader_client.list_vendor_passthru_methods( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_ipa_heartbeat(self): + """Reader cannot heartbeat + + baremetal:node:ipa_heartbeat + """ + try: + resp, body = self.reader_client.ipa_heartbeat( + self.node['uuid'], callback_url='http://foo/', + agent_token=uuidutils.generate_uuid(), agent_version='1') + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + +class TestNodeSystemReader(base.BaseBaremetalRBACTest): + """Tests for baremetal nodes with a tempest system reader. + + All tests here must always expect *multiple* nodes visible, since + this is a global reader role. + + https://opendev.org/openstack/ironic/src/branch/master/ironic/common/policy.py#L60 # noqa + """ + + credentials = ['system_admin', 'system_reader'] + + def setUp(self): + super(TestNodeSystemReader, self).setUp() + + self.client = self.os_system_admin.baremetal.BaremetalClient() + self.reader_client = self.os_system_reader.baremetal.BaremetalClient() + _, self.chassis = self.create_chassis() + _, self.node = self.create_node(self.chassis['uuid']) + + def test_reader_cannot_create_node(self): + """Reader cannot create node + + baremetal:node:create + """ + try: + resp, body = self.reader_client.create_node(self.chassis['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_can_get_node(self): + """Reader can get node + + baremetal:node:??get??list?? + """ + resp, body = self.reader_client.show_node(self.node['uuid']) + self.assertEqual(200, resp.status) + + def test_reader_list_is_not_empty(self): + """List nodes return all nodes + + baremetal:node:list and baremetal:node:list_all + """ + resp, body = self.reader_client.list_nodes() + self.assertGreater(len(body['nodes']), 0) + + def test_reader_cannot_update_node(self): + """Reader cannot update node + + baremetal:node:update + """ + try: + resp, body = self.reader_client.update_node(self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_driver_info(self): + """Reader cannot update node driver_info + + baremetal:node:update:driver_info + """ + patch = [{'path': '/driver_info/ipmi_username', 'op': 'replace', + 'value': 'foo_user'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_properties(self): + """Reader cannot update node properties + + baremetal:node:update:properties + """ + new_p = {'cpu_arch': 'arm64', 'cpus': '1', 'local_gb': '10000', + 'memory_mb': '12300'} + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + properties=new_p) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_chassis_uuid(self): + """Reader cannot update node chassis uuid + + baremetal:node:update:chassis_uuid + """ + patch = [{'path': '/chassis_uuid', 'op': 'replace', + 'value': 'new_chassis_uuid'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_instance_uuid(self): + """Reader cannot update node instance uuid + + baremetal:node:update:instance_uuid + """ + patch = [{'path': '/instance_uuid', 'op': 'replace', + 'value': 'new_instance_uuid'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_lessee(self): + """Reader cannot update node lessee + + baremetal:node:update:lessee + """ + patch = [{'path': '/lessee', 'op': 'replace', + 'value': 'new_lessee'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_owner(self): + """Reader cannot update node owner + + baremetal:node:update:owner + """ + patch = [{'path': '/owner', 'op': 'replace', + 'value': 'new_owner'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_driver_interfaces(self): + """Reader cannot update node driver interfaces + + baremetal:node:update:driver_interfaces + """ + patch = [{'path': '/driver', 'op': 'replace', 'value': 'ipmi'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_network_data(self): + """Reader cannot update node network data + + baremetal:node:update:network_data + """ + new_net_data = {'networks': [], 'services': [], 'links': []} + patch = [{'path': '/network_data', 'op': 'replace', + 'value': new_net_data}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_conductor_group(self): + """Reader cannot update node conductor group + + baremetal:node:update:conductor_group + """ + patch = [{'path': '/conductor_group', 'op': 'replace', + 'value': 'new_group'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_name(self): + """Reader cannot update node name + + baremetal:node:update:name + """ + patch = [{'path': '/name', 'op': 'replace', 'value': 'new_name'}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_retired(self): + """Reader cannot update node retired + + baremetal:node:update:retired + """ + patch = [{'path': '/retired', 'op': 'replace', 'value': True}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_node_extra(self): + """Reader cannot update node extra + + baremetal:node:update:extra + """ + patch = [{'path': '/extra', 'op': 'replace', + 'value': {'extra': 'extra'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_instance_info(self): + """Reader cannot update node instance info + + baremetal:node:update:instance_info + """ + patch = [{'path': '/instance_info', 'op': 'replace', + 'value': {'display_name': 'new_display_name'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_update_owner_provisioned(self): + """Reader cannot update node owner provisioned + + baremetal:node:update_owner_provisioned + """ + provision_states_list = ['manage', 'provide', 'active'] + target_states_list = ['manageable', 'available', 'active'] + for (provision_state, target_state) in zip(provision_states_list, + target_states_list): + self.client.set_node_provision_state(self.node['uuid'], + provision_state) + waiters.wait_for_bm_node_status(self.client, self.node['uuid'], + attr='provision_state', + status=target_state, timeout=10) + + patch = [{'path': '/owner', 'op': 'replace', + 'value': {'display_name': 'new_owner'}}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + finally: + self.client.set_node_provision_state(self.node['uuid'], + 'deleted') + + self.assertEqual(403, resp.status) + + def test_reader_cannot_delete_node(self): + """Reader cannot delete node + + baremetal:node:delete + """ + try: + resp, body = self.reader_client.delete_node(self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_validate_node(self): + """Reader cannot validate node + + baremetal:node:validate + """ + try: + resp, body = self.reader_client.validate_driver_interface( + self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_maintenance(self): + """Reader cannot set maintenance mode + + baremetal:node:set_maintenance + """ + patch = [{'path': '/maintenance', 'op': 'replace', 'value': True}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_unset_maintenance(self): + """Reader cannot unset maintenance mode + + baremetal:node:clear_maintenance + """ + patch = [{'path': '/maintenance', 'op': 'replace', 'value': False}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_get_boot_device(self): + """Reader cannot get boot device + + baremetal:node:get_boot_device + """ + try: + resp, body = self.reader_client.get_node_boot_device( + self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_boot_device(self): + """Reader cannot set boot device + + baremetal:node:set_boot_device + """ + try: + resp, body = self.reader_client.set_node_boot_device( + self.node['uuid'], 'pxe') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_can_get_indicator_state(self): + """Reader can get indicator state + + baremetal:node:get_indicator_state + """ + resp, body = self.reader_client.get_node_indicator_state( + self.node['uuid'], 'system', 'alert') + self.assertEqual(200, resp.status) + + def test_reader_cannot_set_indicator_state(self): + """Reader cannot set indicator state + + baremetal:node:set_indicator_state + """ + try: + resp, body = self.reader_client.set_node_indicator_state( + self.node['uuid'], 'system', 'alert', 'BLINKING') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_inject_nmi(self): + """Reader cannot inject NMI + + baremetal:node:inject_nmi + """ + try: + resp, body = self.reader_client._put_request( + '/v1/nodes/%s/management/inject_nmi' % self.node['uuid'], {}) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_can_get_states(self): + """Reader can list the states of the node + + baremetal:node:get_states + """ + resp, body = self.reader_client.list_nodestates(self.node['uuid']) + self.assertEqual(200, resp.status) + + def test_reader_cannot_set_power_state(self): + """Reader cannot set power state + + baremetal:node:set_power_state + """ + try: + resp, body = self.reader_client.set_node_power_state( + self.node['uuid'], 'off') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_boot_mode(self): + """Reader cannot set boot mode + + baremetal:node:set_boot_mode + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'boot_mode', 'uefi') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_secure_boot(self): + """Reader cannot set secure boot + + baremetal:node:set_secure_boot + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'secure_boot', True) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_provision_state(self): + """Reader cannot set provision state + + baremetal:node:set_provision_state + """ + try: + resp, body = self.reader_client.set_node_provision_state( + self.node['uuid'], 'manage') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_raid_state(self): + """Reader cannot set raid state + + baremetal:node:set_raid_state + """ + try: + resp, body = self.reader_client.set_node_state( + self.node['uuid'], 'target_raid_config', {'raid': 'config'}) + except lib_exc.UnexpectedResponseCode as e: + resp = e.resp + + self.assertEqual(405, resp.status) + + def test_reader_cannot_get_console(self): + """Reader cannot get console + + baremetal:node:get_console + """ + try: + resp, body = self.reader_client.get_console(self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_set_console_state(self): + """Reader cannot set console state + + baremetal:node:set_console_state + """ + try: + resp, body = self.reader_client.set_console_mode( + self.node['uuid'], enabled=True) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_can_vif_list(self): + """Reader can list vifs + + baremetal:node:vif:list + """ + resp, body = self.reader_client.vif_list(self.node['uuid']) + self.assertEqual(200, resp.status) + + def test_reader_cannot_vif_attach(self): + """Reader cannot attach vif + + baremetal:node:vif:attach + """ + try: + resp, body = self.reader_client.vif_attach( + self.node['uuid'], 'vifid') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_vif_detach(self): + """Reader cannot detach vif + + baremetal:node:vif:detach + """ + try: + resp, body = self.reader_client.vif_detach( + self.node['uuid'], 'vifid') + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_can_traits_list(self): + """Reader can list traits + + baremetal:node:traits:list + """ + resp, body = self.reader_client.list_node_traits(self.node['uuid']) + self.assertEqual(200, resp.status) + + def test_reader_cannot_traits_set(self): + """Reader cannot set traits + + baremetal:node:traits:set + """ + try: + resp, body = self.reader_client.set_node_traits( + self.node['uuid'], ['CUSTOM_TRAIT_A', 'CUSTOM_TRAIT_B']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_traits_delete(self): + """Reader cannot delete traits + + baremetal:node:traits:delete + """ + try: + resp, body = self.reader_client.remove_node_traits( + self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_can_bios_get(self): + """Reader can get bios settings + + baremetal:node:bios:get + """ + resp, body = self.reader_client.list_node_bios_settings( + self.node['uuid']) + self.assertEqual(200, resp.status) + + def test_reader_cannot_disable_cleaning(self): + """Reader cannot disable automated node cleaning + + baremetal:node:disable_cleaning + """ + patch = [{'path': '/automated_clean', 'op': 'replace', 'value': False}] + try: + resp, body = self.reader_client.update_node(self.node['uuid'], + patch=patch) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_history_get(self): + """Reader cannot list history entries for a node + + baremetal:node:history:get + """ + try: + resp, body = self.reader_client.list_node_history( + self.node['uuid']) + except lib_exc.NotFound as e: + resp = e.resp + + self.assertEqual(404, resp.status) + + def test_reader_cannot_vendor_passthru(self): + """Reader cannot list vendor-specific extensions + + baremetal:node:vendor_passthru + """ + try: + resp, body = self.reader_client.list_vendor_passthru_methods( + self.node['uuid']) + except lib_exc.Forbidden as e: + resp = e.resp + + self.assertEqual(403, resp.status) + + def test_reader_cannot_ipa_heartbeat(self): + """Reader cannot heartbeat + + baremetal:node:ipa_heartbeat + """ + # TODO(hjensas) + pass