Optimizations for setting boot device
By default, OneView doesn't provide on its API a facility to set one-time boot on managed ServerHardware. This patch aims to allow it through bypassing OneView and using iLO's REST interface directly. Also, some optimization is made to not try to set again the boot device if the selected one is already the primary. Change-Id: I1163048b6edf8778c3a84414804b73eef2c0fd5f
This commit is contained in:
parent
2100accffd
commit
dca44ab0dd
@ -25,6 +25,7 @@ from oneview_client import exceptions
|
||||
from oneview_client import ilo_utils
|
||||
from oneview_client import managers
|
||||
from oneview_client import states
|
||||
from oneview_client import utils
|
||||
|
||||
SUPPORTED_ONEVIEW_VERSION = 200
|
||||
|
||||
@ -237,6 +238,28 @@ class BaseClient(object):
|
||||
finally:
|
||||
ilo_utils.ilo_logout(host_ip, ilo_token)
|
||||
|
||||
def _set_onetime_boot(self, server_hardware_uuid, boot_device):
|
||||
host_ip, ilo_token = self._get_ilo_access(server_hardware_uuid)
|
||||
oneview_ilo_mapping = {
|
||||
'HardDisk': 'Hdd',
|
||||
'PXE': 'Pxe',
|
||||
'CD': 'Cd',
|
||||
'USB': 'Usb',
|
||||
}
|
||||
try:
|
||||
ilo_device = oneview_ilo_mapping[boot_device]
|
||||
return ilo_utils.set_onetime_boot(host_ip, ilo_token,
|
||||
ilo_device,
|
||||
self.allow_insecure_connections)
|
||||
except exceptions.IloException as e:
|
||||
raise e
|
||||
except KeyError as e:
|
||||
raise exceptions.IloException(
|
||||
"Set one-time boot to %s is not supported." % boot_device
|
||||
)
|
||||
finally:
|
||||
ilo_utils.ilo_logout(host_ip, ilo_token)
|
||||
|
||||
|
||||
class ClientV2(BaseClient):
|
||||
|
||||
@ -357,12 +380,32 @@ class Client(BaseClient):
|
||||
)
|
||||
return server_profile.boot.get("order")
|
||||
|
||||
def set_boot_device(self, node_info, new_primary_boot_device):
|
||||
boot_order = self.get_boot_order(node_info)
|
||||
|
||||
def set_boot_device(self, node_info, new_primary_boot_device,
|
||||
onetime=False):
|
||||
if new_primary_boot_device is None:
|
||||
raise exceptions.OneViewBootDeviceInvalidError()
|
||||
|
||||
boot_order = self.get_boot_order(node_info)
|
||||
if new_primary_boot_device == boot_order[0]:
|
||||
return
|
||||
|
||||
if onetime:
|
||||
try:
|
||||
sh_uuid = utils.get_uuid_from_uri(
|
||||
node_info['server_hardware_uri']
|
||||
)
|
||||
self._set_onetime_boot(sh_uuid, new_primary_boot_device)
|
||||
return
|
||||
except exceptions.IloException:
|
||||
# Falls back to persistent in case of failure
|
||||
pass
|
||||
|
||||
self._persistent_set_boot_device(node_info, boot_order,
|
||||
new_primary_boot_device)
|
||||
|
||||
def _persistent_set_boot_device(self, node_info, boot_order,
|
||||
new_primary_boot_device):
|
||||
|
||||
if new_primary_boot_device in boot_order:
|
||||
boot_order.remove(new_primary_boot_device)
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import requests
|
||||
|
||||
from oneview_client import exceptions
|
||||
@ -33,11 +34,20 @@ def rest_op(operation, host, suburi, request_headers, request_body,
|
||||
if operation == "GET":
|
||||
response = requests.get(url, headers=request_headers,
|
||||
verify=enforce_SSL)
|
||||
return_value = (response.status_code, response.headers,
|
||||
response.json())
|
||||
elif operation == "DELETE":
|
||||
response = requests.delete(url, headers=request_headers,
|
||||
verify=enforce_SSL)
|
||||
return_value = (response.status_code, response.headers,
|
||||
response.json())
|
||||
elif operation == "PATCH":
|
||||
response = requests.patch(url, data=json.dumps(request_body),
|
||||
headers=request_headers, verify=enforce_SSL)
|
||||
return_value = (response.status_code, response.headers,
|
||||
response.json())
|
||||
|
||||
return response.status_code, response.headers, response.json()
|
||||
return return_value
|
||||
|
||||
|
||||
# REST GET
|
||||
@ -47,6 +57,14 @@ def rest_get(host, suburi, request_headers, x_auth_token, enforce_SSL=True):
|
||||
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
||||
|
||||
|
||||
# REST PATCH
|
||||
def rest_patch(host, suburi, request_headers, request_body, x_auth_token,
|
||||
enforce_SSL=True):
|
||||
return rest_op('PATCH', host, suburi, request_headers, request_body,
|
||||
x_auth_token, enforce_SSL=enforce_SSL)
|
||||
# NOTE: be prepared for various HTTP responses including 500, 404, etc.
|
||||
|
||||
|
||||
# REST DELETE
|
||||
def rest_delete(host, suburi, request_headers, x_auth_token, enforce_SSL=True):
|
||||
return rest_op('DELETE', host, suburi, request_headers, None, x_auth_token,
|
||||
@ -67,7 +85,6 @@ def collection(host, collection_uri, request_headers, x_auth_token,
|
||||
enforce_SSL)
|
||||
|
||||
while status < 300:
|
||||
|
||||
# verify expected type
|
||||
|
||||
# NOTE: Because of the Redfish standards effort, we have versioned
|
||||
@ -95,7 +112,6 @@ def collection(host, collection_uri, request_headers, x_auth_token,
|
||||
# vs. the Items in case a collection contains both.
|
||||
|
||||
if 'Items' in thecollection:
|
||||
|
||||
# iterate items
|
||||
for item in thecollection['Items']:
|
||||
# if the item has a self uri pointer, supply that for
|
||||
@ -143,12 +159,12 @@ def collection(host, collection_uri, request_headers, x_auth_token,
|
||||
raise exceptions.IloException("HTTP %s" % status)
|
||||
|
||||
|
||||
def get_mac_from_ilo(host_ip, x_auth_token, nic_index=0):
|
||||
def get_mac_from_ilo(host_ip, x_auth_token, nic_index=0, allow_insecure=False):
|
||||
# for each system in the systems collection at /rest/v1/Systems
|
||||
ilo_hardware = collection(host_ip, '/rest/v1/Systems', None,
|
||||
x_auth_token, enforce_SSL=False)
|
||||
x_auth_token, enforce_SSL=not allow_insecure)
|
||||
|
||||
for status, headers, system, memberuri in ilo_hardware:
|
||||
for status, headers, system, member_uri in ilo_hardware:
|
||||
# verify expected type
|
||||
# hint: don't limit to version 0 here as we will rev to 1.0 at
|
||||
# some point hopefully with minimal changes
|
||||
@ -164,12 +180,37 @@ def get_mac_from_ilo(host_ip, x_auth_token, nic_index=0):
|
||||
return system['HostCorrelation']['HostMACAddress'][nic_index]
|
||||
|
||||
|
||||
def set_onetime_boot(host_ip, x_auth_token, boot_target, allow_insecure=False):
|
||||
# for each system in the systems collection at /rest/v1/Systems
|
||||
ilo_hardware = collection(host_ip, '/rest/v1/Systems', None,
|
||||
x_auth_token, enforce_SSL=not allow_insecure)
|
||||
|
||||
for status, headers, system, member_uri in ilo_hardware:
|
||||
# verify expected type
|
||||
# hint: don't limit to version 0 here as we will rev to 1.0 at
|
||||
# some point hopefully with minimal changes
|
||||
assert(get_type(system) == 'ComputerSystem.0' or
|
||||
get_type(system) == 'ComputerSystem.1')
|
||||
|
||||
if boot_target not in system["Boot"]["BootSourceOverrideSupported"]:
|
||||
raise exceptions.IloException(
|
||||
"ERROR: %s is not a supported boot option.\n" % boot_target)
|
||||
else:
|
||||
body = {"Boot": {"BootSourceOverrideTarget": boot_target}}
|
||||
headers = {"Content-Type": "application/json"}
|
||||
status_code, _, response = rest_patch(host_ip, member_uri, headers,
|
||||
body, x_auth_token,
|
||||
not allow_insecure)
|
||||
if status_code != 200:
|
||||
raise exceptions.IloException(response)
|
||||
|
||||
|
||||
def ilo_logout(host_ip, x_auth_token):
|
||||
sessions = collection(host_ip, '/rest/v1/sessions', None, x_auth_token,
|
||||
enforce_SSL=False)
|
||||
for status, headers, member, memberuri in sessions:
|
||||
for status, headers, member, member_uri in sessions:
|
||||
if member.get('Oem').get('Hp').get('MySession') is True:
|
||||
status, headers, member = rest_delete(host_ip, memberuri, None,
|
||||
status, headers, member = rest_delete(host_ip, member_uri, None,
|
||||
x_auth_token, False)
|
||||
if status != 200:
|
||||
message = "iLO returned HTTP %s" % status
|
||||
|
@ -22,6 +22,7 @@ import unittest
|
||||
|
||||
from oneview_client import client
|
||||
from oneview_client import exceptions
|
||||
from oneview_client import ilo_utils
|
||||
from oneview_client import models
|
||||
from oneview_client.tests import fixtures
|
||||
from oneview_client import utils
|
||||
@ -287,6 +288,113 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
utils.get_uuid_from_uri(spt.get('uri'))
|
||||
)
|
||||
|
||||
@mock.patch.object(client.Client, '_wait_for_task_to_complete')
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_set_boot_device(self, mock_put, mock_get, mock__wait_for_task,
|
||||
mock__authenticate):
|
||||
oneview_client = client.Client(self.manager_url,
|
||||
self.username,
|
||||
self.password)
|
||||
response = mock_put.return_value
|
||||
response.status_code = http_client.OK
|
||||
mock_put.return_value = response
|
||||
|
||||
hardware = mock.MagicMock()
|
||||
hardware.status_code = http_client.OK
|
||||
hardware.json = mock.MagicMock(
|
||||
return_value=fixtures.SERVER_HARDWARE_LIST_JSON['members'][0]
|
||||
)
|
||||
profile = mock.MagicMock()
|
||||
profile.status_code = http_client.OK
|
||||
profile.json = mock.MagicMock(
|
||||
return_value=fixtures.SERVER_PROFILE_JSON
|
||||
)
|
||||
mock_get.side_effect = [hardware, profile, hardware, profile]
|
||||
oneview_client._wait_for_task_to_complete = mock__wait_for_task
|
||||
|
||||
node_info = {
|
||||
'server_hardware_uri':
|
||||
'/rest/server-hardware/30303437-3933-4753-4831-31315835524E'
|
||||
}
|
||||
|
||||
oneview_client.set_boot_device(node_info, 'PXE')
|
||||
mock_put.assert_called_once_with(
|
||||
self.manager_url + fixtures.SERVER_PROFILE_JSON.get('uri'),
|
||||
data=mock.ANY,
|
||||
headers=mock.ANY,
|
||||
verify=True
|
||||
)
|
||||
self.assertIn('["PXE", "CD", "Floppy", "USB", "HardDisk"]',
|
||||
mock_put.call_args[1]['data'])
|
||||
|
||||
@mock.patch.object(ilo_utils, 'ilo_logout')
|
||||
@mock.patch.object(client.Client, '_get_ilo_access')
|
||||
@mock.patch.object(client.Client, '_wait_for_task_to_complete')
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
@mock.patch.object(requests, 'patch', autospec=True)
|
||||
def test_set_boot_device_onetime(self, mock_patch, mock_get,
|
||||
mock__wait_for_task, mock_get_ilo_access,
|
||||
mock_ilo_logout, mock__authenticate):
|
||||
oneview_client = client.Client(self.manager_url,
|
||||
self.username,
|
||||
self.password)
|
||||
|
||||
hardware = mock.MagicMock()
|
||||
hardware.status_code = http_client.OK
|
||||
hardware.json = mock.MagicMock(
|
||||
return_value=fixtures.SERVER_HARDWARE_LIST_JSON['members'][0]
|
||||
)
|
||||
profile = mock.MagicMock()
|
||||
profile.status_code = http_client.OK
|
||||
profile.json = mock.MagicMock(
|
||||
return_value=fixtures.SERVER_PROFILE_JSON
|
||||
)
|
||||
ilo_system = mock.MagicMock()
|
||||
ilo_system.status_code = http_client.OK
|
||||
ilo_system.json = mock.MagicMock(
|
||||
return_value={
|
||||
"Type": "Collection.0",
|
||||
"Items": [
|
||||
{
|
||||
"links": {"self": {"href": "/rest/v1/Systems/1"}},
|
||||
"Type": "ComputerSystem.0",
|
||||
"Boot": {
|
||||
"BootSourceOverrideSupported": ["Hdd", "Cd"],
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
mock_get.side_effect = [hardware, profile, # hardware, profile,
|
||||
ilo_system]
|
||||
|
||||
response2 = mock_patch.return_value
|
||||
response2.status_code = http_client.OK
|
||||
mock_patch.return_value = response2
|
||||
|
||||
my_host = 'my-host'
|
||||
key = '123'
|
||||
mock_get_ilo_access.return_value = (my_host, key)
|
||||
|
||||
oneview_client._wait_for_task_to_complete = mock__wait_for_task
|
||||
|
||||
node_info = {
|
||||
'server_hardware_uri':
|
||||
'/rest/server-hardware/30303437-3933-4753-4831-31315835524E'
|
||||
}
|
||||
|
||||
oneview_client.set_boot_device(node_info, 'HardDisk', onetime=True)
|
||||
mock_patch.assert_called_once_with(
|
||||
'https://' + my_host + '/rest/v1/Systems/1',
|
||||
data='{"Boot": {"BootSourceOverrideTarget": "Hdd"}}',
|
||||
headers={
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': key},
|
||||
verify=True
|
||||
)
|
||||
mock_ilo_logout.assert_called()
|
||||
|
||||
|
||||
@mock.patch.object(client.ClientV2, '_authenticate', autospec=True)
|
||||
class OneViewClientV2TestCase(unittest.TestCase):
|
||||
|
@ -287,6 +287,7 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
self.oneview_client, uri="/rest/server-hardware/555"
|
||||
)
|
||||
|
||||
@mock.patch.object(client.Client, '_set_onetime_boot')
|
||||
@mock.patch.object(client.Client, '_wait_for_task_to_complete',
|
||||
autospec=True)
|
||||
@mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True)
|
||||
@ -295,22 +296,25 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
@mock.patch.object(client.Client, 'get_server_hardware', autospec=True)
|
||||
def test_set_boot_device(
|
||||
self, mock_get_server_hardware, mock_get_server_profile,
|
||||
mock__prepare_do_request, mock__wait_for_task
|
||||
mock__prepare_do_request, mock__wait_for_task, mock_set_onetime_boot
|
||||
):
|
||||
mock_get_server_hardware.return_value = (
|
||||
|
||||
server_hardware = (
|
||||
models.ServerHardware.from_json(fixtures.SERVER_HARDWARE_JSON)
|
||||
)
|
||||
mock_get_server_hardware.return_value = server_hardware
|
||||
mock_get_server_profile.return_value = (
|
||||
models.ServerProfile.from_json(fixtures.SERVER_PROFILE_JSON)
|
||||
)
|
||||
expected_profile = copy.deepcopy(fixtures.SERVER_PROFILE_JSON)
|
||||
expected_profile['boot'] = {
|
||||
'manageBoot': True,
|
||||
'order': ["USB", "CD", "Floppy", "HardDisk", "PXE"]
|
||||
# Original boot order is ["CD", "Floppy", "USB", "HardDisk", "PXE"]
|
||||
'order': ["PXE", "CD", "Floppy", "USB", "HardDisk"]
|
||||
}
|
||||
driver_info = {"server_hardware_uri": "/any"}
|
||||
new_first_boot_device = "USB"
|
||||
|
||||
new_first_boot_device = "PXE"
|
||||
# Persistent
|
||||
self.oneview_client.set_boot_device(driver_info, new_first_boot_device)
|
||||
mock__prepare_do_request.assert_called_once_with(
|
||||
self.oneview_client,
|
||||
@ -318,6 +322,32 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
request_type='PUT',
|
||||
uri='/rest/server-profiles/f2160e28-8107-45f9-b4b2-3119a622a3a1'
|
||||
)
|
||||
# Onetime
|
||||
new_first_boot_device = "USB"
|
||||
self.oneview_client.set_boot_device(driver_info, new_first_boot_device,
|
||||
onetime=True)
|
||||
mock_set_onetime_boot.assert_called_once_with(
|
||||
'any',
|
||||
new_first_boot_device
|
||||
)
|
||||
# Fallback in case onetime fails
|
||||
new_first_boot_device = "HardDisk"
|
||||
mock__prepare_do_request.reset_mock()
|
||||
mock_set_onetime_boot.reset_mock()
|
||||
expected_profile['boot'] = {
|
||||
'manageBoot': True,
|
||||
# Boot order should be ["PXE", "CD", "Floppy", "USB", "HardDisk"]
|
||||
'order': ["HardDisk", "PXE", "CD", "Floppy", "USB"]
|
||||
}
|
||||
mock_set_onetime_boot.side_effect = [exceptions.IloException("BOOM")]
|
||||
self.oneview_client.set_boot_device(driver_info, new_first_boot_device,
|
||||
onetime=True)
|
||||
mock__prepare_do_request.assert_called_once_with(
|
||||
self.oneview_client,
|
||||
body=expected_profile,
|
||||
request_type='PUT',
|
||||
uri='/rest/server-profiles/f2160e28-8107-45f9-b4b2-3119a622a3a1'
|
||||
)
|
||||
|
||||
@mock.patch.object(client.Client, '_prepare_and_do_request', autospec=True)
|
||||
@mock.patch.object(client.Client, 'get_server_profile_from_hardware',
|
||||
@ -356,23 +386,18 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
|
||||
@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
|
||||
self, mock_get_server_hardware, mock__prepare_do_request
|
||||
):
|
||||
driver_info = {}
|
||||
new_first_boot_device = "any_boot_device"
|
||||
mock_get_boot_order.return_value = []
|
||||
server_hardware = models.ServerHardware()
|
||||
setattr(server_hardware, 'server_profile_uri', None)
|
||||
mock_get_server_hardware.return_value = server_hardware
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.OneViewServerProfileAssociatedError,
|
||||
self.oneview_client.set_boot_device,
|
||||
driver_info,
|
||||
new_first_boot_device
|
||||
self.oneview_client.get_server_profile_from_hardware,
|
||||
driver_info
|
||||
)
|
||||
setattr(
|
||||
server_hardware,
|
||||
@ -384,9 +409,8 @@ class OneViewClientTestCase(unittest.TestCase):
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.OneViewResourceNotFoundError,
|
||||
self.oneview_client.set_boot_device,
|
||||
driver_info,
|
||||
new_first_boot_device
|
||||
self.oneview_client.get_server_profile_from_hardware,
|
||||
driver_info
|
||||
)
|
||||
|
||||
@mock.patch.object(requests, 'get')
|
||||
|
Loading…
x
Reference in New Issue
Block a user