Add ExpEther Driver in Valence
This commit adds ExpEther driver support inside valence to be able to manage NEC's ExpEther hardware. This would fit into valence support of multi-podm architecture. Change-Id: Ie8eb4d50e88d52aefc3732fd07cd8ed1ad5aee6f Implements: blueprint add-expether-driver
This commit is contained in:
parent
ecd965149b
commit
a5cc27bf41
@ -68,3 +68,4 @@ valence.provision.driver =
|
|||||||
|
|
||||||
valence.podmanager.driver =
|
valence.podmanager.driver =
|
||||||
redfishv1 = valence.podmanagers.podm_base:PodManagerBase
|
redfishv1 = valence.podmanagers.podm_base:PodManagerBase
|
||||||
|
expether = valence.podmanagers.expether_manager:ExpEtherManager
|
||||||
|
@ -17,3 +17,7 @@ PODM_AUTH_BASIC_TYPE = 'basic'
|
|||||||
PODM_STATUS_ONLINE = 'Online'
|
PODM_STATUS_ONLINE = 'Online'
|
||||||
PODM_STATUS_OFFLINE = 'Offline'
|
PODM_STATUS_OFFLINE = 'Offline'
|
||||||
PODM_STATUS_UNKNOWN = "Unknown"
|
PODM_STATUS_UNKNOWN = "Unknown"
|
||||||
|
|
||||||
|
HTTP_HEADERS = {"Content-type": "application/json"}
|
||||||
|
|
||||||
|
DEVICE_STATES = {'ALLOCATED': 'allocated', 'FREE': 'free'}
|
||||||
|
@ -110,6 +110,10 @@ class RedfishException(ValenceError):
|
|||||||
request_id)
|
request_id)
|
||||||
|
|
||||||
|
|
||||||
|
class ExpEtherException(ValenceError):
|
||||||
|
_msg_fmt = "ExpEther Exception"
|
||||||
|
|
||||||
|
|
||||||
class ResourceExists(ValenceError):
|
class ResourceExists(ValenceError):
|
||||||
status = http_client.CONFLICT
|
status = http_client.CONFLICT
|
||||||
_msg_fmt = "Resource Already Exists"
|
_msg_fmt = "Resource Already Exists"
|
||||||
|
71
valence/common/http_adapter.py
Normal file
71
valence/common/http_adapter.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Copyright (c) 2017 NEC, Corp.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from six.moves import http_client
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
OK = http_client.OK
|
||||||
|
CREATED = http_client.CREATED
|
||||||
|
NO_CONTENT = http_client.NO_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
def get(url, http_auth, **kwargs):
|
||||||
|
try:
|
||||||
|
return requests.request('GET', url, verify=False, auth=http_auth,
|
||||||
|
**kwargs)
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
|
def patch(url, http_auth, **kwargs):
|
||||||
|
try:
|
||||||
|
return requests.request('PATCH', url, verify=False, auth=http_auth,
|
||||||
|
**kwargs)
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
|
def post(url, http_auth, data=None, **kwargs):
|
||||||
|
try:
|
||||||
|
return requests.request('POST', url, data=data, verify=False,
|
||||||
|
auth=http_auth, **kwargs)
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
|
def delete(url, http_auth, **kwargs):
|
||||||
|
try:
|
||||||
|
return requests.request('DELETE', url, verify=False, auth=http_auth,
|
||||||
|
**kwargs)
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
|
||||||
|
def put(url, http_auth, **kwargs):
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
try:
|
||||||
|
return requests.request('PUT', url, verify=False, headers=headers,
|
||||||
|
auth=http_auth, **kwargs)
|
||||||
|
except requests.exceptions.RequestException as ex:
|
||||||
|
LOG.error(ex)
|
||||||
|
raise ex
|
@ -49,32 +49,6 @@ class Node(object):
|
|||||||
return {key: node_info[key] for key in node_info.keys()
|
return {key: node_info[key] for key in node_info.keys()
|
||||||
if key in ["uuid", "name", "podm_id", "index", "resource_uri"]}
|
if key in ["uuid", "name", "podm_id", "index", "resource_uri"]}
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _create_compose_request(name, description, requirements):
|
|
||||||
request = {}
|
|
||||||
|
|
||||||
request["Name"] = name
|
|
||||||
request["Description"] = description
|
|
||||||
|
|
||||||
memory = {}
|
|
||||||
if "memory" in requirements:
|
|
||||||
if "capacity_mib" in requirements["memory"]:
|
|
||||||
memory["CapacityMiB"] = requirements["memory"]["capacity_mib"]
|
|
||||||
if "type" in requirements["memory"]:
|
|
||||||
memory["DimmDeviceType"] = requirements["memory"]["type"]
|
|
||||||
request["Memory"] = [memory]
|
|
||||||
|
|
||||||
processor = {}
|
|
||||||
if "processor" in requirements:
|
|
||||||
if "model" in requirements["processor"]:
|
|
||||||
processor["Model"] = requirements["processor"]["model"]
|
|
||||||
if "total_cores" in requirements["processor"]:
|
|
||||||
processor["TotalCores"] = (
|
|
||||||
requirements["processor"]["total_cores"])
|
|
||||||
request["Processors"] = [processor]
|
|
||||||
|
|
||||||
return request
|
|
||||||
|
|
||||||
def compose_node(self, request_body):
|
def compose_node(self, request_body):
|
||||||
"""Compose new node
|
"""Compose new node
|
||||||
|
|
||||||
@ -97,10 +71,10 @@ class Node(object):
|
|||||||
# "description" is optional
|
# "description" is optional
|
||||||
description = request_body.get("description", "")
|
description = request_body.get("description", "")
|
||||||
|
|
||||||
compose_request = self._create_compose_request(name, description,
|
# Moving _create_compose_request to drivers as this can be
|
||||||
requirements)
|
# vendor specific request
|
||||||
|
composed_node = self.connection.compose_node(name, description,
|
||||||
composed_node = self.connection.compose_node(compose_request)
|
requirements)
|
||||||
composed_node["uuid"] = utils.generate_uuid()
|
composed_node["uuid"] = utils.generate_uuid()
|
||||||
|
|
||||||
# Only store the minimum set of composed node info into backend db,
|
# Only store the minimum set of composed node info into backend db,
|
||||||
|
@ -80,6 +80,12 @@ def delete_podmanager(uuid):
|
|||||||
p_nodes = db_api.Connection.list_composed_nodes({'podm_id': uuid})
|
p_nodes = db_api.Connection.list_composed_nodes({'podm_id': uuid})
|
||||||
# Delete the nodes w.r.t podmanager from valence DB
|
# Delete the nodes w.r.t podmanager from valence DB
|
||||||
for node in p_nodes:
|
for node in p_nodes:
|
||||||
nodes.Node(node['uuid']).delete_composed_node(node['uuid'])
|
nodes.Node(node['uuid']).delete_composed_node()
|
||||||
|
|
||||||
|
# Delete the devices w.r.t podmanager from valence DB
|
||||||
|
devices_list = db_api.Connection.list_devices(
|
||||||
|
filters={'podm_id': uuid})
|
||||||
|
for device in devices_list:
|
||||||
|
db_api.Connection.delete_device(device['uuid'])
|
||||||
|
|
||||||
return db_api.Connection.delete_podmanager(uuid)
|
return db_api.Connection.delete_podmanager(uuid)
|
||||||
|
@ -113,7 +113,7 @@ class PooledDevices(object):
|
|||||||
db_api.Connection.add_device(dev)
|
db_api.Connection.add_device(dev)
|
||||||
response['status'] = 'SUCCESS'
|
response['status'] = 'SUCCESS'
|
||||||
|
|
||||||
except exception.ValenceException as e:
|
except exception.ValenceError:
|
||||||
LOG.exception("Update devices failed with exception %s", str(e))
|
LOG.exception("Failed to update resources from podm")
|
||||||
response['status'] = 'FAILED'
|
response['status'] = 'FAILED'
|
||||||
return response
|
return response
|
||||||
|
@ -186,6 +186,12 @@ class Flavor(ModelBaseWithTimeStamp):
|
|||||||
},
|
},
|
||||||
'validate': types.Dict.validate
|
'validate': types.Dict.validate
|
||||||
},
|
},
|
||||||
|
'pci_device': {
|
||||||
|
'type': {
|
||||||
|
'validate': types.List.validate
|
||||||
|
},
|
||||||
|
'validate': types.Dict.validate
|
||||||
|
},
|
||||||
'validate': types.Dict.validate
|
'validate': types.Dict.validate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
537
valence/podmanagers/expether_manager.py
Normal file
537
valence/podmanagers/expether_manager.py
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
# Copyright (c) 2017 NEC, Corp.
|
||||||
|
#
|
||||||
|
# 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 datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from oslo_utils import importutils
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from valence.common import constants
|
||||||
|
from valence.common import exception
|
||||||
|
from valence.common import http_adapter as http
|
||||||
|
from valence.db import api as db_api
|
||||||
|
|
||||||
|
hwdata = importutils.try_import('hwdata', default=None)
|
||||||
|
|
||||||
|
# Free devices group_id
|
||||||
|
EEIO_DEFAULT_GID = '4093'
|
||||||
|
EESV_DEFAULT_GID = '4094'
|
||||||
|
|
||||||
|
# device_type (PCI Base Class Code)
|
||||||
|
type_SSD = 0x1
|
||||||
|
type_NIC = 0x2
|
||||||
|
type_GPU = 0x3
|
||||||
|
type_MULTI = 0x4
|
||||||
|
type_SERIAL = 0xC
|
||||||
|
type_USB = 0x3
|
||||||
|
type_ACS = 0x12
|
||||||
|
type_SWITCH = 0x6
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExpEtherManager(object):
|
||||||
|
def __init__(self, username, password, url):
|
||||||
|
self.url = url
|
||||||
|
self.auth = requests.auth.HTTPBasicAuth(username, password)
|
||||||
|
|
||||||
|
def _send_request_to_eem(self, query, method='get', **kwargs):
|
||||||
|
"""Prepare request and send it to EEM
|
||||||
|
|
||||||
|
:param query: request URI of API
|
||||||
|
:param method: method with which request is sent
|
||||||
|
:param kwargs: additional values if any
|
||||||
|
:return: response in json format
|
||||||
|
"""
|
||||||
|
request_url = '/'.join([self.url, query])
|
||||||
|
f = getattr(http, method)
|
||||||
|
response = f(request_url, self.auth, **kwargs)
|
||||||
|
self._handle_exceptions(response)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def compose_node(self, name, description, requirements):
|
||||||
|
"""Compose node according to flavor
|
||||||
|
|
||||||
|
Attaches the devices as per the flavor specified and returns the
|
||||||
|
EESV node information.
|
||||||
|
|
||||||
|
:param name: name of node
|
||||||
|
:param description: description if any
|
||||||
|
:param requirements: format {'pci_device': {'type': ['NIC', 'SSD']}}
|
||||||
|
:return: allocated node info as dict
|
||||||
|
:raises: ExpEtherException , if eesv not available or requested device
|
||||||
|
is not available(e.g SSD)
|
||||||
|
"""
|
||||||
|
# New key 'PCIDevice' is added to support ExpEther devices.
|
||||||
|
|
||||||
|
# Get available EESV list and choose first available node
|
||||||
|
# TODO(ramineni): work on load balancing part
|
||||||
|
eesv_list = self._send_request_to_eem(
|
||||||
|
query="devices?status=eesv")['devices']
|
||||||
|
nodes_index = [node['index'] for node in
|
||||||
|
db_api.Connection.list_composed_nodes()]
|
||||||
|
|
||||||
|
available_eesv = None
|
||||||
|
for eesv in eesv_list:
|
||||||
|
if eesv['id'] not in nodes_index:
|
||||||
|
available_eesv = eesv
|
||||||
|
break
|
||||||
|
if available_eesv is None:
|
||||||
|
LOG.exception("EESV node not available to compose a node")
|
||||||
|
raise exception.ExpEtherException(status=404, code="40400",
|
||||||
|
detail="Node not available")
|
||||||
|
|
||||||
|
# Attach device according to flavor or properties in request
|
||||||
|
if ('pci_device' in requirements and
|
||||||
|
'type' in requirements['pci_device']):
|
||||||
|
req_pci_type = requirements['pci_device']['type']
|
||||||
|
for pci_type in req_pci_type:
|
||||||
|
dev_filters = {'pooled_group_id': EEIO_DEFAULT_GID,
|
||||||
|
'type': pci_type}
|
||||||
|
eeio_list = db_api.Connection.list_devices(dev_filters)
|
||||||
|
if not eeio_list:
|
||||||
|
LOG.exception("Device type: %s not existing in database",
|
||||||
|
pci_type)
|
||||||
|
raise exception.ExpEtherException(status=404, code="40400",
|
||||||
|
detail="Requested device"
|
||||||
|
" not available")
|
||||||
|
|
||||||
|
self.attach(device_db=eeio_list[0],
|
||||||
|
node_index=available_eesv['id'])
|
||||||
|
|
||||||
|
# If properties is empty, return the existing available eesv node.
|
||||||
|
index = available_eesv['id']
|
||||||
|
composed_node = {'name': name, 'index': index,
|
||||||
|
'resource_uri': 'devices/' + index}
|
||||||
|
return composed_node
|
||||||
|
|
||||||
|
def get_node_info(self, node_index):
|
||||||
|
"""To get existing eesv nodes details to manage it via valence
|
||||||
|
|
||||||
|
:param node_index: EESV index
|
||||||
|
:return: dict of eesv details
|
||||||
|
"""
|
||||||
|
query = 'devices/' + node_index
|
||||||
|
node_info = self._send_request_to_eem(query)['device']
|
||||||
|
|
||||||
|
if node_info['status'] == 'eeio':
|
||||||
|
LOG.exception("EEIO device id %s passed instead of eesv",
|
||||||
|
node_index)
|
||||||
|
raise exception.ExpEtherException("Not a valid EESV node %s"
|
||||||
|
% node_index)
|
||||||
|
|
||||||
|
node_detail = {'name': node_info['id'],
|
||||||
|
'resource_uri': query,
|
||||||
|
'serial_number': node_info['serial_number'],
|
||||||
|
'power_state': node_info['power_status'],
|
||||||
|
'host_model': node_info['host_model'],
|
||||||
|
'host_serial_number': node_info['host_serial_number'],
|
||||||
|
'index': node_index,
|
||||||
|
'description': node_info['model'],
|
||||||
|
'type': node_info['type'],
|
||||||
|
'mac_address': node_info['mac_address'],
|
||||||
|
'pooled_group_id': node_info['group_id']
|
||||||
|
}
|
||||||
|
return node_detail
|
||||||
|
|
||||||
|
def delete_composed_node(self, node_id):
|
||||||
|
"""Delete composed node with respective node index
|
||||||
|
|
||||||
|
Function it perform:-
|
||||||
|
Detaches all connected devices to this node
|
||||||
|
(updates group id of device in valence db and EEM to 4093)
|
||||||
|
|
||||||
|
:param node_id: index of the node
|
||||||
|
:raises ExpEtherException if detaching devices fails
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._detach_all_devices_from_node(node_id)
|
||||||
|
except Exception as e:
|
||||||
|
raise exception.ExpEtherException("Composed node %s deletion "
|
||||||
|
"failed with error : %s"
|
||||||
|
% (node_id, e.detail))
|
||||||
|
|
||||||
|
def node_action(self, node_index, request):
|
||||||
|
"""Attaches and detaches device to a node
|
||||||
|
|
||||||
|
:param node_index: eesv node index
|
||||||
|
:param request: Contains type_of_action(attach or detach) and
|
||||||
|
resource_id
|
||||||
|
Sample request:
|
||||||
|
{"detach":
|
||||||
|
{"resource_id": "660a95b3-adaa-42d3-ac3f-dfe7ce6c9986"}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# NOTE: type_of_action can be attach or detach
|
||||||
|
action = list(request.keys())[0]
|
||||||
|
if action not in ['attach', 'detach']:
|
||||||
|
LOG.exception("Unsupported action: %s", action)
|
||||||
|
raise exception.BadRequest(detail="Unsupported action: " + action)
|
||||||
|
|
||||||
|
device_db = {}
|
||||||
|
if request[action].get('resource_id'):
|
||||||
|
device_uuid = request[action]['resource_id']
|
||||||
|
device_db = db_api.Connection.get_device_by_uuid(
|
||||||
|
device_uuid).as_dict()
|
||||||
|
|
||||||
|
f = getattr(self, action, None)
|
||||||
|
f(device_db, node_index)
|
||||||
|
|
||||||
|
def systems_list(self, filters=None):
|
||||||
|
"""Retrieves list of all connected eesv systems
|
||||||
|
|
||||||
|
:return: List of connected eesv's
|
||||||
|
:raises ExpEtherException if unable to fetch system details
|
||||||
|
"""
|
||||||
|
query = "devices/detail?status=eesv"
|
||||||
|
try:
|
||||||
|
results = []
|
||||||
|
eesv_list = self._send_request_to_eem(query)['devices']
|
||||||
|
for eesv in eesv_list:
|
||||||
|
results.append(self._system_dict(eesv))
|
||||||
|
return results
|
||||||
|
|
||||||
|
except exception.ExpEtherException as e:
|
||||||
|
message = "Listing eesv nodes failed with error: %s" % e.detail
|
||||||
|
raise exception.ExpEtherException(code=e.code, detail=message,
|
||||||
|
status=e.status)
|
||||||
|
|
||||||
|
def get_system_by_id(self, system_id):
|
||||||
|
"""Get system detail by system_id provided by user
|
||||||
|
|
||||||
|
:param system_id: User provided system_id
|
||||||
|
:return: eesv node info
|
||||||
|
:raises ExpEtherException if unable to fetch details
|
||||||
|
"""
|
||||||
|
query = 'devices/' + system_id
|
||||||
|
system = self._send_request_to_eem(query=query)['device']
|
||||||
|
if system['status'] == 'eeio':
|
||||||
|
LOG.exception("eeio device id %s passed instead of eesv",
|
||||||
|
system_id)
|
||||||
|
raise exception.ExpEtherException("Not a valid EESV node %s"
|
||||||
|
% system_id)
|
||||||
|
update_time = self._convert_time_format(system['update_time'])
|
||||||
|
system = {'id': system['id'],
|
||||||
|
'type': system['type'],
|
||||||
|
'pooled_group_id': system['group_id'],
|
||||||
|
'mac_address': system['mac_address'],
|
||||||
|
'serial_number': system['serial_number'],
|
||||||
|
'name': system['status'],
|
||||||
|
'power_state': system['power_status'],
|
||||||
|
'host_model': system['host_model'],
|
||||||
|
'host_serial_number': system['host_serial_number'],
|
||||||
|
'description': system['model'],
|
||||||
|
'ee_version': system['ee_version'],
|
||||||
|
'update_time': update_time
|
||||||
|
}
|
||||||
|
return system
|
||||||
|
|
||||||
|
def attach(self, device_db, node_index):
|
||||||
|
"""Attaches device to requested node
|
||||||
|
|
||||||
|
Performs two actions:
|
||||||
|
1) Sets group id of eeio device in EEM
|
||||||
|
2) Updates device info in valence DB
|
||||||
|
:param device_db: device info from valence DB
|
||||||
|
:param node_index: EESV id to which device needs to be attached
|
||||||
|
:raises BadRequest if devices is not free
|
||||||
|
"""
|
||||||
|
LOG.debug("Attach device %s to node %s", device_db['uuid'],
|
||||||
|
node_index)
|
||||||
|
device_id = device_db['properties']['device_id']
|
||||||
|
if (device_db['node_id'] is not None or
|
||||||
|
device_db['pooled_group_id'] != EEIO_DEFAULT_GID):
|
||||||
|
raise exception.BadRequest(
|
||||||
|
detail="Device %s already assigned to a different node: %s"
|
||||||
|
% (device_db['uuid'], device_db['node_id']))
|
||||||
|
node_query = 'devices/' + node_index
|
||||||
|
eesv = self._send_request_to_eem(node_query)['device']
|
||||||
|
# Set group id of eesv if it is default
|
||||||
|
if eesv['group_id'] == EESV_DEFAULT_GID:
|
||||||
|
eesv['group_id'] = self._set_gid(device_id=node_index,
|
||||||
|
group_id=None)['group_id']
|
||||||
|
LOG.warning('Group ID of an EESV %s has been updated, a reboot'
|
||||||
|
'might be required for changes to take effect',
|
||||||
|
node_index)
|
||||||
|
# Check if maximum number of devices are already connected to eesv
|
||||||
|
max_eeio_count = eesv['max_eeio_count']
|
||||||
|
query = "devices?status=eeio&group_id=" + eesv['group_id']
|
||||||
|
eeio_list = self._send_request_to_eem(query)['devices']
|
||||||
|
eeio_count = len(eeio_list)
|
||||||
|
if eeio_count >= int(max_eeio_count):
|
||||||
|
LOG.exception("Only %s devices that can be attached to node %s",
|
||||||
|
max_eeio_count, node_index)
|
||||||
|
message = ("Node %s has already maximun number of devices attached"
|
||||||
|
% node_index)
|
||||||
|
raise exception.ExpEtherException(title='Internal server error',
|
||||||
|
detail=message, status=500)
|
||||||
|
self._set_gid(device_id=device_id, group_id=eesv['group_id'])
|
||||||
|
update_dev_info = {"pooled_group_id": eesv['group_id'],
|
||||||
|
"node_id": node_index,
|
||||||
|
"state": constants.DEVICE_STATES['ALLOCATED']
|
||||||
|
}
|
||||||
|
db_api.Connection.update_device(device_db['uuid'], update_dev_info)
|
||||||
|
|
||||||
|
def detach(self, device_db, node_index=None):
|
||||||
|
"""Detaches device from requested node
|
||||||
|
|
||||||
|
Performs two function:
|
||||||
|
1) Delete group id of eeio device in EEM
|
||||||
|
2) Updates device group id in valence DB
|
||||||
|
:param device_db: Valence DB entry of device which is to be updated
|
||||||
|
:param node_index: None
|
||||||
|
"""
|
||||||
|
LOG.debug("Detach device %s from node %s", device_db['uuid'],
|
||||||
|
node_index)
|
||||||
|
device_id = device_db['properties']['device_id']
|
||||||
|
device_uuid = device_db['uuid']
|
||||||
|
if device_db['pooled_group_id'] == EEIO_DEFAULT_GID:
|
||||||
|
LOG.debug("Device %s is not attached to any node" % device_uuid)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._del_gid(device_id)
|
||||||
|
update_dev_info = {"pooled_group_id": EEIO_DEFAULT_GID,
|
||||||
|
"node_id": None,
|
||||||
|
"state": constants.DEVICE_STATES['FREE']
|
||||||
|
}
|
||||||
|
db_api.Connection.update_device(device_uuid, update_dev_info)
|
||||||
|
|
||||||
|
def _handle_exceptions(self, response):
|
||||||
|
"""Handles exceptions from http requests
|
||||||
|
|
||||||
|
:param response: output of the request
|
||||||
|
:raises AuthorizationFailure: if invalid credentials are passed
|
||||||
|
ExpEtherException: if any HTTPError occurs
|
||||||
|
"""
|
||||||
|
if response.status_code == 401:
|
||||||
|
raise exception.AuthorizationFailure("Invalid credentials passed")
|
||||||
|
try:
|
||||||
|
resp = response.json()
|
||||||
|
response.raise_for_status()
|
||||||
|
except requests.exceptions.HTTPError:
|
||||||
|
detail = resp['message']
|
||||||
|
code = resp['code']
|
||||||
|
LOG.exception(detail)
|
||||||
|
raise exception.ExpEtherException(code=code, detail=detail,
|
||||||
|
status=response.status_code)
|
||||||
|
|
||||||
|
def _set_gid(self, device_id, group_id):
|
||||||
|
"""Updates the group id of the device
|
||||||
|
|
||||||
|
Set the group id of EEIO device as of EESV to which it is going to
|
||||||
|
connect.
|
||||||
|
:param device_id: the device id on which group id is to updated
|
||||||
|
:param group_id: group_id which is to be assigned
|
||||||
|
:raises ExpEtherException if any HTTPError occurs
|
||||||
|
"""
|
||||||
|
return self._send_request_to_eem("devices/" + device_id + "/group_id",
|
||||||
|
'put', json={"group_id": group_id})
|
||||||
|
|
||||||
|
def _del_gid(self, device_id):
|
||||||
|
"""Deletes the group id of the device
|
||||||
|
|
||||||
|
Sends delete request to the url of device, which updates
|
||||||
|
group id of device to 4093
|
||||||
|
:param device_id: of device which is to be detached from node
|
||||||
|
:raises ExpEtherException if any HTTPError occurs
|
||||||
|
"""
|
||||||
|
return self._send_request_to_eem("devices/" + device_id + "/group_id",
|
||||||
|
'delete')
|
||||||
|
|
||||||
|
def _get_device_type(self, pci_code):
|
||||||
|
"""Gives device type based on its PCI code
|
||||||
|
|
||||||
|
:param pci_code: PCI code of device from eem
|
||||||
|
:return: type of device(e.g SSD, NIC)
|
||||||
|
"""
|
||||||
|
class_code = int(pci_code, 0)
|
||||||
|
c0 = class_code / (256 * 256)
|
||||||
|
if c0 == type_NIC:
|
||||||
|
return 'NIC'
|
||||||
|
elif c0 == type_SSD:
|
||||||
|
return 'SSD'
|
||||||
|
elif c0 == type_GPU:
|
||||||
|
return 'GPU'
|
||||||
|
elif c0 == type_MULTI:
|
||||||
|
return 'Multi'
|
||||||
|
elif c0 == type_SERIAL: # and ccode[1] == type_USB:
|
||||||
|
return 'USB'
|
||||||
|
elif c0 == type_ACS:
|
||||||
|
return 'ACS'
|
||||||
|
elif c0 == type_SWITCH:
|
||||||
|
return 'SWITCH'
|
||||||
|
else:
|
||||||
|
return 'Unknown'
|
||||||
|
|
||||||
|
def _detach_all_devices_from_node(self, node_id):
|
||||||
|
"""Detaches all devices from the node
|
||||||
|
|
||||||
|
Fetches all connected devices to node, deletes group_id
|
||||||
|
:param node_id: index of the node
|
||||||
|
"""
|
||||||
|
db_dev_list = db_api.Connection.list_devices(
|
||||||
|
filters={'node_id': node_id})
|
||||||
|
for db_device in db_dev_list:
|
||||||
|
self.detach(db_device)
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
"""Checks ExpEtherManager Status
|
||||||
|
|
||||||
|
Issues command to check version of EEM.
|
||||||
|
:return: on or off status of pod_manager
|
||||||
|
:raises AuthorizationFailure: if wrong credentials are passed
|
||||||
|
ExpEtherException: if any HTTPError
|
||||||
|
"""
|
||||||
|
error_message = "unable to reach podmanager at url: {} with error: {}"
|
||||||
|
try:
|
||||||
|
self._send_request_to_eem('api_version')
|
||||||
|
return constants.PODM_STATUS_ONLINE
|
||||||
|
# check for wrong ip and offline status
|
||||||
|
except exception.AuthorizationFailure as e:
|
||||||
|
raise exception.AuthorizationFailure(
|
||||||
|
detail=error_message.format(self.url, e.detail))
|
||||||
|
except exception.ExpEtherException as e:
|
||||||
|
raise exception.ExpEtherException(
|
||||||
|
code=e.code, detail=error_message.format(self.url, e.detail),
|
||||||
|
status=e.status)
|
||||||
|
|
||||||
|
def _system_dict(self, device):
|
||||||
|
"""Converts the resource details into desired dictionary format
|
||||||
|
|
||||||
|
:param device: resource data from eem
|
||||||
|
:return: Dictionary in desired format
|
||||||
|
"""
|
||||||
|
|
||||||
|
system = {'id': device['id'],
|
||||||
|
'resource_uri': 'devices/' + device['id'],
|
||||||
|
'pooled_group_id': device['group_id'],
|
||||||
|
'type': device['type'],
|
||||||
|
'mac_address': device['mac_address'],
|
||||||
|
'host_serial_num': device['host_serial_number'],
|
||||||
|
'host_model': device['host_model']
|
||||||
|
}
|
||||||
|
return system
|
||||||
|
|
||||||
|
def _get_device_info(self, vendor_id, device_id):
|
||||||
|
"""Calculates vendor and device name
|
||||||
|
|
||||||
|
Using python-hwdata retrieve information of 40G devices
|
||||||
|
:param vendor_id: field 'pcie_vendor_id' value from EEM
|
||||||
|
:param device_id: field 'pcie_device_id' value from EEM
|
||||||
|
:return: Vendor and device name
|
||||||
|
"""
|
||||||
|
vendor_name = ''
|
||||||
|
device_name = ''
|
||||||
|
if not hwdata:
|
||||||
|
LOG.warning("hwdata module not available, unable to get the"
|
||||||
|
"vendor details")
|
||||||
|
return vendor_name, device_name
|
||||||
|
|
||||||
|
vendor_id = hex(int(vendor_id, 16))[2:].zfill(4)
|
||||||
|
device_id = hex(int(device_id, 16))[2:].zfill(4)
|
||||||
|
pci = hwdata.PCI()
|
||||||
|
vendor_name = pci.get_vendor(vendor_id)
|
||||||
|
device_name = pci.get_device(vendor_id, device_id)
|
||||||
|
return vendor_name, device_name
|
||||||
|
|
||||||
|
def get_all_devices(self):
|
||||||
|
"""Get all eeio devices connected to eem."""
|
||||||
|
devices = []
|
||||||
|
eeios = self._send_request_to_eem(query="devices/detail?status=eeio")
|
||||||
|
for eeio in eeios['devices']:
|
||||||
|
if eeio['notification_status0'][0] == 'down':
|
||||||
|
continue
|
||||||
|
extra = dict()
|
||||||
|
state, eesv_id = self._check_eeio_state(eeio['group_id'])
|
||||||
|
# If 40g, retreive device details, else None in case of 10g
|
||||||
|
device_type = None
|
||||||
|
if eeio["type"] == '40g':
|
||||||
|
device_type = self._get_device_type(eeio["pcie_class_code"])
|
||||||
|
vendor_name, device_name = self._get_device_info(
|
||||||
|
eeio["pcie_vendor_id"], eeio["pcie_device_id"])
|
||||||
|
extra['vendor_name'] = vendor_name
|
||||||
|
extra['device_name'] = device_name
|
||||||
|
|
||||||
|
properties = dict()
|
||||||
|
properties['device_id'] = eeio['id']
|
||||||
|
properties['mac_address'] = eeio['mac_address']
|
||||||
|
properties['model'] = eeio['type']
|
||||||
|
|
||||||
|
values = {"type": device_type,
|
||||||
|
"pooled_group_id": eeio['group_id'],
|
||||||
|
"node_id": eesv_id,
|
||||||
|
"resource_uri": 'devices/' + eeio['id'],
|
||||||
|
"state": state,
|
||||||
|
"extra": extra,
|
||||||
|
"properties": properties,
|
||||||
|
}
|
||||||
|
devices.append(values)
|
||||||
|
return devices
|
||||||
|
|
||||||
|
def _check_eeio_state(self, group_id):
|
||||||
|
"""Checks if eeio device is free or allocated to node
|
||||||
|
|
||||||
|
:param group_id: pooled group id of device
|
||||||
|
:return: state i.e allocated or free and eesv_id if allocated
|
||||||
|
"""
|
||||||
|
state = constants.DEVICE_STATES['FREE']
|
||||||
|
eesv_id = None
|
||||||
|
if group_id != EEIO_DEFAULT_GID:
|
||||||
|
state = constants.DEVICE_STATES['ALLOCATED']
|
||||||
|
query = "devices?status=eesv&group_id=" + group_id
|
||||||
|
result = self._send_request_to_eem(query)['devices']
|
||||||
|
if result:
|
||||||
|
eesv_id = result[0]['id']
|
||||||
|
return state, eesv_id
|
||||||
|
|
||||||
|
def get_ironic_node_params(self, node_info, **param):
|
||||||
|
"""Get ironic node params to register to ironic
|
||||||
|
|
||||||
|
:param node_info: eesv node info
|
||||||
|
:param param:
|
||||||
|
Eg:{"driver_info":
|
||||||
|
{"ipmi_address": "xxx.xxx.xx.xx",
|
||||||
|
"ipmi_username": "xxxxx",
|
||||||
|
"ipmi_password": "xxxxx"},
|
||||||
|
"mac": "11:11:11:11:11:11",
|
||||||
|
"driver": "agent_ipmitool"}
|
||||||
|
:return: node and port arguments
|
||||||
|
"""
|
||||||
|
driver = param.pop('driver', 'pxe_ipmitool')
|
||||||
|
if not param.get('driver_info'):
|
||||||
|
raise exception.ExpEtherException(
|
||||||
|
detail='Missing driver_info in params %s' % node_info['uuid'],
|
||||||
|
status=400)
|
||||||
|
driver_info = param.pop('driver_info')
|
||||||
|
|
||||||
|
node_args = {'name': node_info['name'],
|
||||||
|
'driver': driver,
|
||||||
|
'driver_info': driver_info}
|
||||||
|
if param.get('mac', None):
|
||||||
|
# MAC provided, create ironic ports
|
||||||
|
port_args = {'address': param['mac']}
|
||||||
|
return node_args, port_args
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _convert_time_format(timestamp):
|
||||||
|
"""Convert time elapsed since 1/1/1970 to readable format
|
||||||
|
|
||||||
|
:param timestamp: Update time from eem device
|
||||||
|
:return: readable time format
|
||||||
|
"""
|
||||||
|
value = int(timestamp)/1000.0
|
||||||
|
return datetime.datetime.utcfromtimestamp(value).strftime(
|
||||||
|
'%Y-%m-%d %H:%M:%S')
|
@ -33,7 +33,7 @@ class PodManagerBase(object):
|
|||||||
return self.get_resource_info_by_url(self.podm_url)
|
return self.get_resource_info_by_url(self.podm_url)
|
||||||
|
|
||||||
# TODO(): use rsd_lib here
|
# TODO(): use rsd_lib here
|
||||||
def compose_node(self, request_body):
|
def compose_node(self, name, description, requirements):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# TODO(): use rsd_lib here
|
# TODO(): use rsd_lib here
|
||||||
|
@ -481,15 +481,49 @@ def build_hierarchy_tree():
|
|||||||
return podmtree
|
return podmtree
|
||||||
|
|
||||||
|
|
||||||
def compose_node(request_body):
|
def _create_compose_request(name, description, requirements):
|
||||||
|
"""Generate compose node request following podm format
|
||||||
|
|
||||||
|
:param name: name of node
|
||||||
|
:param description: description of node if any
|
||||||
|
:param requirements: additional requirements of node if any
|
||||||
|
:return: request body to compose node
|
||||||
|
"""
|
||||||
|
request = {}
|
||||||
|
|
||||||
|
request["Name"] = name
|
||||||
|
request["Description"] = description
|
||||||
|
|
||||||
|
memory = {}
|
||||||
|
if "memory" in requirements:
|
||||||
|
if "capacity_mib" in requirements["memory"]:
|
||||||
|
memory["CapacityMiB"] = requirements["memory"]["capacity_mib"]
|
||||||
|
if "type" in requirements["memory"]:
|
||||||
|
memory["DimmDeviceType"] = requirements["memory"]["type"]
|
||||||
|
request["Memory"] = [memory]
|
||||||
|
|
||||||
|
processor = {}
|
||||||
|
if "processor" in requirements:
|
||||||
|
if "model" in requirements["processor"]:
|
||||||
|
processor["Model"] = requirements["processor"]["model"]
|
||||||
|
if "total_cores" in requirements["processor"]:
|
||||||
|
processor["TotalCores"] = (
|
||||||
|
requirements["processor"]["total_cores"])
|
||||||
|
request["Processors"] = [processor]
|
||||||
|
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
def compose_node(name, description, requirements):
|
||||||
"""Compose new node through podm api.
|
"""Compose new node through podm api.
|
||||||
|
|
||||||
:param request_body: The request content to compose new node, which should
|
:param name: name of node
|
||||||
follow podm format. Valence api directly pass it to
|
:param description: description of node if any
|
||||||
podm right now.
|
:param requirements: additional requirements of node if any
|
||||||
:returns: The numeric index of new composed node.
|
:returns: The numeric index of new composed node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
request_body = _create_compose_request(name, description, requirements)
|
||||||
# Get url of allocating resource to node
|
# Get url of allocating resource to node
|
||||||
nodes_url = get_base_resource_url('Nodes')
|
nodes_url = get_base_resource_url('Nodes')
|
||||||
resp = send_request(nodes_url, 'GET')
|
resp = send_request(nodes_url, 'GET')
|
||||||
|
@ -47,37 +47,6 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
self.assertEqual(expected,
|
self.assertEqual(expected,
|
||||||
nodes.Node._show_node_brief_info(node_info))
|
nodes.Node._show_node_brief_info(node_info))
|
||||||
|
|
||||||
def test_create_compose_request(self):
|
|
||||||
name = "test_request"
|
|
||||||
description = "request for testing purposes"
|
|
||||||
requirements = {
|
|
||||||
"memory": {
|
|
||||||
"capacity_mib": "4000",
|
|
||||||
"type": "DDR3"
|
|
||||||
},
|
|
||||||
"processor": {
|
|
||||||
"model": "Intel",
|
|
||||||
"total_cores": "4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expected = {
|
|
||||||
"Name": "test_request",
|
|
||||||
"Description": "request for testing purposes",
|
|
||||||
"Memory": [{
|
|
||||||
"CapacityMiB": "4000",
|
|
||||||
"DimmDeviceType": "DDR3"
|
|
||||||
}],
|
|
||||||
"Processors": [{
|
|
||||||
"Model": "Intel",
|
|
||||||
"TotalCores": "4"
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
result = nodes.Node._create_compose_request(name,
|
|
||||||
description,
|
|
||||||
requirements)
|
|
||||||
self.assertEqual(expected, result)
|
|
||||||
|
|
||||||
@mock.patch("valence.db.api.Connection.create_composed_node")
|
@mock.patch("valence.db.api.Connection.create_composed_node")
|
||||||
@mock.patch("valence.common.utils.generate_uuid")
|
@mock.patch("valence.common.utils.generate_uuid")
|
||||||
@mock.patch("valence.controller.nodes.Node.list_composed_nodes")
|
@mock.patch("valence.controller.nodes.Node.list_composed_nodes")
|
||||||
@ -119,7 +88,8 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
@mock.patch("valence.db.api.Connection.create_composed_node")
|
@mock.patch("valence.db.api.Connection.create_composed_node")
|
||||||
@mock.patch("valence.common.utils.generate_uuid")
|
@mock.patch("valence.common.utils.generate_uuid")
|
||||||
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.compose_node")
|
@mock.patch("valence.podmanagers.podm_base.PodManagerBase.compose_node")
|
||||||
def test_compose_node(self, mock_redfish_compose_node, mock_generate_uuid,
|
def test_compose_node(self, mock_redfish_compose_node,
|
||||||
|
mock_generate_uuid,
|
||||||
mock_db_create_composed_node):
|
mock_db_create_composed_node):
|
||||||
"""Test compose node successfully"""
|
"""Test compose node successfully"""
|
||||||
node_hw = node_fakes.get_test_composed_node()
|
node_hw = node_fakes.get_test_composed_node()
|
||||||
@ -129,13 +99,13 @@ class TestAPINodes(unittest.TestCase):
|
|||||||
"name": node_hw["name"],
|
"name": node_hw["name"],
|
||||||
"resource_uri": node_hw["resource_uri"]}
|
"resource_uri": node_hw["resource_uri"]}
|
||||||
|
|
||||||
|
compose_request = {'name': 'fake_name',
|
||||||
|
'description': 'fake_description'}
|
||||||
mock_redfish_compose_node.return_value = node_hw
|
mock_redfish_compose_node.return_value = node_hw
|
||||||
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
|
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
|
||||||
mock_generate_uuid.return_value = uuid
|
mock_generate_uuid.return_value = uuid
|
||||||
|
|
||||||
result = self.node_controller.compose_node(
|
result = self.node_controller.compose_node(compose_request)
|
||||||
{"name": node_hw["name"],
|
|
||||||
"description": node_hw["description"]})
|
|
||||||
expected = nodes.Node._show_node_brief_info(node_hw)
|
expected = nodes.Node._show_node_brief_info(node_hw)
|
||||||
|
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
@ -184,7 +184,7 @@ class TestPooledDevices(unittest.TestCase):
|
|||||||
mock_device_list,
|
mock_device_list,
|
||||||
mock_pod_conn):
|
mock_pod_conn):
|
||||||
mock_device_list.return_value = [fakes.fake_device()]
|
mock_device_list.return_value = [fakes.fake_device()]
|
||||||
mock_pod_conn.side_effect = exception.ValenceException('fake_detail')
|
mock_pod_conn.side_effect = exception.ValenceError('fake_detail')
|
||||||
result = pooled_devices.PooledDevices.update_device_info('podm_id')
|
result = pooled_devices.PooledDevices.update_device_info('podm_id')
|
||||||
expected = {'podm_id': 'podm_id', 'status': 'FAILED'}
|
expected = {'podm_id': 'podm_id', 'status': 'FAILED'}
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
40
valence/tests/unit/fakes/expether_fakes.py
Normal file
40
valence/tests/unit/fakes/expether_fakes.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
def fake_eesv_list():
|
||||||
|
return {"devices": [{"id": "0x1111111111",
|
||||||
|
"status": "eesv",
|
||||||
|
"update_time": "1518999910510",
|
||||||
|
"mac_address": "11:11:11:11:11:11",
|
||||||
|
"group_id": "1234",
|
||||||
|
"type": "40g",
|
||||||
|
"power_status": "on",
|
||||||
|
"ee_version": "v1.0",
|
||||||
|
"device_id": "0x00000",
|
||||||
|
"serial_number": "abcd 01234",
|
||||||
|
"model": "ExpEther Board (40G)",
|
||||||
|
"max_eeio_count": "16",
|
||||||
|
"host_serial_number": "",
|
||||||
|
"host_model": "",
|
||||||
|
"notification_status0": ["up", "down"],
|
||||||
|
"notification_status1": ["down", "down"]},
|
||||||
|
{"id": "0x2222222222",
|
||||||
|
"status": "eesv",
|
||||||
|
"update_time": "1518999910510",
|
||||||
|
"mac_address": "22:22:22:22:22:22",
|
||||||
|
"group_id": "5678",
|
||||||
|
"type": "10g",
|
||||||
|
"power_status": "on",
|
||||||
|
"ee_version": "v1.0",
|
||||||
|
"device_id": "0x00000",
|
||||||
|
"serial_number": "abcd 01234",
|
||||||
|
"model": "ExpEther Board (10G)",
|
||||||
|
"max_eeio_count": "8",
|
||||||
|
"host_serial_number": "",
|
||||||
|
"host_model": "",
|
||||||
|
"notification_status0": ["up", "down"],
|
||||||
|
"notification_status1": ["down", "down"]}
|
||||||
|
],
|
||||||
|
"timestamp": "1521089295162"}
|
||||||
|
|
||||||
|
|
||||||
|
def fake_eesv():
|
||||||
|
return {"device": fake_eesv_list()['devices'][0],
|
||||||
|
"timestamp": "1521089295162"}
|
@ -25,6 +25,9 @@ def fake_flavor():
|
|||||||
"processor": {
|
"processor": {
|
||||||
"total_cores": "2",
|
"total_cores": "2",
|
||||||
"model": "Intel"
|
"model": "Intel"
|
||||||
|
},
|
||||||
|
"pci_device": {
|
||||||
|
"type": ["SSD", "NIC"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +50,9 @@ def fake_flavor_list():
|
|||||||
"processor": {
|
"processor": {
|
||||||
"total_cores": "10",
|
"total_cores": "10",
|
||||||
"model": "Intel"
|
"model": "Intel"
|
||||||
|
},
|
||||||
|
"pci_device": {
|
||||||
|
"type": ["NIC"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -61,6 +67,9 @@ def fake_flavor_list():
|
|||||||
"processor": {
|
"processor": {
|
||||||
"total_cores": "20",
|
"total_cores": "20",
|
||||||
"model": "Intel"
|
"model": "Intel"
|
||||||
|
},
|
||||||
|
"pci_device": {
|
||||||
|
"type": ["SSD"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -75,6 +84,9 @@ def fake_flavor_list():
|
|||||||
"processor": {
|
"processor": {
|
||||||
"total_cores": "30",
|
"total_cores": "30",
|
||||||
"model": "Intel"
|
"model": "Intel"
|
||||||
|
},
|
||||||
|
"pci_device": {
|
||||||
|
"type": ["SSD", "NIC"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
392
valence/tests/unit/podmanagers/test_expether_manager.py
Normal file
392
valence/tests/unit/podmanagers/test_expether_manager.py
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
# Copyright (c) 2017 NEC, Corp.
|
||||||
|
#
|
||||||
|
# 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 copy
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import etcd
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from valence.common import exception
|
||||||
|
from valence.podmanagers import expether_manager
|
||||||
|
from valence.tests.unit.fakes import device_fakes
|
||||||
|
from valence.tests.unit.fakes import expether_fakes
|
||||||
|
from valence.tests.unit.fakes import node_fakes
|
||||||
|
|
||||||
|
|
||||||
|
class TestExpEtherManager(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.expether_manager = expether_manager.ExpEtherManager(
|
||||||
|
'username', 'password', 'http://fake_url')
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.list_composed_nodes')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_compose_node(self, mock_eesv_list, mock_list_composed_node):
|
||||||
|
mock_eesv_list.return_value = {"devices": [{"id": "1"}, {"id": "2"},
|
||||||
|
{"id": "3"}, {"id": "4"}],
|
||||||
|
"timestamp": "1520845301785"}
|
||||||
|
mock_list_composed_node.return_value = node_fakes.get_test_node_list()
|
||||||
|
result = self.expether_manager.compose_node('node4', '', {})
|
||||||
|
expected = {'name': 'node4', 'index': '4', 'resource_uri': 'devices/4'}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.list_composed_nodes')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_compose_node_no_free_eesv(self, mock_eesv_list, mock_db_list):
|
||||||
|
mock_eesv_list.return_value = {"devices": [{"id": "1"}, {"id": "2"},
|
||||||
|
{"id": "3"}],
|
||||||
|
"timestamp": "1520845301785"}
|
||||||
|
mock_db_list.return_value = node_fakes.get_test_node_list()
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.compose_node,
|
||||||
|
'node4', '', {})
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.attach')
|
||||||
|
@mock.patch('valence.db.api.Connection.list_devices')
|
||||||
|
@mock.patch('valence.db.api.Connection.list_composed_nodes')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_compose_node_with_attach_pci_device(self, mock_eesv_list,
|
||||||
|
mock_list_composed_node,
|
||||||
|
mock_list_devices,
|
||||||
|
mock_attach):
|
||||||
|
mock_eesv_list.return_value = {'devices': [{'id': '1'}, {'id': '2'},
|
||||||
|
{'id': '3'}, {'id': '4'}],
|
||||||
|
'timestamp': '1520845301785'}
|
||||||
|
mock_list_composed_node.return_value = node_fakes.get_test_node_list()
|
||||||
|
db_device = device_fakes.fake_device_list()[1]
|
||||||
|
db_device2 = copy.deepcopy(db_device)
|
||||||
|
db_device2['type'] = 'SSD'
|
||||||
|
mock_list_devices.side_effect = [[db_device], [db_device2]]
|
||||||
|
result = self.expether_manager.compose_node(
|
||||||
|
'node4', '', {'pci_device': {'type': ['NIC', 'SSD']}})
|
||||||
|
expected = {'name': 'node4', 'index': '4',
|
||||||
|
'resource_uri': 'devices/4'}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
self.assertEqual(mock_attach.call_count, 2)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._send_request_to_eem')
|
||||||
|
def test_get_node_info(self, mock_get_eesv):
|
||||||
|
mock_get_eesv.return_value = expether_fakes.fake_eesv()
|
||||||
|
result = self.expether_manager.get_node_info('0x1111111111')
|
||||||
|
expected = {'name': '0x1111111111',
|
||||||
|
'resource_uri': 'devices/0x1111111111',
|
||||||
|
'serial_number': 'abcd 01234',
|
||||||
|
'power_state': 'on',
|
||||||
|
'host_model': '',
|
||||||
|
'host_serial_number': '',
|
||||||
|
'index': '0x1111111111',
|
||||||
|
'description': 'ExpEther Board (40G)',
|
||||||
|
'type': '40g',
|
||||||
|
'mac_address': '11:11:11:11:11:11',
|
||||||
|
'pooled_group_id': '1234'
|
||||||
|
}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._send_request_to_eem')
|
||||||
|
def test_get_node_info_with_invalid_node(self, mock_get_eesv):
|
||||||
|
eesv_info = expether_fakes.fake_eesv()
|
||||||
|
eesv_info['device']['status'] = 'eeio'
|
||||||
|
mock_get_eesv.return_value = eesv_info
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.get_node_info, '0x1111111111')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._detach_all_devices_from_node')
|
||||||
|
def test_delete_composed_node(self, mock_detach_devices):
|
||||||
|
self.expether_manager.delete_composed_node('node_id')
|
||||||
|
mock_detach_devices.assert_called_once_with('node_id')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._detach_all_devices_from_node')
|
||||||
|
def test_delete_composed_node_with_exception(self, mock_detach_devices):
|
||||||
|
mock_detach_devices.side_effect = exception.ExpEtherException()
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.delete_composed_node,
|
||||||
|
'node_id')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.attach')
|
||||||
|
@mock.patch('valence.db.api.Connection.get_device_by_uuid')
|
||||||
|
def test_node_action_attach(self, mock_db_device, mock_attach):
|
||||||
|
device_obj = device_fakes.fake_device_obj()
|
||||||
|
mock_db_device.return_value = device_obj
|
||||||
|
request_body = {"attach": {
|
||||||
|
"resource_id": "00000000-0000-0000-0000-000000000000"}}
|
||||||
|
self.expether_manager.node_action('0x12345', request_body)
|
||||||
|
mock_attach.assert_called_once_with(device_obj.as_dict(), '0x12345')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.detach')
|
||||||
|
@mock.patch('valence.db.api.Connection.get_device_by_uuid')
|
||||||
|
def test_node_action_detach(self, mock_db_device, mock_detach):
|
||||||
|
device_obj = device_fakes.fake_device_obj()
|
||||||
|
mock_db_device.return_value = device_obj
|
||||||
|
request_body = {"detach": {
|
||||||
|
"resource_id": "00000000-0000-0000-0000-000000000000"}}
|
||||||
|
self.expether_manager.node_action('0x12345', request_body)
|
||||||
|
mock_detach.assert_called_once_with(device_obj.as_dict(), '0x12345')
|
||||||
|
|
||||||
|
def test_node_action_with_unsupported_action(self):
|
||||||
|
request_body = {"run": {
|
||||||
|
"resource_id": "00000000-0000-0000-0000-000000000000"}}
|
||||||
|
self.assertRaises(exception.BadRequest,
|
||||||
|
self.expether_manager.node_action,
|
||||||
|
'0x12345', request_body)
|
||||||
|
|
||||||
|
@mock.patch('etcd.Client.read')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.attach')
|
||||||
|
def test_node_action_with_non_existing_resource(self, mock_attach,
|
||||||
|
mock_etcd_read):
|
||||||
|
mock_etcd_read.side_effect = etcd.EtcdKeyNotFound
|
||||||
|
self.assertRaises(exception.NotFound,
|
||||||
|
self.expether_manager.node_action,
|
||||||
|
'0x12345', {"attach": {"resource_id": "000-00000"}})
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_system_list(self, mock_eesv_list):
|
||||||
|
mock_eesv_list.return_value = expether_fakes.fake_eesv_list()
|
||||||
|
result = self.expether_manager.systems_list()
|
||||||
|
expected = [{'id': '0x1111111111',
|
||||||
|
'resource_uri': 'devices/0x1111111111',
|
||||||
|
'pooled_group_id': '1234',
|
||||||
|
'type': '40g',
|
||||||
|
'mac_address': '11:11:11:11:11:11',
|
||||||
|
'host_model': '',
|
||||||
|
'host_serial_num': '',
|
||||||
|
},
|
||||||
|
{'id': '0x2222222222',
|
||||||
|
'resource_uri': 'devices/0x2222222222',
|
||||||
|
'pooled_group_id': '5678',
|
||||||
|
'type': '10g',
|
||||||
|
'mac_address': '22:22:22:22:22:22',
|
||||||
|
'host_model': '',
|
||||||
|
'host_serial_num': '',
|
||||||
|
}]
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_system_list_with_exception(self, mock_eesv_list):
|
||||||
|
mock_eesv_list.side_effect = exception.ExpEtherException
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.systems_list)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_get_system_by_id(self, mock_eesv):
|
||||||
|
mock_eesv.return_value = expether_fakes.fake_eesv()
|
||||||
|
result = self.expether_manager.get_system_by_id('0x1111111111')
|
||||||
|
expected = {'id': '0x1111111111',
|
||||||
|
'type': '40g',
|
||||||
|
'pooled_group_id': '1234',
|
||||||
|
'mac_address': '11:11:11:11:11:11',
|
||||||
|
'serial_number': 'abcd 01234',
|
||||||
|
'name': 'eesv',
|
||||||
|
'power_state': 'on',
|
||||||
|
'host_model': '',
|
||||||
|
'host_serial_number': '',
|
||||||
|
'description': 'ExpEther Board (40G)',
|
||||||
|
'ee_version': 'v1.0',
|
||||||
|
'update_time': '2018-02-19 00:25:10'
|
||||||
|
}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_get_system_by_id_with_invalid_system(self, mock_eesv):
|
||||||
|
system = expether_fakes.fake_eesv()
|
||||||
|
system['device']['status'] = 'eeio'
|
||||||
|
mock_eesv.return_value = system
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.get_system_by_id,
|
||||||
|
'0x1111111111')
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.update_device')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_attach(self, mock_req_to_eem, mock_update_device):
|
||||||
|
mock_req_to_eem.side_effect = [expether_fakes.fake_eesv(),
|
||||||
|
{"devices": [{"id": "0x8cdf9d911cb0"},
|
||||||
|
{"id": "0x8cdf9d53e9d8"}],
|
||||||
|
"timestamp": "1521117379726"}, None]
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['pooled_group_id'] = '4093'
|
||||||
|
self.expether_manager.attach(device, '0x1111111111')
|
||||||
|
mock_update_device.assert_called_once_with(
|
||||||
|
device['uuid'], {'pooled_group_id': '1234',
|
||||||
|
'node_id': '0x1111111111',
|
||||||
|
'state': 'allocated'})
|
||||||
|
|
||||||
|
def test_attach_with_device_already_attached_to_node(self):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['node_id'] = 'fake_node_id'
|
||||||
|
device['pooled_group_id'] = '1234'
|
||||||
|
self.assertRaises(exception.BadRequest, self.expether_manager.attach,
|
||||||
|
device, '0x1111111111')
|
||||||
|
|
||||||
|
def test_attach_with_device_with_non_default_gid(self):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['pooled_group_id'] = '1234'
|
||||||
|
self.assertRaises(exception.BadRequest, self.expether_manager.attach,
|
||||||
|
device, '0x1111111111')
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.update_device')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._set_gid')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_attach_to_node_with_default_gid(self, mock_req_to_eem,
|
||||||
|
mock_set_gid,
|
||||||
|
mock_update_device):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['pooled_group_id'] = '4093'
|
||||||
|
eesv = expether_fakes.fake_eesv()
|
||||||
|
eesv['device']['group_id'] = '4094'
|
||||||
|
mock_req_to_eem.side_effect = [eesv,
|
||||||
|
{"devices": [{"id": "0x8cdf9d911cb0"},
|
||||||
|
{"id": "0x8cdf9d53e9d8"}],
|
||||||
|
"timestamp": "1521117379726"}]
|
||||||
|
mock_set_gid.return_value = {'group_id': '678'}
|
||||||
|
self.expether_manager.attach(device, '0x1111111111')
|
||||||
|
mock_set_gid.assert_any_call(device_id='0x1111111111', group_id=None)
|
||||||
|
mock_set_gid.assert_any_call(device_id='0x7777777777', group_id='678')
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.update_device')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_attach_to_node_with_max_devices_attached(self, mock_req_to_eem,
|
||||||
|
mock_update_device):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['pooled_group_id'] = '4093'
|
||||||
|
eesv = expether_fakes.fake_eesv()
|
||||||
|
eesv['device']['max_eeio_count'] = '2'
|
||||||
|
mock_req_to_eem.side_effect = [eesv,
|
||||||
|
{"devices": [{"id": "0x8cdf9d911cb0"},
|
||||||
|
{"id": "0x8cdf9d53e9d8"}],
|
||||||
|
"timestamp": "1521117379726"}]
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.attach,
|
||||||
|
device, '0x1111111111')
|
||||||
|
|
||||||
|
@mock.patch('valence.db.api.Connection.update_device')
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager'
|
||||||
|
'._del_gid')
|
||||||
|
def test_detach(self, mock_del_gid, mock_update_device):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
self.expether_manager.detach(device)
|
||||||
|
mock_del_gid.assert_called_once_with(device['properties']['device_id'])
|
||||||
|
mock_update_device.assert_called_once_with(device['uuid'],
|
||||||
|
{'pooled_group_id': '4093',
|
||||||
|
'node_id': None,
|
||||||
|
'state': 'free'})
|
||||||
|
|
||||||
|
def test_detach_with_device_already_detached(self):
|
||||||
|
device = device_fakes.fake_device()
|
||||||
|
device['pooled_group_id'] = '4093'
|
||||||
|
device['node_id'] = None
|
||||||
|
self.expether_manager.detach(device)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test__set_gid(self, mock_request_to_eem):
|
||||||
|
self.expether_manager._set_gid('dev_id', 'gid')
|
||||||
|
mock_request_to_eem.assert_called_once_with('devices/dev_id/group_id',
|
||||||
|
'put',
|
||||||
|
json={'group_id': 'gid'})
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test__del_gid(self, mock_request_to_eem):
|
||||||
|
self.expether_manager._del_gid('dev_id')
|
||||||
|
mock_request_to_eem.assert_called_once_with('devices/dev_id/group_id',
|
||||||
|
'delete')
|
||||||
|
|
||||||
|
def test__get_device_type(self):
|
||||||
|
result = self.expether_manager._get_device_type('0x20000')
|
||||||
|
self.assertEqual(result, 'NIC')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.detach')
|
||||||
|
@mock.patch('valence.db.api.Connection.list_devices')
|
||||||
|
def test__detach_all_devices_from_node(self, mock_db_devices, mock_detach):
|
||||||
|
dev_list = device_fakes.fake_device_list()
|
||||||
|
mock_db_devices.return_value = dev_list
|
||||||
|
self.expether_manager._detach_all_devices_from_node('node_id')
|
||||||
|
mock_detach.assert_has_calls([mock.call(dev_list[0]),
|
||||||
|
mock.call(dev_list[1])])
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_get_status(self, mock_request_to_eem):
|
||||||
|
result = self.expether_manager.get_status()
|
||||||
|
self.assertEqual(result, 'Online')
|
||||||
|
mock_request_to_eem.assert_called_once_with('api_version')
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_get_status_with_auth_exception(self, mock_request_to_eem):
|
||||||
|
mock_request_to_eem.side_effect = exception.AuthorizationFailure
|
||||||
|
self.assertRaises(exception.AuthorizationFailure,
|
||||||
|
self.expether_manager.get_status)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test_get_status_with_expether_exception(self, mock_request_to_eem):
|
||||||
|
mock_request_to_eem.side_effect = exception.ExpEtherException
|
||||||
|
self.assertRaises(exception.ExpEtherException,
|
||||||
|
self.expether_manager.get_status)
|
||||||
|
|
||||||
|
def test__system_dict(self):
|
||||||
|
result = self.expether_manager._system_dict(
|
||||||
|
expether_fakes.fake_eesv()['device'])
|
||||||
|
expected = {'id': '0x1111111111',
|
||||||
|
'resource_uri': 'devices/0x1111111111',
|
||||||
|
'pooled_group_id': '1234',
|
||||||
|
'type': '40g',
|
||||||
|
'mac_address': '11:11:11:11:11:11',
|
||||||
|
'host_serial_num': '',
|
||||||
|
'host_model': ''
|
||||||
|
}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
@mock.patch('valence.podmanagers.expether_manager.ExpEtherManager.'
|
||||||
|
'_send_request_to_eem')
|
||||||
|
def test__check_eeio_state(self, mock_request_to_eem):
|
||||||
|
mock_request_to_eem.return_value = {"devices": [{"id": "012345678"}],
|
||||||
|
"timestamp": "1521178770450"}
|
||||||
|
state, eesv_id = self.expether_manager._check_eeio_state('1234')
|
||||||
|
self.assertEqual(state, 'allocated')
|
||||||
|
self.assertEqual(eesv_id, '012345678')
|
||||||
|
|
||||||
|
def test_get_ironic_node_params(self):
|
||||||
|
request_body = {"driver_info": {"ipmi_address": "xxx.xxx.xx.xx",
|
||||||
|
"ipmi_username": "xxxxx",
|
||||||
|
"ipmi_password": "xxxxx"},
|
||||||
|
"mac": "11:11:11:11:11:11",
|
||||||
|
"driver": "agent_ipmitool"}
|
||||||
|
node, port = self.expether_manager.get_ironic_node_params(
|
||||||
|
{'name': 'node1'}, **request_body)
|
||||||
|
expected_node = {'name': 'node1',
|
||||||
|
'driver': 'agent_ipmitool',
|
||||||
|
'driver_info': request_body['driver_info']}
|
||||||
|
self.assertEqual(node, expected_node)
|
||||||
|
self.assertEqual(port, {'address': '11:11:11:11:11:11'})
|
||||||
|
|
||||||
|
def test__convert_time_format(self):
|
||||||
|
result = self.expether_manager._convert_time_format('1518999910510')
|
||||||
|
self.assertEqual(result, '2018-02-19 00:25:10')
|
@ -345,7 +345,7 @@ class TestRedfish(TestCase):
|
|||||||
fake_node_allocation_conflict]
|
fake_node_allocation_conflict]
|
||||||
|
|
||||||
with self.assertRaises(exception.RedfishException) as context:
|
with self.assertRaises(exception.RedfishException) as context:
|
||||||
redfish.compose_node({"name": "test_node"})
|
redfish.compose_node('test_node', '', {})
|
||||||
|
|
||||||
self.assertTrue("There are no computer systems available for this "
|
self.assertTrue("There are no computer systems available for this "
|
||||||
"allocation request." in str(context.exception.detail))
|
"allocation request." in str(context.exception.detail))
|
||||||
@ -381,7 +381,7 @@ class TestRedfish(TestCase):
|
|||||||
fake_node_assemble_failed]
|
fake_node_assemble_failed]
|
||||||
|
|
||||||
with self.assertRaises(exception.RedfishException):
|
with self.assertRaises(exception.RedfishException):
|
||||||
redfish.compose_node({"name": "test_node"})
|
redfish.compose_node('test_node', '', {})
|
||||||
|
|
||||||
mock_delete_node.assert_called_once()
|
mock_delete_node.assert_called_once()
|
||||||
|
|
||||||
@ -416,7 +416,7 @@ class TestRedfish(TestCase):
|
|||||||
fake_node_detail,
|
fake_node_detail,
|
||||||
fake_node_assemble_failed]
|
fake_node_assemble_failed]
|
||||||
|
|
||||||
redfish.compose_node({"name": "test_node"})
|
redfish.compose_node('test_node', '', {})
|
||||||
|
|
||||||
mock_delete_node.assert_not_called()
|
mock_delete_node.assert_not_called()
|
||||||
mock_get_node_by_id.assert_called_once()
|
mock_get_node_by_id.assert_called_once()
|
||||||
@ -666,3 +666,33 @@ class TestRedfish(TestCase):
|
|||||||
]
|
]
|
||||||
result = redfish.show_rack("2")
|
result = redfish.show_rack("2")
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
||||||
|
def test__create_compose_request(self):
|
||||||
|
name = "test_request"
|
||||||
|
description = "request for testing purposes"
|
||||||
|
requirements = {
|
||||||
|
"memory": {
|
||||||
|
"capacity_mib": "4000",
|
||||||
|
"type": "DDR3"
|
||||||
|
},
|
||||||
|
"processor": {
|
||||||
|
"model": "Intel",
|
||||||
|
"total_cores": "4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"Name": "test_request",
|
||||||
|
"Description": "request for testing purposes",
|
||||||
|
"Memory": [{
|
||||||
|
"CapacityMiB": "4000",
|
||||||
|
"DimmDeviceType": "DDR3"
|
||||||
|
}],
|
||||||
|
"Processors": [{
|
||||||
|
"Model": "Intel",
|
||||||
|
"TotalCores": "4"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
result = redfish._create_compose_request(name, description,
|
||||||
|
requirements)
|
||||||
|
self.assertEqual(expected, result)
|
||||||
|
@ -38,6 +38,13 @@ flavor_schema = {
|
|||||||
},
|
},
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
},
|
},
|
||||||
|
'pci_device': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'type': {'type': 'array'}
|
||||||
|
},
|
||||||
|
'additionalProperties': False,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
},
|
},
|
||||||
@ -122,6 +129,13 @@ compose_node_with_properties = {
|
|||||||
},
|
},
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
},
|
},
|
||||||
|
'pci_device': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {
|
||||||
|
'type': {'type': 'array'}
|
||||||
|
},
|
||||||
|
'additionalProperties': False,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user