diff --git a/oneview_client/__init__.py b/oneview_client/__init__.py index 7d74190..8a93ad5 100644 --- a/oneview_client/__init__.py +++ b/oneview_client/__init__.py @@ -1,16 +1,19 @@ -# -*- coding: utf-8 -*- - -# 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 +# -*- encoding: utf-8 -*- # -# http://www.apache.org/licenses/LICENSE-2.0 +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# Copyright 2015 Universidade Federal de Campina Grande # -# 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. +# 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 pbr.version diff --git a/oneview_client/client.py b/oneview_client/client.py new file mode 100644 index 0000000..da2e816 --- /dev/null +++ b/oneview_client/client.py @@ -0,0 +1,492 @@ +# -*- encoding: utf-8 -*- +# +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# Copyright 2015 Universidade Federal de Campina Grande +# +# 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 time + +import requests +import retrying + +from oneview_client import exceptions +from oneview_client import states + + +SUPPORTED_ONEVIEW_VERSION = 200 + +WAIT_DO_REQUEST_IN_MILLISECONDS = 100 +WAIT_TASK_IN_MILLISECONDS = 1000 + +GET_REQUEST_TYPE = 'GET' +PUT_REQUEST_TYPE = 'PUT' +POST_REQUEST_TYPE = 'POST' +DELETE_REQUEST_TYPE = 'DELETE' + +MOMENTARY_PRESS = 'MomentaryPress' +PRESS_AND_HOLD = 'PressAndHold' + + +class Client(object): + + def __init__( + self, manager_url, username, password, + allow_insecure_connections=False, tls_cacert_file='', + max_polling_attempts=20 + ): + self.manager_url = manager_url + self.username = username + self.password = password + self.allow_insecure_connections = allow_insecure_connections + self.tls_cacert_file = tls_cacert_file + self.max_polling_attempts = max_polling_attempts + + self.session_id = self.get_session() + + def get_session(self): + response = self._authenticate() + return response.json().get('sessionID') + + def _authenticate(self): + url = '%s/rest/login-sessions' % self.manager_url + body = { + 'userName': self.username, + 'password': self.password + } + headers = { + 'content-type': 'application/json' + } + + verify_ssl = self._get_verify_connection_option() + + r = requests.post(url, + data=json.dumps(body), + headers=headers, + verify=verify_ssl) + + if r.status_code == 401: + raise exceptions.OneViewNotAuthorizedException() + else: + return r + + def _get_verify_connection_option(self): + verify_status = False + user_cacert = self.tls_cacert_file + + if self.allow_insecure_connections is False: + if not user_cacert: + verify_status = True + else: + verify_status = user_cacert + return verify_status + + def verify_oneview_version(self): + if not self._is_oneview_version_compatible(): + raise exceptions.IncompatibleOneViewAPIVersion( + "The version of the OneView's API is unsupported. Supported " + "version is '%s'" % SUPPORTED_ONEVIEW_VERSION) + + def _is_oneview_version_compatible(self): + versions = self.get_oneview_version() + v = SUPPORTED_ONEVIEW_VERSION + min_version_compatible = versions.get('minimumVersion') <= v + max_version_compatible = versions.get("currentVersion") >= v + return min_version_compatible and max_version_compatible + + def get_oneview_version(self): + url = '%s/rest/version' % self.manager_url + headers = {"Accept-Language": "en_US"} + + verify_ssl = self._get_verify_connection_option() + + try: + versions = requests.get( + url, headers=headers, verify=verify_ssl + ).json() + return versions + except requests.RequestException as e: + raise exceptions.OneViewConnectionError(e.message) + + # --- Power Driver --- + def get_node_power_state(self, node_info): + server_hardware_json = self.get_server_hardware(node_info) + power_state = server_hardware_json.get('powerState') + + return power_state + + def power_on(self, node_info): + if self.get_node_power_state(node_info) == states.ONEVIEW_POWER_ON: + ret = states.ONEVIEW_POWER_ON + else: + ret = self.set_node_power_state( + node_info, states.ONEVIEW_POWER_ON + ) + return ret + + def power_off(self, node_info): + if self.get_node_power_state(node_info) == states.ONEVIEW_POWER_OFF: + ret = states.ONEVIEW_POWER_OFF + else: + ret = self.set_node_power_state( + node_info, states.ONEVIEW_POWER_OFF, PRESS_AND_HOLD + ) + return ret + + def set_node_power_state( + self, node_info, state, press_type=MOMENTARY_PRESS + ): + body = {'powerState': state, 'powerControl': press_type} + power_state_uri = (node_info.get('server_hardware_uri') + + '/powerState') + task = self._prepare_and_do_request( + uri=power_state_uri, body=body, request_type=PUT_REQUEST_TYPE + ) + try: + self._wait_for_task_to_complete(task) + except exceptions.OneViewTaskError as e: + raise exceptions.OneViewErrorStateSettingPowerState(e.message) + + current_state = self.get_node_power_state(node_info) + + if current_state is states.ONEVIEW_ERROR: + message = ( + "Error setting node power state to %(state)s" % + {"state": state} + ) + raise exceptions.OneViewErrorStateSettingPowerState(message) + + return current_state + + # --- ManagementDriver --- + def get_server_hardware(self, node_info): + server_hardware_uri = node_info.get('server_hardware_uri') + server_hardware_json = self._prepare_and_do_request( + uri=server_hardware_uri + ) + if server_hardware_json.get("uri") is None: + message = "OneView Server Hardware resource not found." + raise exceptions.OneViewResourceNotFoundError(message) + + return server_hardware_json + + def get_server_profile_from_hardware(self, node_info): + server_hardware_json = self.get_server_hardware(node_info) + server_profile_uri = server_hardware_json.get("serverProfileUri") + if server_profile_uri is None: + message = ( + "There is no server profile assigned to" + " %(server_hardware_uri)s" % + {'server_hardware_uri': node_info.get('server_hardware_uri')} + ) + raise exceptions.OneViewServerProfileAssociatedError(message) + + server_profile_json = self._prepare_and_do_request( + uri=server_profile_uri + ) + if server_profile_json.get("uri") is None: + message = "OneView Server Profile resource not found." + raise exceptions.OneViewResourceNotFoundError(message) + + return server_profile_json + + def get_server_profile_template(self, node_info): + server_profile_template_uri = ( + node_info.get('server_profile_template_uri') + ) + server_profile_template_json = self._prepare_and_do_request( + uri=server_profile_template_uri, request_type=GET_REQUEST_TYPE + ) + + if server_profile_template_json.get("uri") is None: + message = "OneView Server Profile Template resource not found." + raise exceptions.OneViewResourceNotFoundError(message) + + return server_profile_template_json + + def get_boot_order(self, node_info): + server_profile_json = self.get_server_profile_from_hardware( + node_info + ) + return server_profile_json.get("boot").get("order") + + def _make_boot_order_body(self, server_profile_dict, order): + manageBoot = server_profile_dict.get("boot").get("manageBoot") + server_profile_dict["boot"] = { + "manageBoot": manageBoot, + "order": order + } + + return server_profile_dict + + def set_boot_device(self, node_info, new_primary_boot_device): + boot_order = self.get_boot_order(node_info) + + if new_primary_boot_device is None: + raise exceptions.OneViewBootDeviceInvalidError() + + if new_primary_boot_device in boot_order: + boot_order.remove(new_primary_boot_device) + + boot_order.insert(0, new_primary_boot_device) + + server_profile_dict = self.get_server_profile_from_hardware( + node_info + ) + boot_order_body = self._make_boot_order_body( + server_profile_dict, + boot_order + ) + + task = self._prepare_and_do_request( + uri=server_profile_dict.get('uri'), body=boot_order_body, + request_type=PUT_REQUEST_TYPE + ) + try: + self._wait_for_task_to_complete(task) + except exceptions.OneViewTaskError as e: + raise exceptions.OneViewErrorSettingBootDevice(e.message) + + # ---- Node validate ---- + def validate_node_server_hardware( + self, node_info, node_memorymb, node_cpus + ): + node_sh_uri = node_info.get('server_hardware_uri') + server_hardware_json = self.get_server_hardware(node_info) + server_hardware_memorymb = server_hardware_json.get('memoryMb') + server_hardware_cpus = (server_hardware_json.get('processorCoreCount') + * server_hardware_json.get('processorCount')) + if server_hardware_memorymb != node_memorymb: + message = ( + "Node memory_mb is inconsistent with OneView's" + " server hardware %(server_hardware_uri)s memoryMb. Node" + " validation failed." % {'server_hardware_uri': node_sh_uri} + ) + raise exceptions.OneViewInconsistentResource(message) + elif server_hardware_cpus != node_cpus: + message = ( + "Node cpus is inconsistent with OneView's" + " server hardware %(server_hardware_uri)s cpus. Node" + " validation failed." % {'server_hardware_uri': node_sh_uri} + ) + raise exceptions.OneViewInconsistentResource(message) + + def validate_node_server_hardware_type(self, node_info): + node_sht_uri = node_info.get('server_hardware_type_uri') + server_hardware = self.get_server_hardware(node_info) + server_hardware_sht_uri = server_hardware.get('serverHardwareTypeUri') + + if server_hardware_sht_uri != node_sht_uri: + message = ( + "Node server_hardware_type_uri is inconsistent" + " with OneView's server hardware %(server_hardware_uri)s" + " serverHardwareTypeUri. Node validation failed." % + {'server_hardware_uri': node_info.get('server_hardware_uri')} + ) + raise exceptions.OneViewInconsistentResource(message) + + def check_server_profile_is_applied(self, node_info): + self.get_server_profile_from_hardware(node_info) + + def validate_node_enclosure_group(self, node_info): + server_hardware = self.get_server_hardware(node_info) + sh_enclosure_group_uri = server_hardware.get('serverGroupUri') + node_enclosure_group_uri = node_info.get('enclosure_group_uri') + + if node_enclosure_group_uri is not '': + if sh_enclosure_group_uri != node_enclosure_group_uri: + message = ( + "Node enclosure_group_uri is inconsistent" + " with OneView's server hardware %(server_hardware_uri)s" + " serverGroupUri. Node validation failed." % + {'server_hardware_uri': node_info. + get('server_hardware_uri')} + ) + raise exceptions.OneViewInconsistentResource(message) + + def is_node_port_mac_compatible_with_server_profile( + self, node_info, ports + ): + server_profile_json = self.get_server_profile_from_hardware( + node_info + ) + + primary_boot_connection = None + for connection in server_profile_json.get('connections'): + if connection.get('boot').get('priority') == 'Primary': + primary_boot_connection = connection + + if primary_boot_connection is None: + message = ( + "No primary boot connection configured for server profile" + " %s Node validation failed." % server_profile_json.get('uri') + ) + raise exceptions.OneViewInconsistentResource(message) + + server_profile_mac = primary_boot_connection.get('mac') + + is_mac_address_compatible = True + for port in ports: + if port.__dict__.get('_obj_address').lower() != \ + server_profile_mac.lower(): + is_mac_address_compatible = False + + if (not is_mac_address_compatible) or len(ports) == 0: + message = ( + "The ports of the node are not compatible with its" + " server profile %(server_profile_uri)s. Node validation" + " failed." % + {'server_profile_uri': server_profile_json.get('uri')} + ) + raise exceptions.OneViewInconsistentResource(message) + + def validate_node_server_profile_template(self, node_info): + node_spt_uri = node_info.get('server_profile_template_uri') + + server_profile_template_json = self.get_server_profile_template( + node_info + ) + spt_server_hardware_type_uri = server_profile_template_json \ + .get('serverHardwareTypeUri') + spt_enclosure_group_uri = server_profile_template_json \ + .get('enclosureGroupUri') + + server_hardware_json = self.get_server_hardware(node_info) + sh_server_hardware_type_uri = server_hardware_json \ + .get('serverHardwareTypeUri') + sh_enclosure_group_uri_uri = server_hardware_json \ + .get('serverGroupUri') + + if spt_server_hardware_type_uri != sh_server_hardware_type_uri: + message = ( + "Server profile template %(spt_uri)s serverHardwareTypeUri is" + " inconsistent with server hardware %(server_hardware_uri)s" + " serverHardwareTypeUri. Node validation failed." % + {'spt_uri': node_spt_uri, + 'server_hardware_uri': node_info.get('server_hardware_uri')} + ) + raise exceptions.OneViewInconsistentResource(message) + if spt_enclosure_group_uri != sh_enclosure_group_uri_uri: + message = ( + "Server profile template %(spt_uri)s enclosureGroupUri is" + " inconsistent with server hardware %(server_hardware_uri)s" + " serverGroupUri. Node validation failed." % + {'spt_uri': node_spt_uri, + 'server_hardware_uri': node_info.get('server_hardware_uri')} + ) + raise exceptions.OneViewInconsistentResource(message) + + # --- Requests --- + def _prepare_and_do_request( + self, uri, body={}, request_type=GET_REQUEST_TYPE + ): + json_response = {} + try: + if not self.session_id: + self.session_id = self.get_session() + + headers = { + 'content-type': 'application/json', + 'X-Api-Version': SUPPORTED_ONEVIEW_VERSION, + 'Auth': self.session_id + } + url = '%s%s' % (self.manager_url, uri) + body = json.dumps(body) + response = self._do_request(url, headers, body, request_type) + + json_response = response.json() + except requests.RequestException as e: + connection_error = str(e.message).split(':')[-1] + log_message = ("Can't connect to OneView: %s" % connection_error) + raise exceptions.OneViewConnectionError(log_message) + + return json_response + + def _do_request(self, url, headers, body, request_type): + verify_status = self._get_verify_connection_option() + + @retrying.retry( + stop_max_attempt_number=self.max_polling_attempts, + retry_on_result=lambda response: _check_request_status(response), + wait_fixed=WAIT_DO_REQUEST_IN_MILLISECONDS + ) + def request(url, headers, body, request_type): + + if request_type == PUT_REQUEST_TYPE: + response = requests.put( + url, data=body, headers=headers, verify=verify_status + ) + elif request_type == POST_REQUEST_TYPE: + response = requests.post( + url, data=body, headers=headers, verify=verify_status + ) + elif request_type == DELETE_REQUEST_TYPE: + response = requests.delete( + url, headers=headers, verify=verify_status + ) + else: + response = requests.get( + url, headers=headers, verify=verify_status + ) + return response + return request(url, headers, body, request_type) + + def _wait_for_task_to_complete(self, task): + @retrying.retry( + stop_max_attempt_number=self.max_polling_attempts, + retry_on_result=lambda task: task.get('percentComplete') < 100, + wait_fixed=WAIT_TASK_IN_MILLISECONDS, + retry_on_exception=lambda task: False + ) + def wait(task): + uri = task.get('uri') + task = self._prepare_and_do_request(uri) + + task_state = task.get("taskState") + error_code = task.get("errorCode") + if (not task_state) and error_code: + details = task.get("details") + if error_code == "RESOURCE_NOT_FOUND": + raise exceptions.OneViewResourceNotFoundError(details) + else: + raise exceptions.OneViewTaskError("%s - %s" + % (error_code, details)) + elif task_state.lower() == 'error': + raise exceptions.OneViewTaskError("The task '%s' returned an " + "error state" % uri) + return task + return wait(task) + + +def _check_request_status(response): + repeat = False + status = response.status_code + + if status == 404: + raise exceptions.OneViewResourceNotFoundError() + elif status in (409,): + time.sleep(10) + repeat = True + elif status == 500: + raise exceptions.OneViewInternalServerError( + "OneView returned HTTP 500" + ) + # Any other unexpected status are logged + elif status not in (200, 202,): + message = ( + "OneView appliance returned an unknown response status: %s" + % status + ) + raise exceptions.UnknowOneViewResponseError(message) + return repeat diff --git a/oneview_client/exceptions.py b/oneview_client/exceptions.py new file mode 100644 index 0000000..9d89912 --- /dev/null +++ b/oneview_client/exceptions.py @@ -0,0 +1,75 @@ +# -*- encoding: utf-8 -*- +# +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# Copyright 2015 Universidade Federal de Campina Grande +# +# 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. + + +class OneViewConnectionError(Exception): + message = "Can't connect to OneView" + + +class OneViewNotAuthorizedException(Exception): + message = ("Unauthorized access to OneView. Check the credentials used.") + + +class OneViewResourceNotFoundError(Exception): + message = ("Resource not found in OneView") + + +class OneViewServerProfileTemplateError(Exception): + message = ("Server Profile Template not found in the node's driver_info") + + +class OneViewMaxPollingAttemptsExceededError(Exception): + message = ("Max polling attempts to OneView exceeded") + + +class OneViewBootDeviceInvalidError(Exception): + message = ("Device selected is not a valid boot device") + + +class OneViewServerProfileAssociatedError(Exception): + message = ("There is no Server Profile assigned to this Server" + " Hardware") + + +class OneViewErrorStateSettingPowerState(Exception): + message = ("Server Hardware went to error state when trying to change" + " power state") + + +class OneViewErrorSettingBootDevice(Exception): + message = ("Server Hardware went to error state when trying to change" + " the primary boot device") + + +class OneViewTaskError(Exception): + message = ("The task for this action in OneView returned an error state") + + +class OneViewInconsistentResource(Exception): + message = ("The resource is inconsistent with its OneView counterpart") + + +class OneViewHealthStatusError(Exception): + message = ("There is a health status issue with an OneView Server Profile") + + +class IncompatibleOneViewAPIVersion(Exception): + message = ("The version of OneView's API is unsupported") + + +class UnknowOneViewResponseError(Exception): + message = ("OneView appliance returned an unknown response status") diff --git a/oneview_client/states.py b/oneview_client/states.py new file mode 100644 index 0000000..daae8b5 --- /dev/null +++ b/oneview_client/states.py @@ -0,0 +1,32 @@ +# -*- encoding: utf-8 -*- +# +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# Copyright 2015 Universidade Federal de Campina Grande +# +# 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. + + +# Power states on OneView +ONEVIEW_POWER_ON = 'On' + +ONEVIEW_POWER_OFF = 'Off' + +ONEVIEW_POWERING_ON = 'PoweringOn' + +ONEVIEW_POWERING_OFF = 'PoweringOff' + +ONEVIEW_RESETTING = 'Resetting' + + +# Error states +ONEVIEW_ERROR = 'error' diff --git a/oneview_client/tests/base.py b/oneview_client/tests/base.py deleted file mode 100644 index 1c30cdb..0000000 --- a/oneview_client/tests/base.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2010-2011 OpenStack Foundation -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 oslotest import base - - -class TestCase(base.BaseTestCase): - - """Test case base class for all unit tests.""" diff --git a/oneview_client/tests/test_oneview_client.py b/oneview_client/tests/test_oneview_client.py new file mode 100644 index 0000000..cfaa7d5 --- /dev/null +++ b/oneview_client/tests/test_oneview_client.py @@ -0,0 +1,656 @@ +# -*- encoding: utf-8 -*- +# +# (c) Copyright 2015 Hewlett Packard Enterprise Development LP +# Copyright 2015 Universidade Federal de Campina Grande +# +# 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 mock +import requests +import retrying +import six.moves.http_client as http_client +import unittest + +from oneview_client import client +from oneview_client import exceptions +from oneview_client import states + + +PROPERTIES_DICT = {"cpu_arch": "x86_64", + "cpus": "8", + "local_gb": "10", + "memory_mb": "4096", + "capabilities": "server_hardware_type_uri:fake_sht_uri," + "enclosure_group_uri:fake_eg_uri"} + +DRIVER_INFO_DICT = {'server_hardware_uri': 'fake_sh_uri', + 'server_profile_template_uri': 'fake_spt_uri'} + + +class OneViewClientAuthTestCase(unittest.TestCase): + + def setUp(self): + super(OneViewClientAuthTestCase, self).setUp() + self.manager_url = 'https://1.2.3.4' + self.username = 'user' + self.password = 'password' + + @mock.patch.object(requests, 'post') + def test_authenticate(self, mock_post): + client.Client(self.manager_url, + self.username, + self.password) + mock_post.assert_called_once_with( + 'https://1.2.3.4/rest/login-sessions', + data=json.dumps({"userName": "user", "password": "password"}), + headers={'content-type': 'application/json'}, + verify=True + ) + + @mock.patch.object(requests, 'post') + def test_authenticate_insecure(self, mock_post): + client.Client(self.manager_url, + self.username, + self.password, + allow_insecure_connections=True) + mock_post.assert_called_once_with( + 'https://1.2.3.4/rest/login-sessions', + data=json.dumps({"userName": "user", "password": "password"}), + headers={'content-type': 'application/json'}, + verify=False + ) + + @mock.patch.object(requests, 'post') + def test_authenticate_invalid_credentials(self, mock_post): + response = mock_post.return_value + response.status_code = http_client.UNAUTHORIZED + mock_post.return_value = response + + self.assertRaises( + exceptions.OneViewNotAuthorizedException, + client.Client, + self.manager_url, + 'any', + 'any' + ) + + @mock.patch.object(client.Client, '_authenticate', autospec=True) + def test_get_session(self, mock__authenticate): + reference = "xyz" + + response = mock__authenticate.return_value + response.status_code = http_client.OK + response.json = mock.MagicMock(return_value={"sessionID": reference}) + mock__authenticate.return_value = response + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + session_id = oneview_client.get_session() + self.assertEqual(reference, session_id) + + +@mock.patch.object(client.Client, '_authenticate', autospec=True) +class OneViewClientTestCase(unittest.TestCase): + + def setUp(self): + super(OneViewClientTestCase, self).setUp() + self.manager_url = 'https://1.2.3.4' + self.username = 'user' + self.password = 'password' + + @mock.patch.object(client.Client, 'set_node_power_state', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_power_on_server_on(self, mock_get_pstate, mock_set_pstate, + mock__authenticate): + driver_info = {"server_hardware_uri": "/any"} + mock_get_pstate.return_value = states.ONEVIEW_POWER_ON + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + oneview_client.power_on(driver_info) + mock_get_pstate.assert_called_once_with(oneview_client, driver_info) + self.assertFalse(mock_set_pstate.called) + + @mock.patch.object(client.Client, 'set_node_power_state', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_power_on_server_off(self, mock_get_pstate, mock_set_pstate, + mock__authenticate): + driver_info = {"server_hardware_uri": "/any"} + mock_get_pstate.return_value = states.ONEVIEW_POWER_OFF + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + oneview_client.power_on(driver_info) + mock_get_pstate.assert_called_once_with(oneview_client, driver_info) + mock_set_pstate.assert_called_once_with(oneview_client, driver_info, + states.ONEVIEW_POWER_ON) + + @mock.patch.object(client.Client, 'set_node_power_state', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_power_off_server_off(self, mock_get_pstate, mock_set_pstate, + mock__authenticate): + driver_info = {"server_hardware_uri": "/any"} + mock_get_pstate.return_value = states.ONEVIEW_POWER_OFF + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + oneview_client.power_off(driver_info) + mock_get_pstate.assert_called_once_with(oneview_client, driver_info) + self.assertFalse(mock_set_pstate.called) + + @mock.patch.object(client.Client, 'set_node_power_state', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_power_off_server_on(self, mock_get_pstate, mock_set_pstate, + mock__authenticate): + driver_info = {"server_hardware_uri": "/any"} + mock_get_pstate.return_value = states.ONEVIEW_POWER_ON + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + oneview_client.power_off(driver_info) + mock_get_pstate.assert_called_once_with(oneview_client, driver_info) + mock_set_pstate.assert_called_once_with(oneview_client, driver_info, + states.ONEVIEW_POWER_OFF, + client.PRESS_AND_HOLD) + + @mock.patch.object(client.Client, '_wait_for_task_to_complete', + autospec=True) + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_set_power_state_server_hardware(self, mock_get_node_power, + mock__prepare_do_request, + mock__wait_for_task, + mock__authenticate): + mock_get_node_power.return_value = states.ONEVIEW_POWER_ON + mock__prepare_do_request.return_value = {} + driver_info = {"server_hardware_uri": "/any"} + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + oneview_client.set_node_power_state( + driver_info, + states.ONEVIEW_POWER_ON + ) + mock__prepare_do_request.assert_called_once_with( + oneview_client, + uri='/any/powerState', + body={'powerControl': client.MOMENTARY_PRESS, + 'powerState': states.ONEVIEW_POWER_ON}, + request_type=client.PUT_REQUEST_TYPE, + ) + + @mock.patch.object(requests, 'put', autospec=True) + def test_set_power_state_nonexistent_server_hardware( + self, mock_do_request, mock__authenticate): + + class Response(object): + status_code = 404 + + def json(self): + return { + "errorCode": "RESOURCE_NOT_FOUND", + "details": "Resource not found, ID = /any_invalid", + } + + mock_do_request.return_value = Response() + + driver_info = {"server_hardware_uri": "/any_invalid"} + target_state = states.ONEVIEW_POWER_ON + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaises( + exceptions.OneViewResourceNotFoundError, + oneview_client.set_node_power_state, driver_info, target_state + ) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + @mock.patch.object(client.Client, 'get_node_power_state', autospec=True) + def test_set_power_state_server_hardware_power_status_error( + self, mock_get_node_power, mock__prepare_do_request, mock__authenticate + ): + power = states.ONEVIEW_ERROR + mock_get_node_power.return_value = power + mock__prepare_do_request.return_value = { + "taskState": "Error", + "percentComplete": 100 + } + driver_info = {"server_hardware_uri": "/any"} + target_state = "On" + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + self.assertRaises( + exceptions.OneViewErrorStateSettingPowerState, + oneview_client.set_node_power_state, driver_info, target_state + ) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + def test_get_server_hardware_nonexistent(self, mock__prepare_do_request, + mock__authenticate): + mock__prepare_do_request.return_value = {"error": "resource not found"} + driver_info = {"server_hardware_uri": ""} + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaises( + exceptions.OneViewResourceNotFoundError, + oneview_client.get_server_hardware, + driver_info + ) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + @mock.patch.object(client.Client, 'get_server_profile_from_hardware', + autospec=True) + def test_set_boot_device_nonexistent_resource_uri(self, + mock_get_server_profile, + mock__prepare_do_request, + mock__authenticate): + driver_info = {} + new_first_boot_device = "None" + mock__prepare_do_request.return_value = { + "errorCode": "RESOURCE_NOT_FOUND", + "data": None + } + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaises( + exceptions.OneViewResourceNotFoundError, + oneview_client.set_boot_device, + driver_info, + new_first_boot_device + ) + + @mock.patch.object(client.Client, 'get_boot_order', autospec=True) + def test_set_boot_device_nonexistent_resource_first_boot_device( + self, mock_get_boot_order, mock__authenticate + ): + driver_info = {} + new_first_boot_device = None + mock_get_boot_order.return_value = [] + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaises( + exceptions.OneViewBootDeviceInvalidError, + oneview_client.set_boot_device, + driver_info, + new_first_boot_device + ) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + @mock.patch.object(client.Client, 'get_boot_order', autospec=True) + def test_get_server_profile_from_hardware(self, mock_get_boot_order, + mock_get_server_hardware, + mock__prepare_do_request, + mock__authenticate): + driver_info = {} + new_first_boot_device = "any_boot_device" + mock_get_boot_order.return_value = [] + mock_get_server_hardware.return_value = {} + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaises( + exceptions.OneViewServerProfileAssociatedError, + oneview_client.set_boot_device, + driver_info, + new_first_boot_device + ) + + mock_get_server_hardware.return_value = {"serverProfileUri": "any_uri"} + mock__prepare_do_request.return_value = {} + + self.assertRaises( + exceptions.OneViewResourceNotFoundError, + oneview_client.set_boot_device, + driver_info, + new_first_boot_device + ) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + def test__wait_for_task_to_complete(self, mock__prepare_do_request, + mock__authenticate): + task = { + "uri": "/any_uri", + "taskState": "Something", + "percentComplete": 100 + } + + oneview_client = client.Client(self.manager_url, + self.username, + self.password, + max_polling_attempts=1) + + mock__prepare_do_request.return_value = task + oneview_client._wait_for_task_to_complete(task) + + @mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True) + def test__wait_for_task_to_complete_timeout(self, mock__prepare_do_request, + mock__authenticate): + task = { + "uri": "/any_uri", + "taskState": "Something", + "percentComplete": 30 + } + + oneview_client = client.Client(self.manager_url, + self.username, + self.password, + max_polling_attempts=1) + + mock__prepare_do_request.return_value = task + self.assertRaises( + retrying.RetryError, + oneview_client._wait_for_task_to_complete, + task, + ) + + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_server_hardware_inconsistent_memorymb_value( + self, mock_get_server_hardware, mock__authenticate + ): + mock_get_server_hardware.return_value = { + "memoryMb": 1, + "processorCoreCount": 1, + "processorCount": 1, + } + driver_info = { + "server_hardware_uri": "/any_uri", + } + node_memorymb = 2 + node_cpus = 1 + + exc_expected_msg = ( + "Node memory_mb is inconsistent with OneView's server" + " hardware /any_uri memoryMb. Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_server_hardware, + driver_info, + node_memorymb, + node_cpus + ) + + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_server_hardware_inconsistent_cpus_value( + self, mock_get_server_hardware, mock__authenticate + ): + mock_get_server_hardware.return_value = { + "memoryMb": 1, + "processorCoreCount": 2, + "processorCount": 3, + } + driver_info = { + "server_hardware_uri": "/any_uri", + } + node_memorymb = 1 + node_cpus = 3 + + exc_expected_msg = ( + "Node cpus is inconsistent with OneView's server" + " hardware /any_uri cpus. Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_server_hardware, + driver_info, + node_memorymb, + node_cpus + ) + + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_server_hardware_type_inconsistent_sht_uri( + self, mock_get_server_hardware, mock__authenticate + ): + mock_get_server_hardware.return_value = { + "serverHardwareTypeUri": "/incosistent_uri" + } + driver_info = { + "server_hardware_uri": "/any_serveruri", + "server_hardware_type_uri": "/any_uri", + } + + exc_expected_msg = ( + "Node server_hardware_type_uri is inconsistent with" + " OneView's server hardware /any_serveruri serverHardwareTypeUri." + " Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_server_hardware_type, + driver_info + ) + + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_enclosure_group_inconsistent( + self, mock_get_server_hardware, mock__authenticate + ): + driver_info = { + "server_hardware_uri": "/any_uri", + "enclosure_group_uri": "/inconsistent_uri" + } + + exc_expected_msg = ( + "Node enclosure_group_uri is inconsistent with" + " OneView's server hardware /any_uri serverGroupUri." + " Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_enclosure_group, + driver_info + ) + + @mock.patch.object(client.Client, 'get_server_profile_from_hardware', + autospec=True) + def test_check_node_port_mac_incompatible_with_server_profile( + self, mock_server_profile, mock__authenticate + ): + mock_server_profile.return_value = { + "uri": "/anyuri", + "connections": [ + {'boot': {'priority': u'Primary'}, + 'mac': u'56:88:7B:C0:00:0B'} + ] + } + + exc_expected_msg = ( + "The ports of the node are not compatible with its" + " server profile /anyuri. Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client + .is_node_port_mac_compatible_with_server_profile, + {}, + {} + ) + + @mock.patch.object(client.Client, 'get_server_profile_from_hardware', + autospec=True) + def test_check_node_port_mac_no_primary_boot_connection( + self, mock_server_profile, mock__authenticate + ): + mock_server_profile.return_value = { + "uri": "/anyuri", + "connections": [ + {'boot': {'priority': u'NotBootable'}, + 'mac': u'56:88:7B:C0:00:0B'} + ] + } + + exc_expected_msg = ( + "No primary boot connection configured for server profile" + " /anyuri Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client + .is_node_port_mac_compatible_with_server_profile, + {}, + {} + ) + + @mock.patch.object(client.Client, 'get_server_profile_template', + autospec=True) + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_server_profile_template_inconsistent_sht( + self, mock_server_hardware, mock_server_template, mock__authenticate + ): + mock_server_hardware.return_value = { + 'serverHardwareTypeUri': '/sht_uri', + 'serverGroupUri': 'eg_uri'} + mock_server_template.return_value = { + 'serverHardwareTypeUri': '/inconsistent_uri', + 'enclosureGroupUri': '/inconsistent_uri'} + + driver_info = { + "server_hardware_uri": "/any_uri", + "server_profile_template_uri": "/profile_uri" + } + + exc_expected_msg = ( + "Server profile template /profile_uri serverHardwareTypeUri is" + " inconsistent with server hardware /any_uri" + " serverHardwareTypeUri. Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_server_profile_template, + driver_info + ) + + @mock.patch.object(client.Client, 'get_server_profile_template', + autospec=True) + @mock.patch.object(client.Client, 'get_server_hardware', autospec=True) + def test_validate_node_server_profile_template_inconsistent_eg( + self, mock_server_hardware, mock_server_template, mock__authenticate + ): + mock_server_hardware.return_value = { + 'serverHardwareTypeUri': '/sht_uri', + 'serverGroupUri': 'eg_uri'} + mock_server_template.return_value = { + 'serverHardwareTypeUri': '/sht_uri', + 'enclosureGroupUri': '/inconsistent_uri'} + + driver_info = { + "server_hardware_uri": "/any_uri", + "server_profile_template_uri": "/profile_uri" + } + + exc_expected_msg = ( + "Server profile template /profile_uri enclosureGroupUri is" + " inconsistent with server hardware /any_uri" + " serverGroupUri. Node validation failed." + ) + + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + + self.assertRaisesRegexp( + exceptions.OneViewInconsistentResource, + exc_expected_msg, + oneview_client.validate_node_server_profile_template, + driver_info + ) + + @mock.patch.object(client.Client, 'get_oneview_version') + def test_verify_oneview_version(self, mock_get_oneview_version, + mock__authenticate): + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + mock_get_oneview_version.return_value = { + 'minimumVersion': 120, + 'currentVersion': 200 + } + oneview_client.verify_oneview_version() + mock_get_oneview_version.assert_called_once_with() + + @mock.patch.object(client.Client, 'get_oneview_version') + def test_verify_oneview_version_fail(self, mock_get_oneview_version, + mock__authenticate): + oneview_client = client.Client(self.manager_url, + self.username, + self.password) + mock_get_oneview_version.return_value = { + 'minimumVersion': 120, + 'currentVersion': 120 + } + self.assertRaises( + exceptions.IncompatibleOneViewAPIVersion, + oneview_client.verify_oneview_version + ) diff --git a/oneview_client/tests/test_python-oneviewclient.py b/oneview_client/tests/test_python-oneviewclient.py deleted file mode 100644 index 25d8ca1..0000000 --- a/oneview_client/tests/test_python-oneviewclient.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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. - -""" -test_python-oneviewclient ----------------------------------- - -Tests for `python-oneviewclient` module. -""" - -from python-oneviewclient.tests import base - - -class TestPython-oneviewclient(base.TestCase): - - def test_something(self): - pass diff --git a/requirements.txt b/requirements.txt index 95137a6..fe5ab09 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ pbr>=0.6,!=0.7,<1.0 Babel>=1.3 +retrying>=1.2.3 +six>=1.9.0 diff --git a/setup.cfg b/setup.cfg index 8e78e4a..6a2130b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,7 +22,7 @@ classifier = [files] packages = - python-oneviewclient + oneview_client [build_sphinx] source-dir = doc/source diff --git a/tox.ini b/tox.ini index 73ef134..8b4e2d5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py33,py34,py26,py27,pypy,pep8 +envlist = py34,py27,pep8 skipsdist = True [testenv]