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 =
|
||||
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_OFFLINE = 'Offline'
|
||||
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)
|
||||
|
||||
|
||||
class ExpEtherException(ValenceError):
|
||||
_msg_fmt = "ExpEther Exception"
|
||||
|
||||
|
||||
class ResourceExists(ValenceError):
|
||||
status = http_client.CONFLICT
|
||||
_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()
|
||||
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):
|
||||
"""Compose new node
|
||||
|
||||
@ -97,10 +71,10 @@ class Node(object):
|
||||
# "description" is optional
|
||||
description = request_body.get("description", "")
|
||||
|
||||
compose_request = self._create_compose_request(name, description,
|
||||
requirements)
|
||||
|
||||
composed_node = self.connection.compose_node(compose_request)
|
||||
# Moving _create_compose_request to drivers as this can be
|
||||
# vendor specific request
|
||||
composed_node = self.connection.compose_node(name, description,
|
||||
requirements)
|
||||
composed_node["uuid"] = utils.generate_uuid()
|
||||
|
||||
# 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})
|
||||
# Delete the nodes w.r.t podmanager from valence DB
|
||||
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)
|
||||
|
@ -113,7 +113,7 @@ class PooledDevices(object):
|
||||
db_api.Connection.add_device(dev)
|
||||
response['status'] = 'SUCCESS'
|
||||
|
||||
except exception.ValenceException as e:
|
||||
LOG.exception("Update devices failed with exception %s", str(e))
|
||||
except exception.ValenceError:
|
||||
LOG.exception("Failed to update resources from podm")
|
||||
response['status'] = 'FAILED'
|
||||
return response
|
||||
|
@ -186,6 +186,12 @@ class Flavor(ModelBaseWithTimeStamp):
|
||||
},
|
||||
'validate': types.Dict.validate
|
||||
},
|
||||
'pci_device': {
|
||||
'type': {
|
||||
'validate': types.List.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)
|
||||
|
||||
# TODO(): use rsd_lib here
|
||||
def compose_node(self, request_body):
|
||||
def compose_node(self, name, description, requirements):
|
||||
pass
|
||||
|
||||
# TODO(): use rsd_lib here
|
||||
|
@ -481,15 +481,49 @@ def build_hierarchy_tree():
|
||||
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.
|
||||
|
||||
:param request_body: The request content to compose new node, which should
|
||||
follow podm format. Valence api directly pass it to
|
||||
podm right now.
|
||||
:param name: name of node
|
||||
:param description: description of node if any
|
||||
:param requirements: additional requirements of node if any
|
||||
:returns: The numeric index of new composed node.
|
||||
"""
|
||||
|
||||
request_body = _create_compose_request(name, description, requirements)
|
||||
# Get url of allocating resource to node
|
||||
nodes_url = get_base_resource_url('Nodes')
|
||||
resp = send_request(nodes_url, 'GET')
|
||||
|
@ -47,37 +47,6 @@ class TestAPINodes(unittest.TestCase):
|
||||
self.assertEqual(expected,
|
||||
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.common.utils.generate_uuid")
|
||||
@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.common.utils.generate_uuid")
|
||||
@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):
|
||||
"""Test compose node successfully"""
|
||||
node_hw = node_fakes.get_test_composed_node()
|
||||
@ -129,13 +99,13 @@ class TestAPINodes(unittest.TestCase):
|
||||
"name": node_hw["name"],
|
||||
"resource_uri": node_hw["resource_uri"]}
|
||||
|
||||
compose_request = {'name': 'fake_name',
|
||||
'description': 'fake_description'}
|
||||
mock_redfish_compose_node.return_value = node_hw
|
||||
uuid = 'ea8e2a25-2901-438d-8157-de7ffd68d051'
|
||||
mock_generate_uuid.return_value = uuid
|
||||
|
||||
result = self.node_controller.compose_node(
|
||||
{"name": node_hw["name"],
|
||||
"description": node_hw["description"]})
|
||||
result = self.node_controller.compose_node(compose_request)
|
||||
expected = nodes.Node._show_node_brief_info(node_hw)
|
||||
|
||||
self.assertEqual(expected, result)
|
||||
|
@ -184,7 +184,7 @@ class TestPooledDevices(unittest.TestCase):
|
||||
mock_device_list,
|
||||
mock_pod_conn):
|
||||
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')
|
||||
expected = {'podm_id': 'podm_id', 'status': 'FAILED'}
|
||||
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": {
|
||||
"total_cores": "2",
|
||||
"model": "Intel"
|
||||
},
|
||||
"pci_device": {
|
||||
"type": ["SSD", "NIC"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,6 +50,9 @@ def fake_flavor_list():
|
||||
"processor": {
|
||||
"total_cores": "10",
|
||||
"model": "Intel"
|
||||
},
|
||||
"pci_device": {
|
||||
"type": ["NIC"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -61,6 +67,9 @@ def fake_flavor_list():
|
||||
"processor": {
|
||||
"total_cores": "20",
|
||||
"model": "Intel"
|
||||
},
|
||||
"pci_device": {
|
||||
"type": ["SSD"]
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -75,6 +84,9 @@ def fake_flavor_list():
|
||||
"processor": {
|
||||
"total_cores": "30",
|
||||
"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]
|
||||
|
||||
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 "
|
||||
"allocation request." in str(context.exception.detail))
|
||||
@ -381,7 +381,7 @@ class TestRedfish(TestCase):
|
||||
fake_node_assemble_failed]
|
||||
|
||||
with self.assertRaises(exception.RedfishException):
|
||||
redfish.compose_node({"name": "test_node"})
|
||||
redfish.compose_node('test_node', '', {})
|
||||
|
||||
mock_delete_node.assert_called_once()
|
||||
|
||||
@ -416,7 +416,7 @@ class TestRedfish(TestCase):
|
||||
fake_node_detail,
|
||||
fake_node_assemble_failed]
|
||||
|
||||
redfish.compose_node({"name": "test_node"})
|
||||
redfish.compose_node('test_node', '', {})
|
||||
|
||||
mock_delete_node.assert_not_called()
|
||||
mock_get_node_by_id.assert_called_once()
|
||||
@ -666,3 +666,33 @@ class TestRedfish(TestCase):
|
||||
]
|
||||
result = redfish.show_rack("2")
|
||||
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,
|
||||
},
|
||||
'pci_device': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {'type': 'array'}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
@ -122,6 +129,13 @@ compose_node_with_properties = {
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
'pci_device': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {'type': 'array'}
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user