Sysinv extensions for FPGA support

This update adds cli and restapi to support FPGA device
programming.

CLI commands:
system device-image-apply
system device-image-create
system device-image-delete
system device-image-list
system device-image-remove
system device-image-show
system device-image-state-list
system device-label-list
system host-device-image-update
system host-device-image-update-abort
system host-device-label-assign
system host-device-label-list
system host-device-label-remove

Story: 2006740
Task: 39498

Change-Id: I556c2e7a51b3931b5a66ab27b67f51e3a8aebd9f
Signed-off-by: Teresa Ho <teresa.ho@windriver.com>
This commit is contained in:
Teresa Ho 2020-03-31 10:08:57 -04:00
parent bc9cde71a0
commit d141e954fa
53 changed files with 4343 additions and 35 deletions

View File

@ -2036,7 +2036,7 @@ itemNotFound (404)
::
{
{
"istors":[
{
"function":"osd",
@ -5721,6 +5721,14 @@ itemNotFound (404)
"links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage."
"created_at (Optional)", "plain", "xsd:dateTime", "The time when the object was created."
"updated_at (Optional)", "plain", "xsd:dateTime", "The time when the object was last updated."
"needs_firmware_update (optional) ", "plain", "xsd:string", "Indicates whether the device requires firmware update."
"status (optional) ", "plain", "xsd:string", "The status of firmware update of the device."
"root_key (optional) ", "plain", "xsd:string", "The root key of the FPGA device."
"revoked_key_ids (optional) ", "plain", "xsd:string", "The revoked key ids of the FPGA device."
"boot_page (optional) ", "plain", "xsd:string", "The boot page of the FPGA device."
"bitstream_id (optional) ", "plain", "xsd:string", "The bitstream id of the FPGA device."
"bmc_build_version (optional) ", "plain", "xsd:string", "The BMC build version of the FPGA device."
"bmc_fw_version (optional) ", "plain", "xsd:string", "The BMC firmware version of the FPGA device."
::
@ -6109,7 +6117,47 @@ itemNotFound (404)
"psvendor": "",
"enabled": "False",
"name": "pci_0000_00_0b_0"
}
},
{
"links": [
{
"href": "http://192.168.204.1:6385/v1/pci_devices/3ab614a6-3906-4c55-8114-4d78a6dde445",
"rel": "self"
},
{
"href": "http://192.168.204.1:6385/pci_devices/3ab614a6-3906-4c55-8114-4d78a6dde445",
"rel": "bookmark"
}
],
"enabled": true,
"updated_at": "2020-05-04T18:54:03.679744+00:00",
"needs_firmware_update": false,
"bitstream_id": null,
"uuid": "3ab614a6-3906-4c55-8114-4d78a6dde445",
"pdevice": "Device 0b30",
"boot_page": null,
"psvendor": "Intel Corporation",
"psdevice": "Device 0000",
"pclass_id": "120000",
"pvendor": "Intel Corporation",
"status": null,
"sriov_numvfs": 0,
"driver": "intel-fpga-pci",
"bmc_fw_version": null,
"root_key": null,
"host_uuid": "35436a7d-ce05-4e5f-87ac-706fe7513ece",
"bmc_build_version": null,
"name": "pci_0000_b3_00_0",
"revoked_key_ids": null,
"numa_node": 1,
"created_at": "2020-05-04T18:23:34.697710+00:00",
"pdevice_id": "0b30",
"pclass": "Processing accelerators",
"sriov_vfs_pci_address": "",
"sriov_totalvfs": 1,
"pciaddr": "0000:b3:00.0",
"pvendor_id": "8086"
},
]
}
@ -6310,6 +6358,531 @@ badMediaType (415)
"pvendor_id": "8086"
}
--------------
Device images
--------------
************************
List the device images
************************
.. rest_method:: GET /v1/device_images
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"device_images (Optional)", "plain", "xsd:list", "The list of device images."
"bitstream_type (Optional)", "plain", "xsd:string", "The bitstream type of the device image."
"pci_vendor (Optional)", "plain", "xsd:string", "The vendor ID of the pci device."
"pci_device (Optional)", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image."
"key_signature (Optional)", "plain", "xsd:string", "The key signature of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
"applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
"links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage."
::
{
"device_images": [
{
"uuid": "7e794693-2060-4e9e-b0bd-b281b059e8e4",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "functional",
"bitstream_id": "1234",
"key_signature": null,
"revoke_key_id": null,
"description": null,
"name": null,
"image_version": null,
"applied_labels":
{
"key1": "value1",
"key2": "value2"
},
},
{
"uuid": "09100124-5ae9-44d8-aefc-a192b8f27360",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "root-key",
"bitstream_id": null
"key_signature": "a123",
"revoke_key_id": null,
"name": "Image name",
"description": null,
"image_version": null,
"applied_labels": null,
},
{
"uuid": "ef4c39b1-81e9-42dd-b850-06fc8833b47c",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "key-revocation",
"bitstream_id": null
"key_signature": null,
"revoke_key_id": 123,
"name": "Image name",
"description": null,
"image_version": null,
"applied_labels": null,
},
]
}
This operation does not accept a request body.
**************************************************
Shows attributes of the Device Image object
**************************************************
.. rest_method:: GET /v1/device_images/{image_id}
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_id", "URI", "csapi:UUID", "The unique identifier of a device image."
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"device_images (Optional)", "plain", "xsd:list", "The list of device images."
"bitstream_type (Optional)", "plain", "xsd:string", "The bitstream type of the device image."
"pci_vendor (Optional)", "plain", "xsd:string", "The vendor ID of the pci device ."
"pci_device (Optional)", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image."
"key_signature (Optional)", "plain", "xsd:string", "The key id of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
"applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
"links (Optional)", "plain", "xsd:list", "For convenience, resources contain links to themselves. This allows a client to easily obtain rather than construct resource URIs. The following types of link relations are associated with resources: a self link containing a versioned link to the resource, and a bookmark link containing a permanent link to a resource that is appropriate for long term storage."
::
{
"device_images": [
{
"uuid": "7e794693-2060-4e9e-b0bd-b281b059e8e4",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "functional",
"bitstream_id": "1234",
"key_signature": null,
"revoke_key_id": null,
"description": null,
"name": null,
"image_version": null,
"applied_labels":
{
"key1": "value1",
"key2": "value2"
},
}
]
}
************************
Creates a device image
************************
.. rest_method:: POST /v1/device_image
**Normal response codes**
200
**Error response codes**
badMediaType (415)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"bitstream_type ", "plain", "xsd:string", "The bitstream type of the device image. Valid types are ``functional``, ``root-key``, ``key-revocation``"
"pci_vendor ", "plain", "xsd:string", "The vendor ID of the pci device."
"pci_device ", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image. Required for bitstream type ``functional`` "
"key_signature (Optional)", "plain", "xsd:string", "The key id of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"bitstream_type ", "plain", "xsd:string", "The bitstream type of the device image."
"pci_vendor ", "plain", "xsd:string", "The vendor ID of the pci device ."
"pci_device ", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image."
"key_signature (Optional)", "plain", "xsd:string", "The key id of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
"applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
::
{
"device_images": [
{
"uuid": "7e794693-2060-4e9e-b0bd-b281b059e8e4",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "functional",
"bitstream_id": "1234",
"key_signature": null,
"revoke_key_id": null,
"description": null,
"name": null,
"image_version": null,
"applied_labels": null
}
]
}
************************************************
Applies the device image to all hosts or label
************************************************
.. rest_method:: PATCH /v1/device_images/{image_id}?action=apply
**Normal response codes**
200
**Error response codes**
badMediaType (415)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_id", "URI", "csapi:UUID", "The unique identifier of a device image."
"device_label (Optional)", "plain", "xsd:string", "The key-value paired device label assigned to a device."
::
{
"key1": "value1"
}
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"bitstream_type ", "plain", "xsd:string", "The bitstream type of the device image."
"pci_vendor ", "plain", "xsd:string", "The vendor ID of the pci device ."
"pci_device ", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image."
"key_signature (Optional)", "plain", "xsd:string", "The key id of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
"applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
::
{
"device_images": [
{
"uuid": "7e794693-2060-4e9e-b0bd-b281b059e8e4",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "functional",
"bitstream_id": "1234",
"key_signature": null,
"revoke_key_id": null,
"description": null,
"name": null,
"image_version": null,
"applied_labels":
{
"key1": "value1"
},
}
]
}
*******************************************
Remove the device image from host or label
*******************************************
.. rest_method:: PATCH /v1/device_images/{image_id}?action=remove
**Normal response codes**
200
**Error response codes**
badMediaType (415)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_id", "URI", "csapi:UUID", "The unique identifier of a device image."
"device_label (Optional)", "plain", "xsd:string", "The key-value paired device label assigned to a device."
::
{
"key1": "value1"
}
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"bitstream_type ", "plain", "xsd:string", "The bitstream type of the device image."
"pci_vendor ", "plain", "xsd:string", "The vendor ID of the pci device ."
"pci_device ", "plain", "xsd:string", "The device ID of the pci device."
"bitstream_id (Optional)", "plain", "xsd:string", "The bitstream id of the functional device image."
"key_signature (Optional)", "plain", "xsd:string", "The key id of the root-key device image."
"revoked_key_id (Optional)", "plain", "xsd:string", "The key revocation id of the key revocation device image."
"name (Optional)", "plain", "xsd:string", "The name of the device image."
"description (Optional)", "plain", "xsd:string", "The description of the device image."
"image_version (Optional)", "plain", "xsd:string", "The version of the device image."
"applied_labels (Optional)", "plain", "xsd:list", "The device image applied to the device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
::
{
"device_images": [
{
"uuid": "7e794693-2060-4e9e-b0bd-b281b059e8e4",
"pci_vendor": "8086",
"pci_device": "0b30",
"bitstream_type": "functional",
"bitstream_id": "1234",
"key_signature": null,
"revoke_key_id": null,
"description": null,
"name": null,
"image_version": null,
"applied_labels": null
}
]
}
*****************************
Deletes a device image
*****************************
.. rest_method:: DELETE /v1/device_images/{image_id}
**Normal response codes**
204
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"image_id", "URI", "csapi:UUID", "The unique identifier of a device image."
This operation does not accept a request body.
--------------
Device labels
--------------
************************
List the device labels
************************
.. rest_method:: GET /v1/device_labels
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"device_labels ", "plain", "xsd:list", "The list of device labels."
"uuid (Optional)", "plain", "csapi:UUID", "The universally unique identifier for this object."
"pcidevice_uuid ", "plain", "csapi:UUID", "The universally unique identifier for the pci device object."
"host_uuid ", "plain", "csapi:UUID", "The universally unique identifier for the host object."
"label_key ", "plain", "xsd:string", "The key of the device label."
"label_value ", "plain", "xsd:string", "The value of the device label."
::
{
"device_labels": [
{
"uuid": "fe26ca98-35d4-43b7-8c51-f0ca957b35e1",
"pcidevice_uuid": "64641c6d-4fdd-4ecb-9c66-a68982267b6d",
"host_uuid": "32be8077-1174-46cf-8309-48c107765ffc"
"label_key": "key1",
"label_value": "value1",
},
{
"uuid": "60342a18-a686-48c4-8e71-13a005ffda1b",
"pcidevice_uuid": "9d69d492-9888-4d85-90d0-e52def926b17",
"host_uuid": "32be8077-1174-46cf-8309-48c107765ffc"
"label_key": "key5",
"label_value": "value5",
},
]
}
*************************************
Assign device label to a pci device
*************************************
.. rest_method:: POST /v1/device_labels
**Normal response codes**
200
**Error response codes**
computeFault (400, 500, ...), serviceUnavailable (503), badRequest (400),
unauthorized (401), forbidden (403), badMethod (405), overLimit (413),
itemNotFound (404)
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"pcidevice_uuid", "URI", "csapi:UUID", "The unique identifier of a pci device."
"device_labels", "URI", "xsd:list", "List of key-value paired of device labels."
::
{
"pcidevice_uuid": "da98f600-49cf-4f0e-b14e-15ef91069fe8",
"key1": "value1",
"key2": "value2"
}
**Response parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"uuid", "URI", "csapi:UUID", "The unique identifier of the device label object."
"pcidevice_uuid", "URI", "csapi:UUID", "The unique identifier of a pci device."
"label_key", "URI", "xsd:string", "The label key of device labels."
"label_value", "URI", "xsd:string", "The label value of device labels."
::
{
"device_labels": [
{
"uuid": "66daffb1-72ee-4e6e-9489-206c5eeaec94",
"pcidevice_uuid": "da98f600-49cf-4f0e-b14e-15ef91069fe8",
"label_key": "key1",
"label_value": "value1",
},
{
"uuid": "2e7821ed-e373-4cb8-a47b-f70ff2558dfd",
"pcidevice_uuid": "da98f600-49cf-4f0e-b14e-15ef91069fe8",
"label_key": "key2",
"label_value": "value2",
}
]
}
************************
Deletes a device label
************************
.. rest_method:: DELETE /v1/device_labels/{device_label_uuid}
**Normal response codes**
204
**Request parameters**
.. csv-table::
:header: "Parameter", "Style", "Type", "Description"
:widths: 20, 20, 20, 60
"device_label_uuid", "URI", "csapi:UUID", "The unique identifier of a device label."
This operation does not accept a request body.
------------------
Service Parameter
------------------

View File

@ -1,2 +1,2 @@
SRC_DIR="cgts-client"
TIS_PATCH_VER=75
TIS_PATCH_VER=76

View File

@ -21,6 +21,7 @@ Requires: python-keystoneclient
Requires: python2-oslo-i18n
Requires: python2-oslo-serialization
Requires: python2-oslo-utils
Requires: requests-toolbelt
# Needed for python2 and python3 compatible
Requires: python-six

View File

@ -57,6 +57,11 @@ class Manager(object):
'POST', url, body=body, data=data)
return resp
def _upload_multipart(self, url, body, data=None):
resp = self.api.upload_request_with_multipart(
'POST', url, body=body, data=data)
return resp
def _json_get(self, url, body=None):
"""send a GET request and return a json serialized object"""
_, body = self.api.json_request('GET', url, body=body)

View File

@ -15,13 +15,13 @@
# under the License.
#
import httplib2
import logging
import os
import requests
from requests_toolbelt import MultipartEncoder
import socket
import httplib2
import six
from six.moves.urllib.parse import urlparse
@ -293,6 +293,19 @@ class HTTPClient(httplib2.Http):
data=data)
return req.json()
def upload_request_with_multipart(self, method, url, **kwargs):
self.authenticate_and_fetch_endpoint_url()
connection_url = self._get_connection_url(url)
fields = kwargs.get('data')
fields['file'] = (kwargs['body'], open(kwargs['body'], 'rb'))
enc = MultipartEncoder(fields)
headers = {'Content-Type': enc.content_type,
"X-Auth-Token": self.auth_token}
req = requests.post(connection_url,
data=enc,
headers=headers)
return req.json()
#################
# AUTHENTICATE
#################

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
@ -26,6 +26,9 @@ from cgtsclient.v1 import certificate
from cgtsclient.v1 import cluster
from cgtsclient.v1 import controller_fs
from cgtsclient.v1 import datanetwork
from cgtsclient.v1 import device_image
from cgtsclient.v1 import device_image_state
from cgtsclient.v1 import device_label
from cgtsclient.v1 import drbdconfig
from cgtsclient.v1 import ethernetport
from cgtsclient.v1 import fernet
@ -165,3 +168,6 @@ class Client(http.HTTPClient):
self.kube_version = kube_version.KubeVersionManager(self)
self.kube_upgrade = kube_upgrade.KubeUpgradeManager(self)
self.kube_host_upgrade = kube_host_upgrade.KubeHostUpgradeManager(self)
self.device_image = device_image.DeviceImageManager(self)
self.device_image_state = device_image_state.DeviceImageStateManager(self)
self.device_label = device_label.DeviceLabelManager(self)

View File

@ -0,0 +1,81 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import base
from cgtsclient.common import utils
from cgtsclient import exc
CREATION_ATTRIBUTES = [
'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version', 'uuid']
class DeviceImage(base.Resource):
def __repr__(self):
return "<DeviceImage %s>" % self._info
class DeviceImageManager(base.Manager):
resource_class = DeviceImage
@staticmethod
def _path(uuid=None):
return '/v1/device_images/%s' % uuid if uuid else '/v1/device_images'
def list(self):
return self._list(self._path(), "device_images")
def get(self, device_image_id):
try:
return self._list(self._path(device_image_id))[0]
except IndexError:
return None
def create(self, file, **kwargs):
data = {}
for (key, value) in kwargs.items():
if key in CREATION_ATTRIBUTES:
data[key] = value
else:
raise exc.InvalidAttribute('%s' % key)
return self._upload_multipart(self._path(), file, data=data)
def apply(self, device_image_id, labels=None):
return self._update(self._path(device_image_id) + '?action=apply',
labels)
def remove(self, device_image_id, labels=None):
return self._update(self._path(device_image_id) + '?action=remove',
labels)
def delete(self, device_image_id):
return self._delete(self._path(device_image_id))
def _find_device_image(cc, device_image):
if device_image.isdigit() and not utils.is_uuid_like(device_image):
device_image_list = cc.device_image.list()
for n in device_image_list:
if str(n.id) == device_image:
return n
else:
raise exc.CommandError('device image not found: %s' % device_image)
elif utils.is_uuid_like(device_image):
try:
h = cc.device_image.get(device_image)
except exc.HTTPNotFound:
raise exc.CommandError('device image not found: %s' % device_image)
else:
return h
else:
device_image_list = cc.device_image.list()
for n in device_image_list:
if n.name == device_image:
return n
else:
raise exc.CommandError('device image not found: %s' % device_image)

View File

@ -0,0 +1,157 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import utils
from cgtsclient import exc
import os
def _print_device_image_show(obj):
fields = ['uuid', 'bitstream_type',
'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version', 'applied_labels']
if type(obj) is dict:
data = [(f, obj.get(f, '')) for f in fields]
else:
data = [(f, getattr(obj, f, '')) for f in fields]
utils.print_tuple_list(data)
@utils.arg('device_image_id',
metavar='<device_image_id>',
help="UUID or name of device_image")
def do_device_image_show(cc, args):
"""Show device image details."""
device_image = cc.device_image.get(args.device_image_id)
_print_device_image_show(device_image)
def do_device_image_list(cc, args):
"""List device images."""
labels = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version', 'applied_labels']
fields = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version', 'applied_labels']
device_images = cc.device_image.list()
utils.print_list(device_images, fields, labels, sortby=1)
@utils.arg('bitstream_file',
metavar='<bitstream_file>',
help='Path to Bitstream file [REQUIRED] ')
@utils.arg('bitstream_type',
metavar='<bitstream_type>',
choices=['root-key', 'functional', 'key-revocation'],
help="Type of the device image bitstream [REQUIRED]")
@utils.arg('pci_vendor',
metavar='<pci_vendor>',
help="PCI vendor (hexadecimal) of the device image [REQUIRED]")
@utils.arg('pci_device',
metavar='<pci_device>',
help="PCI device (hexadecimal) of the device image [REQUIRED]")
@utils.arg('--bitstream-id',
metavar='<bitstream_id>',
help='Bitstream ID (hexadecimal) of the functional device image')
@utils.arg('--key-signature',
metavar='<key_signature>',
help='Key signature (hexadecimal) of the root-key device image')
@utils.arg('--revoke-key-id',
metavar='<revoke_key_id>',
help='Key ID of the key revocation device image')
@utils.arg('--name',
metavar='<name>',
help='Name of the device image')
@utils.arg('--description',
metavar='<description>',
help='Description of the device image')
@utils.arg('--image-version',
metavar='<version>',
help='Version of the device image')
@utils.arg('-u', '--uuid',
metavar='<uuid>',
help='UUID of the device image')
def do_device_image_create(cc, args):
"""Create a device image."""
if not os.path.isfile(args.bitstream_file):
raise exc.CommandError('Bitstream file does not exist: %s' %
args.bitstream_file)
field_list = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version']
# Prune input fields down to required/expected values
user_fields = dict((k, v) for (k, v) in vars(args).items()
if k in field_list and not (v is None))
try:
response = cc.device_image.create(args.bitstream_file, **user_fields)
error = response.get('error')
if error:
raise exc.CommandError("%s" % error)
except exc.HTTPNotFound:
raise exc.CommandError(
'Device image not created for %s. No response.' % args.bitstream_file)
except Exception as e:
raise exc.CommandError('Device image not created for %s: %s' %
(args.bitstream_file, e))
else:
device_image = response.get('device_image')
_print_device_image_show(device_image)
@utils.arg('device_image_uuid', metavar='<device_image_uuid>',
help='UUID of the device image')
@utils.arg('attributes',
metavar='<name=value>',
nargs='*',
action='append',
default=[],
help="List of device labels")
def do_device_image_apply(cc, args):
"""Apply the device image"""
attributes = utils.extract_keypairs(args)
try:
response = cc.device_image.apply(args.device_image_uuid,
attributes)
_print_device_image_show(response)
except exc.HTTPNotFound:
raise exc.CommandError('Device image apply failed')
@utils.arg('device_image_uuid', metavar='<device_image_uuid>',
help='UUID of the device image')
@utils.arg('attributes',
metavar='<name=value>',
nargs='*',
action='append',
default=[],
help="List of device labels")
def do_device_image_remove(cc, args):
"""Remove the device image"""
attributes = utils.extract_keypairs(args)
try:
response = cc.device_image.remove(args.device_image_uuid,
attributes)
_print_device_image_show(response)
except exc.HTTPNotFound:
raise exc.CommandError('Device image remove failed')
@utils.arg('device_image_uuid',
metavar='<device_image_uuid>',
help="UUID of device image entry")
def do_device_image_delete(cc, args):
"""Delete a device image."""
cc.device_image.delete(args.device_image_uuid)
print('Deleted device image: %s' % args.device_image_uuid)

View File

@ -0,0 +1,23 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import base
class DeviceImageState(base.Resource):
def __repr__(self):
return "<DeviceImageState %s>" % self._info
class DeviceImageStateManager(base.Manager):
resource_class = DeviceImageState
@staticmethod
def _path(uuid=None):
return '/v1/device_image_state/%s' % uuid if uuid else '/v1/device_image_state'
def list(self):
return self._list(self._path(), "device_image_state")

View File

@ -0,0 +1,24 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import utils
from cgtsclient.v1 import ihost as ihost_utils
def do_device_image_state_list(cc, args):
"""List image to device mapping with status."""
device_image_state = cc.device_image_state.list()
for d in device_image_state[:]:
pdevice = cc.pci_device.get(d.pcidevice_uuid)
setattr(d, 'pciaddr', getattr(pdevice, 'pciaddr'))
host = ihost_utils._find_ihost(cc, getattr(pdevice, 'host_uuid'))
setattr(d, 'hostname', host.hostname)
labels = ['hostname', 'PCI device address', 'Device image uuid', 'status',
'Update start time', 'updated_at']
fields = ['hostname', 'pciaddr', 'image_uuid', 'status',
'update_start_time', 'updated_at']
utils.print_list(device_image_state, fields, labels, sortby=1)

View File

@ -0,0 +1,40 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import base
from cgtsclient.v1 import options
class DeviceLabel(base.Resource):
def __repr__(self):
return "<DeviceLabel %s>" % self._info
class DeviceLabelManager(base.Manager):
resource_class = DeviceLabel
@staticmethod
def _path(label_id=None):
return '/v1/device_labels/%s' % label_id if label_id else \
'/v1/device_labels'
def list(self):
path = '/v1/device_labels'
return self._list(path, "device_labels")
def get(self, uuid):
path = '/v1/device_labels/%s' % uuid
try:
return self._list(path)[0]
except IndexError:
return None
def assign(self, label, parameters=None):
return self._create(options.build_url(self._path(), q=None,
params=parameters), label)
def remove(self, uuid):
return self._delete(self._path(uuid))

View File

@ -0,0 +1,120 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from cgtsclient.common import utils
from cgtsclient import exc
from cgtsclient.v1 import ihost as ihost_utils
from cgtsclient.v1 import pci_device
def _print_device_label_show(obj):
fields = ['uuid', 'label_key', 'label_value']
data = [(f, getattr(obj, f, '')) for f in fields]
utils.print_tuple_list(data)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of host")
@utils.arg('nameorpciaddr',
metavar='<devicename or address>',
help="Name or PCI address of device")
def do_host_device_label_list(cc, args):
"""List device labels"""
host = ihost_utils._find_ihost(cc, args.hostnameorid)
device = pci_device.find_device(cc, host, args.nameorpciaddr)
device_labels = cc.device_label.list()
for dl in device_labels[:]:
if dl.pcidevice_uuid != device.uuid:
device_labels.remove(dl)
else:
setattr(dl, 'hostname', host.hostname)
setattr(dl, 'devicename', device.name)
field_labels = ['hostname', 'PCI device name', 'label key', 'label value']
fields = ['hostname', 'devicename', 'label_key', 'label_value']
utils.print_list(device_labels, fields, field_labels, sortby=1)
def do_device_label_list(cc, args):
"""List all device labels"""
device_labels = cc.device_label.list()
for dl in device_labels[:]:
pci_device = cc.pci_device.get(dl.pcidevice_uuid)
setattr(dl, 'devicename', getattr(pci_device, 'name'))
host = ihost_utils._find_ihost(cc, getattr(pci_device, 'host_uuid'))
setattr(dl, 'hostname', host.hostname)
field_labels = ['hostname', 'PCI device name', 'label key', 'label value']
fields = ['hostname', 'devicename', 'label_key', 'label_value']
utils.print_list(device_labels, fields, field_labels, sortby=1)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of host")
@utils.arg('nameorpciaddr',
metavar='<pci name or address>',
help="Name or PCI address of device")
@utils.arg('attributes',
metavar='<name=value>',
nargs='+',
action='append',
default=[],
help="List of device labels")
@utils.arg('--overwrite',
action='store_true',
help="Allow existing label values to be overwritten")
def do_host_device_label_assign(cc, args):
"""Assign a label to a device of a host"""
attributes = utils.extract_keypairs(args)
parameters = ["overwrite=" + str(args.overwrite)]
host = ihost_utils._find_ihost(cc, args.hostnameorid)
device = pci_device.find_device(cc, host, args.nameorpciaddr)
attributes.update({'pcidevice_uuid': device.uuid})
new_device_labels = cc.device_label.assign(attributes, parameters)
for p in new_device_labels.device_labels:
uuid = p['uuid']
if uuid is not None:
try:
device_label = cc.device_label.get(uuid)
except exc.HTTPNotFound:
raise exc.CommandError('Host device label not found: %s' % uuid)
_print_device_label_show(device_label)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of host [REQUIRED]")
@utils.arg('nameorpciaddr',
metavar='<pci name or address>',
help="Name or PCI address of device")
@utils.arg('attributes',
metavar='<name>',
nargs='+',
action='append',
default=[],
help="List of device label keys")
def do_host_device_label_remove(cc, args):
"""Remove a device label from a device of a host"""
host = ihost_utils._find_ihost(cc, args.hostnameorid)
device = pci_device.find_device(cc, host, args.nameorpciaddr)
for i in args.attributes[0]:
lbl = _find_host_device_label(cc, host, device, i)
if lbl:
cc.device_label.remove(lbl.uuid)
print('Deleted device label %s for host %s device %s' %
(i, host.hostname, device.name))
def _find_host_device_label(cc, host, device, label):
device_labels = cc.device_label.list()
for lbl in device_labels:
if (lbl.pcidevice_uuid == device.uuid and lbl.label_key == label):
break
else:
lbl = None
print('Host device label not found: host %s, device %s, label key %s ' %
(host.hostname, device.name, label))
return lbl

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -39,7 +39,8 @@ def _print_ihost_show(ihost, columns=None, output_format=None):
'boot_device', 'rootfs_device', 'install_output', 'console',
'tboot', 'vim_progress_status', 'software_load',
'install_state', 'install_state_info', 'inv_state',
'clock_synchronization']
'clock_synchronization',
'device_image_update', 'reboot_needed']
optional_fields = ['vsc_controllers', 'ttys_dcd']
if ihost.subfunctions != ihost.personality:
fields.append('subfunctions')
@ -848,3 +849,31 @@ def do_kube_host_upgrade(cc, args):
data = dict(data_list)
ordereddata = OrderedDict(sorted(data.items(), key=lambda t: t[0]))
utils.print_dict(ordereddata, wrap=72)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of host")
def do_host_device_image_update(cc, args):
"""Update device image on a host."""
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
try:
host = cc.ihost.device_image_update(ihost.uuid)
except exc.HTTPNotFound:
raise exc.CommandError(
'Device image update failed: host %s' % args.hostnameorid)
_print_ihost_show(host)
@utils.arg('hostnameorid',
metavar='<hostname or id>',
help="Name or ID of host")
def do_host_device_image_update_abort(cc, args):
"""Abort device image update on a host."""
ihost = ihost_utils._find_ihost(cc, args.hostnameorid)
try:
host = cc.ihost.device_image_update_abort(ihost.uuid)
except exc.HTTPNotFound:
raise exc.CommandError(
'Device image update-abort failed: host %s' % args.hostnameorid)
_print_ihost_show(host)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -138,6 +138,16 @@ class ihostManager(base.Manager):
body=post_body)
return self.resource_class(self, body)
def device_image_update(self, hostid):
path = self._path(hostid) + "/device_image_update"
resp, body = self.api.json_request('POST', path)
return self.resource_class(self, body)
def device_image_update_abort(self, hostid):
path = self._path(hostid) + "/device_image_update_abort"
resp, body = self.api.json_request('POST', path)
return self.resource_class(self, body)
def _find_ihost(cc, ihost):
if ihost.isdigit() or utils.is_uuid_like(ihost):

View File

@ -1,13 +1,11 @@
#
# Copyright (c) 2015 Wind River Systems, Inc.
# Copyright (c) 2015-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# -*- encoding: utf-8 -*-
#
from cgtsclient.common import base
from cgtsclient import exc
class PciDevice(base.Resource):
@ -43,3 +41,13 @@ def get_pci_device_display_name(p):
return p.name
else:
return '(' + str(p.uuid)[-8:] + ')'
def find_device(cc, host, nameorpciaddr):
devices = cc.pci_device.list(host.uuid)
for d in devices:
if d.name == nameorpciaddr or d.pciaddr == nameorpciaddr:
return d
else:
raise exc.CommandError('PCI device not found: host %s device %s' %
(host.hostname, nameorpciaddr))

View File

@ -1,18 +1,16 @@
#
# Copyright (c) 2015 Wind River Systems, Inc.
# Copyright (c) 2015-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# All Rights Reserved.
#
from cgtsclient.common import utils
from cgtsclient import exc
from cgtsclient.v1 import ihost as ihost_utils
# PCI Device Class ID in hexadecimal string
PCI_DEVICE_CLASS_FPGA = '120000'
def _print_device_show(device):
fields = ['name', 'pciaddr', 'pclass_id', 'pvendor_id', 'pdevice_id',
@ -26,6 +24,15 @@ def _print_device_show(device):
'sriov_vfs_pci_address', 'extra_info', 'created_at',
'updated_at']
pclass_id = getattr(device, 'pclass_id')
if pclass_id == PCI_DEVICE_CLASS_FPGA:
fields += ['needs_firmware_update', 'status', 'root_key',
'revoked_key_ids', 'boot_page', 'bitstream_id',
'bmc_build_version', 'bmc_fw_version']
labels += ['needs_firmware_update', 'status', 'root_key',
'revoked_key_ids', 'boot_page', 'bitstream_id',
'bmc_build_version', 'bmc_fw_version']
data = [(f, getattr(device, f, '')) for f in fields]
utils.print_tuple_list(data, labels)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -14,6 +14,9 @@ from cgtsclient.v1 import certificate_shell
from cgtsclient.v1 import cluster_shell
from cgtsclient.v1 import controller_fs_shell
from cgtsclient.v1 import datanetwork_shell
from cgtsclient.v1 import device_image_shell
from cgtsclient.v1 import device_image_state_shell
from cgtsclient.v1 import device_label_shell
from cgtsclient.v1 import drbdconfig_shell
from cgtsclient.v1 import ethernetport_shell
from cgtsclient.v1 import health_shell
@ -123,6 +126,9 @@ COMMAND_MODULES = [
host_fs_shell,
kube_version_shell,
kube_upgrade_shell,
device_image_shell,
device_image_state_shell,
device_label_shell,
]

View File

@ -3,3 +3,4 @@ keyring
oslo.i18n # Apache-2.0
oslo.serialization>=1.10.0,!=2.19.1 # Apache-2.0
oslo.utils>=3.5.0 # Apache-2.0
requests-toolbelt

View File

@ -1,2 +1,2 @@
SRC_DIR="sysinv"
TIS_PATCH_VER=345
TIS_PATCH_VER=346

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -28,6 +28,9 @@ from sysinv.api.controllers.v1 import cluster
from sysinv.api.controllers.v1 import community
from sysinv.api.controllers.v1 import controller_fs
from sysinv.api.controllers.v1 import cpu
from sysinv.api.controllers.v1 import device_image
from sysinv.api.controllers.v1 import device_image_state
from sysinv.api.controllers.v1 import device_label
from sysinv.api.controllers.v1 import disk
from sysinv.api.controllers.v1 import datanetwork
from sysinv.api.controllers.v1 import interface_datanetwork
@ -261,6 +264,15 @@ class V1(base.APIBase):
kube_host_upgrades = [link.Link]
"Links to the kube_host_upgrade resource"
device_images = [link.Link]
"Links to the device images resource"
device_image_state = [link.Link]
"Links to the device image state resource"
device_labels = [link.Link]
"Links to the device labels resource"
@classmethod
def convert(self):
v1 = V1()
@ -809,6 +821,26 @@ class V1(base.APIBase):
'kube_host_upgrades', '',
bookmark=True)]
v1.device_images = [link.Link.make_link('self', pecan.request.host_url,
'device_images', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'device_images', '',
bookmark=True)]
v1.device_image_state = [link.Link.make_link('self', pecan.request.host_url,
'device_image_state', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'device_image_state', '',
bookmark=True)]
v1.device_labels = [link.Link.make_link('self', pecan.request.host_url,
'device_labels', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'device_labels', '',
bookmark=True)]
return v1
@ -880,6 +912,9 @@ class Controller(rest.RestController):
kube_versions = kube_version.KubeVersionController()
kube_upgrade = kube_upgrade.KubeUpgradeController()
kube_host_upgrades = kube_host_upgrade.KubeHostUpgradeController()
device_images = device_image.DeviceImageController()
device_image_state = device_image_state.DeviceImageStateController()
device_labels = device_label.DeviceLabelController()
@wsme_pecan.wsexpose(V1)
def get(self):

View File

@ -0,0 +1,478 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import os
import pecan
from pecan import expose
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_log import log
from sysinv._i18n import _
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv.common import constants
from sysinv.common import device as dconstants
from sysinv.common import exception
from sysinv.common import utils as cutils
from sysinv import objects
LOG = log.getLogger(__name__)
ALLOWED_BITSTREAM_TYPES = [
dconstants.BITSTREAM_TYPE_ROOT_KEY,
dconstants.BITSTREAM_TYPE_FUNCTIONAL,
dconstants.BITSTREAM_TYPE_KEY_REVOCATION,
]
class DeviceImagePatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return []
class DeviceImage(base.APIBase):
"""API representation of a device_image.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
a device image.
"""
id = int
"Unique ID for this device_image"
uuid = types.uuid
"Unique UUID for this device_image"
bitstream_type = wtypes.text
"The bitstream type of the device image"
pci_vendor = wtypes.text
"The vendor ID of the pci device"
pci_device = wtypes.text
"The device ID of the pci device"
bitstream_id = wtypes.text
"The bitstream id of the functional device image"
key_signature = wtypes.text
"The key signature of the root-key device image"
revoke_key_id = int
"The key revocation id of the key revocation device image"
name = wtypes.text
"The name of the device image"
description = wtypes.text
"The description of the device image"
image_version = wtypes.text
"The version of the device image"
applied = bool
"Represent current status: created or applied"
applied_labels = types.MultiType({dict})
"Represent a list of key-value pair of labels"
def __init__(self, **kwargs):
self.fields = list(objects.device_image.fields.keys())
for k in self.fields:
setattr(self, k, kwargs.get(k))
# API-only attribute
self.fields.append('action')
setattr(self, 'action', kwargs.get('action', None))
# 'applied_labels' is not part of the object.device_image.fields
# (it is an API-only attribute)
self.fields.append('applied_labels')
setattr(self, 'applied_labels', kwargs.get('applied_labels', None))
@classmethod
def convert_with_links(cls, rpc_device_image, expand=True):
device_image = DeviceImage(**rpc_device_image.as_dict())
if not expand:
device_image.unset_fields_except(
['id', 'uuid', 'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version', 'applied_labels'])
# insert applied labels for this device image if they exist
device_image = _get_applied_labels(device_image)
# do not expose the id attribute
device_image.id = wtypes.Unset
return device_image
def _validate_bitstream_type(self):
if self.bitstream_type not in ALLOWED_BITSTREAM_TYPES:
raise ValueError(_("Bitstream type %s not supported") %
self.bitstream_type)
def validate_syntax(self):
"""
Validates the syntax of each field.
"""
self._validate_bitstream_type()
class DeviceImageCollection(collection.Collection):
"""API representation of a collection of device_image."""
device_images = [DeviceImage]
"A list containing device_image objects"
def __init__(self, **kwargs):
self._type = 'device_images'
@classmethod
def convert_with_links(cls, rpc_device_images, limit, url=None,
expand=False, **kwargs):
collection = DeviceImageCollection()
collection.device_images = [DeviceImage.convert_with_links(p, expand)
for p in rpc_device_images]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
def _get_applied_labels(device_image):
if not device_image:
return device_image
image_labels = pecan.request.dbapi.device_image_label_get_by_image(
device_image.id)
if image_labels:
applied_labels = {}
for image_label in image_labels:
label = pecan.request.dbapi.device_label_get(image_label.label_uuid)
applied_labels[label.label_key] = label.label_value
device_image.applied_labels = applied_labels
return device_image
LOCK_NAME = 'DeviceImageController'
class DeviceImageController(rest.RestController):
"""REST controller for device_image."""
def __init__(self, parent=None, **kwargs):
self._parent = parent
def _get_device_image_collection(
self, marker=None, limit=None, sort_key=None,
sort_dir=None, expand=False, resource_url=None):
limit = utils.validate_limit(limit)
sort_dir = utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.device_image.get_by_uuid(
pecan.request.context,
marker)
deviceimages = pecan.request.dbapi.deviceimages_get_all(
limit=limit, marker=marker_obj,
sort_key=sort_key, sort_dir=sort_dir)
return DeviceImageCollection.convert_with_links(
deviceimages, limit, url=resource_url, expand=expand,
sort_key=sort_key, sort_dir=sort_dir)
def _get_one(self, deviceimage_uuid):
rpc_deviceimage = objects.device_image.get_by_uuid(
pecan.request.context, deviceimage_uuid)
return DeviceImage.convert_with_links(rpc_deviceimage)
@wsme_pecan.wsexpose(DeviceImageCollection,
types.uuid, int, wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of device images."""
return self._get_device_image_collection(marker, limit,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(DeviceImage, wtypes.text)
def get_one(self, deviceimage_uuid):
"""Retrieve a single device image."""
return self._get_one(deviceimage_uuid)
@expose('json')
@cutils.synchronized(LOCK_NAME)
def post(self):
"""Create a new device image."""
fileitem = pecan.request.POST['file']
if not fileitem.filename:
return dict(success="", error="Error: No file uploaded")
try:
file_content = fileitem.file.read()
except Exception as e:
return dict(
success="",
error=("No bitstream file has been added, "
"invalid file: %s" % e))
field_list = ['uuid', 'bitstream_type', 'pci_vendor', 'pci_device',
'bitstream_id', 'key_signature', 'revoke_key_id',
'name', 'description', 'image_version']
data = dict((k, v) for (k, v) in pecan.request.POST.items()
if k in field_list and not (v is None))
msg = _validate_syntax(data)
if msg:
return dict(success="", error=msg)
device_image = pecan.request.dbapi.deviceimage_create(data)
device_image_dict = device_image.as_dict()
# Save the file contents in a temporary location
filename = cutils.format_image_filename(device_image)
image_file_path = os.path.join(dconstants.DEVICE_IMAGE_TMP_PATH, filename)
if not os.path.exists(dconstants.DEVICE_IMAGE_TMP_PATH):
os.makedirs(dconstants.DEVICE_IMAGE_TMP_PATH)
with os.fdopen(os.open(image_file_path,
os.O_CREAT | os.O_TRUNC | os.O_WRONLY,
constants.CONFIG_FILE_PERMISSION_DEFAULT),
'wb') as f:
f.write(file_content)
# Call rpc to move the bitstream file to the final destination
pecan.request.rpcapi.store_bitstream_file(pecan.request.context, filename)
return dict(success="", error="", device_image=device_image_dict)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, deviceimage_uuid):
"""Delete a device image."""
# TODO Only allow delete if there are no devices using the image
device_image = objects.device_image.get_by_uuid(
pecan.request.context, deviceimage_uuid)
filename = cutils.format_image_filename(device_image)
pecan.request.rpcapi.delete_bitstream_file(pecan.request.context,
filename)
pecan.request.dbapi.deviceimage_destroy(deviceimage_uuid)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(DeviceImage, types.uuid, wtypes.text, body=types.apidict)
def patch(self, uuid, action, body):
"""Apply/Remove a device image to/from host ."""
if action not in [dconstants.APPLY_ACTION, dconstants.REMOVE_ACTION]:
raise exception.OperationNotPermitted
try:
device_image = objects.device_image.get_by_uuid(
pecan.request.context, uuid)
except exception.DeviceImageNotFound:
LOG.error("Device image %s deos not exist." % uuid)
raise wsme.exc.ClientSideError(_(
"Device image {} failed: image does not exist".format(action)))
# For now, update status in fpga_device
# find device label with matching label key and value
for key, value in body.items():
device_labels = pecan.request.dbapi.device_label_get_by_label(
key, value)
if not device_labels:
raise wsme.exc.ClientSideError(_(
"Device image {} failed: label {}={} does not exist".format(
action, key, value)))
break
for device_label in device_labels:
if action == dconstants.APPLY_ACTION:
process_device_image_apply(device_label.pcidevice_id,
device_image, device_label.id)
# Create an entry of image to label mapping
pecan.request.dbapi.device_image_label_create({
'image_id': device_image.id,
'label_id': device_label.id,
})
update_device_image_state(device_label.host_id,
device_label.pcidevice_id,
device_image.id, dconstants.DEVICE_IMAGE_UPDATE_PENDING)
# Update flags in pci_device and host
modify_flags(device_label.pcidevice_id, device_label.host_id)
elif action == dconstants.REMOVE_ACTION:
try:
img_lbl = pecan.request.dbapi.device_image_label_get_by_image_label(
device_image.id, device_label.id)
if img_lbl:
pecan.request.dbapi.device_image_label_destroy(img_lbl.id)
except exception.DeviceImageLabelNotFoundByKey:
raise wsme.exc.ClientSideError(_(
"Device image {} not associated with label {}={}".format(
device_image.uuid, device_label.label_key,
device_label.label_value
)))
delete_device_image_state(device_label.pcidevice_id, device_image)
if not body:
# No host device labels specified, apply to all hosts
LOG.info("No host device labels specified")
hosts = pecan.request.dbapi.ihost_get_list()
for host in hosts:
fpga_devices = pecan.request.dbapi.fpga_device_get_by_host(host.id)
for dev in fpga_devices:
if action == dconstants.APPLY_ACTION:
process_device_image_apply(dev.pci_id, device_image)
update_device_image_state(host.id,
dev.pci_id, device_image.id,
dconstants.DEVICE_IMAGE_UPDATE_PENDING)
# Update flags in pci_device and host
modify_flags(dev.pci_id, dev.host_id)
elif action == dconstants.REMOVE_ACTION:
delete_device_image_state(dev.pci_id, device_image)
return DeviceImage.convert_with_links(device_image)
def _validate_bitstream_type(dev_img):
msg = None
if dev_img['bitstream_type'] not in ALLOWED_BITSTREAM_TYPES:
msg = _("Bitstream type %s not supported" % dev_img['bitstream_type'])
elif (dev_img['bitstream_type'] == dconstants.BITSTREAM_TYPE_FUNCTIONAL and
'bitstream_id' not in dev_img):
msg = _("bitstream_id is required for functional bitstream type")
elif (dev_img['bitstream_type'] == dconstants.BITSTREAM_TYPE_ROOT_KEY and
'key_signature' not in dev_img):
msg = _("key_signature is required for root key bitstream type")
elif (dev_img['bitstream_type'] == dconstants.BITSTREAM_TYPE_KEY_REVOCATION and
'revoke_key_id' not in dev_img):
msg = _("revoke_key_id is required for key revocation bitstream type")
return msg
def _is_hex_string(s):
try:
int(s, 16)
return True
except ValueError:
return False
def _validate_hexadecimal_fields(dev_img):
msg = None
if ('pci_vendor' in dev_img.keys() and
not _is_hex_string(dev_img['pci_vendor'])):
msg = _("pci_vendor must be hexadecimal")
elif ('pci_device' in dev_img.keys() and
not _is_hex_string(dev_img['pci_device'])):
msg = _("pci_device must be hexadecimal")
elif ('bitstream_id' in dev_img.keys() and
not _is_hex_string(dev_img['bitstream_id'])):
msg = _("bitstream_id must be hexadecimal")
elif ('key_signature' in dev_img.keys() and
not _is_hex_string(dev_img['key_signature'])):
msg = _("key_signature must be hexadecimal")
return msg
def _check_revoke_key(dev_img):
msg = None
if ('revoke_key_id' in dev_img.keys()):
if str(dev_img['revoke_key_id']).isdigit():
dev_img['revoke_key_id'] = int(dev_img['revoke_key_id'])
else:
msg = _("revoke_key_id must be an integer")
return msg
def _validate_syntax(device_image):
"""
Validates the syntax of each field.
"""
msg = _validate_hexadecimal_fields(device_image)
if not msg:
msg = _validate_bitstream_type(device_image)
if not msg:
msg = _check_revoke_key(device_image)
return msg
def update_device_image_state(host_id, pcidevice_id, image_id, status):
try:
dev_img_state = pecan.request.dbapi.device_image_state_get_by_image_device(
image_id, pcidevice_id)
pecan.request.dbapi.device_image_state_update(dev_img_state.id,
{'status': status})
except exception.DeviceImageStateNotFoundByKey:
# Create an entry of image to device mapping
state_values = {
'host_id': host_id,
'pcidevice_id': pcidevice_id,
'image_id': image_id,
'status': status,
}
pecan.request.dbapi.device_image_state_create(state_values)
def process_device_image_apply(pcidevice_id, device_image, label_id=None):
pci_device = pecan.request.dbapi.pci_device_get(pcidevice_id)
host = pecan.request.dbapi.ihost_get(pci_device.host_uuid)
# check if device image with type functional or root-key already applied
# to the device
records = pecan.request.dbapi.device_image_state_get_all(
host_id=host.id, pcidevice_id=pcidevice_id)
for r in records:
img = pecan.request.dbapi.deviceimage_get(r.image_id)
if img.bitstream_type == device_image.bitstream_type:
if img.bitstream_type == dconstants.BITSTREAM_TYPE_ROOT_KEY:
# Block applying root-key image if another one is already applied
msg = _("Root-key image {} is already applied to host {} device"
" {}".format(img.uuid, host.hostname, pci_device.pciaddr))
raise wsme.exc.ClientSideError(msg)
elif img.bitstream_type == dconstants.BITSTREAM_TYPE_FUNCTIONAL:
if r.status == dconstants.DEVICE_IMAGE_UPDATE_IN_PROGRESS:
msg = _("Applying image {} for host {} device {} not allowed "
"while device image update is in progress".format(
device_image.uuid, host.hostname, pci_device.pciaddr))
raise wsme.exc.ClientSideError(msg)
# Remove the existing device_image_state record
pecan.request.dbapi.device_image_state_destroy(r.uuid)
# Remove the existing device image label if any
if label_id:
try:
img_lbl = pecan.request.dbapi.device_image_label_get_by_image_label(
img.id, label_id)
pecan.request.dbapi.device_image_label_destroy(img_lbl.uuid)
except exception.DeviceImageLabelNotFoundByKey:
pass
def delete_device_image_state(pcidevice_id, device_image):
try:
dev_img = pecan.request.dbapi.device_image_state_get_by_image_device(
device_image.id, pcidevice_id)
pecan.request.dbapi.device_image_state_destroy(dev_img.uuid)
except exception.DeviceImageStateNotFoundByKey:
pass
def modify_flags(pcidevice_id, host_id):
# Set flag for pci_device indicating device requires image update
pecan.request.dbapi.pci_device_update(pcidevice_id,
{'needs_firmware_update': True},
host_id)
# Set flag for host indicating device image update is pending if it is
# not already in progress
host = pecan.request.dbapi.ihost_get(host_id)
if host.device_image_update != dconstants.DEVICE_IMAGE_UPDATE_IN_PROGRESS:
pecan.request.dbapi.ihost_update(host_id,
{'device_image_update': dconstants.DEVICE_IMAGE_UPDATE_PENDING})

View File

@ -0,0 +1,152 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import pecan
from pecan import rest
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_log import log
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import link
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv import objects
LOG = log.getLogger(__name__)
class DeviceImageState(base.APIBase):
"""API representation of a device_image_state.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
a device image.
"""
id = int
"Unique ID for this device_image_state"
uuid = types.uuid
"Unique UUID for this device_image_state"
host_id = int
"Represent the host id of the host that the pci_device belongs to"
host_uuid = types.uuid
"Represent the UUID of the host that the pci_device belongs to"
pcidevice_id = int
"Represent the id of pci_device"
pcidevice_uuid = types.uuid
"Represent the uuid of pci_device"
image_id = int
"Represent the id of device image"
image_uuid = types.uuid
"Represent the uuid of device image"
status = wtypes.text
"Firmware update status"
update_start_time = wtypes.datetime.datetime
"Represents the start time of the device image update"
updated_at = wtypes.datetime.datetime
"The time at which the record is updated "
links = [link.Link]
"A list containing a self link and associated device image state links"
def __init__(self, **kwargs):
self.fields = list(objects.device_image_state.fields.keys())
for k in self.fields:
setattr(self, k, kwargs.get(k))
@classmethod
def convert_with_links(cls, rpc_device_image_state, expand=True):
device_image_state = DeviceImageState(**rpc_device_image_state.as_dict())
if not expand:
device_image_state.unset_fields_except(
['id', 'uuid', 'host_id', 'host_uuid',
'pcidevice_id', 'pcidevice_uuid',
'image_id', 'image_uuid', 'status',
'update_start_time', 'updated_at'])
# do not expose the id attribute
device_image_state.host_id = wtypes.Unset
return device_image_state
class DeviceImageStateCollection(collection.Collection):
"""API representation of a collection of device_image_state."""
device_image_state = [DeviceImageState]
"A list containing device_image_state objects"
def __init__(self, **kwargs):
self._type = 'device_image_state'
@classmethod
def convert_with_links(cls, rpc_device_image_state, limit, url=None,
expand=False, **kwargs):
collection = DeviceImageStateCollection()
collection.device_image_state = [DeviceImageState.convert_with_links(p, expand)
for p in rpc_device_image_state]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
LOCK_NAME = 'DeviceImageStateController'
class DeviceImageStateController(rest.RestController):
"""REST controller for device image state."""
def __init__(self, parent=None, **kwargs):
self._parent = parent
def _get_device_image_state_collection(
self, marker=None, limit=None, sort_key=None,
sort_dir=None, expand=False, resource_url=None):
limit = utils.validate_limit(limit)
sort_dir = utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.device_image_state.get_by_uuid(
pecan.request.context,
marker)
states = pecan.request.dbapi.device_image_state_get_list(
limit=limit, marker=marker_obj,
sort_key=sort_key, sort_dir=sort_dir)
return DeviceImageStateCollection.convert_with_links(
states, limit, url=resource_url, expand=expand,
sort_key=sort_key, sort_dir=sort_dir)
def _get_one(self, uuid):
obj = objects.device_image_state.get_by_uuid(
pecan.request.context, uuid)
return DeviceImageState.convert_with_links(obj)
@wsme_pecan.wsexpose(DeviceImageStateCollection,
types.uuid, int, wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, sort_key='id', sort_dir='asc'):
"""Retrieve a list of device image state."""
return self._get_device_image_state_collection(marker, limit,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(DeviceImageState, wtypes.text)
def get_one(self, deviceimagestate_uuid):
"""Retrieve a single device image state."""
return self._get_one(deviceimagestate_uuid)

View File

@ -0,0 +1,244 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
import wsmeext.pecan as wsme_pecan
from oslo_log import log
from sysinv._i18n import _
from sysinv.api.controllers.v1 import base
from sysinv.api.controllers.v1 import collection
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv.common import exception
from sysinv.common import utils as cutils
from sysinv import objects
LOG = log.getLogger(__name__)
class DeviceLabelPatchType(types.JsonPatchType):
@staticmethod
def mandatory_attrs():
return []
class DeviceLabel(base.APIBase):
"""API representation of a device label.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
a device label.
"""
id = int
"Unique ID for this device label"
uuid = types.uuid
"Unique UUID for this device label"
host_id = int
"Represent the id of host the device label belongs to"
host_uuid = types.uuid
"Represent the uuid of the host the device label belongs to"
pcidevice_id = int
"Represent the id of pci_device the device label belongs to"
pcidevice_uuid = types.uuid
"Represent the uuid of the pci_device the device label belongs to"
label_key = wtypes.text
"Represents a label key assigned to the device"
label_value = wtypes.text
"Represents a label value assigned to the device"
def __init__(self, **kwargs):
self.fields = list(objects.device_label.fields.keys())
for k in self.fields:
setattr(self, k, kwargs.get(k))
# API-only attribute)
self.fields.append('action')
setattr(self, 'action', kwargs.get('action', None))
@classmethod
def convert_with_links(cls, rpc_device_label, expand=True):
device_label = DeviceLabel(**rpc_device_label.as_dict())
if not expand:
device_label.unset_fields_except(
['uuid', 'host_id', 'host_uuid', 'pcidevice_id', 'pcidevice_uuid',
'label_key', 'label_value'])
# do not expose the id attribute
device_label.host_id = wtypes.Unset
device_label.pcidevice_id = wtypes.Unset
return device_label
class DeviceLabelCollection(collection.Collection):
"""API representation of a collection of device label."""
device_labels = [DeviceLabel]
"A list containing device_label objects"
def __init__(self, **kwargs):
self._type = 'device_labels'
@classmethod
def convert_with_links(cls, rpc_device_labels, limit, url=None,
expand=False, **kwargs):
collection = DeviceLabelCollection()
collection.device_labels = [DeviceLabel.convert_with_links(p, expand)
for p in rpc_device_labels]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
LOCK_NAME = 'DeviceLabelController'
class DeviceLabelController(rest.RestController):
"""REST controller for device label."""
def __init__(self, parent=None, **kwargs):
self._parent = parent
def _get_device_label_collection(
self, device_uuid, marker=None, limit=None, sort_key=None,
sort_dir=None, expand=False, resource_url=None):
if self._parent and not device_uuid:
raise exception.InvalidParameterValue(_(
"Device id not specified."))
limit = utils.validate_limit(limit)
sort_dir = utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.device_label.get_by_uuid(
pecan.request.context,
marker)
if device_uuid:
device_labels = pecan.request.dbapi.device_label_get_by_device(
device_uuid, limit,
marker_obj,
sort_key=sort_key,
sort_dir=sort_dir)
else:
device_labels = pecan.request.dbapi.device_label_get_list(
limit, marker_obj,
sort_key=sort_key,
sort_dir=sort_dir)
return DeviceLabelCollection.convert_with_links(
device_labels, limit, url=resource_url, expand=expand,
sort_key=sort_key, sort_dir=sort_dir)
def _get_one(self, device_label_uuid):
rpc_device_label = objects.device_label.get_by_uuid(
pecan.request.context, device_label_uuid)
return DeviceLabel.convert_with_links(rpc_device_label)
@wsme_pecan.wsexpose(DeviceLabelCollection, types.uuid, types.uuid,
int, wtypes.text, wtypes.text)
def get_all(self, uuid=None, marker=None, limit=None,
sort_key='id', sort_dir='asc'):
"""Retrieve a list of device labels."""
return self._get_device_label_collection(uuid, marker, limit,
sort_key=sort_key,
sort_dir=sort_dir)
@wsme_pecan.wsexpose(DeviceLabel, types.uuid)
def get_one(self, device_label_uuid):
"""Retrieve a single device label."""
try:
sp_label = objects.device_label.get_by_uuid(
pecan.request.context,
device_label_uuid)
except exception.InvalidParameterValue:
raise wsme.exc.ClientSideError(
_("No device label found for %s" % device_label_uuid))
return DeviceLabel.convert_with_links(sp_label)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(DeviceLabelCollection, types.boolean,
body=types.apidict)
def post(self, overwrite=False, body=None):
"""Assign a new device label."""
pcidevice_uuid = body['pcidevice_uuid']
del body['pcidevice_uuid']
pcidevice = objects.pci_device.get_by_uuid(pecan.request.context,
pcidevice_uuid)
fpgadevice = pecan.request.dbapi.fpga_device_get(pcidevice.pciaddr,
pcidevice.host_id)
existing_labels = {}
for label_key in body.keys():
label = None
try:
label = pecan.request.dbapi.device_label_query(
pcidevice.id, label_key)
except exception.DeviceLabelNotFoundByKey:
pass
if label:
if overwrite:
existing_labels.update({label_key: label.uuid})
else:
raise wsme.exc.ClientSideError(_(
"Label %s exists for device %s. Use overwrite option"
" to assign a new value." %
(label_key, pcidevice.name)))
new_records = []
for key, value in body.items():
values = {
'host_id': pcidevice.host_id,
'pcidevice_id': pcidevice.id,
'fpgadevice_id': fpgadevice.id,
'label_key': key,
'label_value': value
}
try:
if existing_labels.get(key, None):
# Update the value
label_uuid = existing_labels.get(key)
new_label = pecan.request.dbapi.device_label_update(
label_uuid, {'label_value': value})
else:
new_label = pecan.request.dbapi.device_label_create(
pcidevice_uuid, values)
new_records.append(new_label)
except exception.DeviceLabelAlreadyExists:
# We should not be here
raise wsme.exc.ClientSideError(_(
"Error creating label %s") % label_key)
return DeviceLabelCollection.convert_with_links(
new_records, limit=None, url=None, expand=False,
sort_key='id', sort_dir='asc')
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
def delete(self, device_label_uuid):
"""Delete a device label."""
pecan.request.dbapi.device_label_destroy(device_label_uuid)
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(DeviceLabel, body=DeviceLabel)
def patch(self, device_label):
"""Modify a new device label."""
raise exception.OperationNotPermitted

View File

@ -88,6 +88,7 @@ from sysinv.api.controllers.v1 import patch_api
from sysinv.common import ceph
from sysinv.common import constants
from sysinv.common import device
from sysinv.common import exception
from sysinv.common import kubernetes
from sysinv.common import utils as cutils
@ -515,6 +516,12 @@ class Host(base.APIBase):
iscsi_initiator_name = wtypes.text
"The iscsi initiator name (only used for worker hosts)"
device_image_update = wtypes.text
"Represent the status of device image update of this ihost."
reboot_needed = types.boolean
" Represent whether a reboot is needed after device image update"
def __init__(self, **kwargs):
self.fields = list(objects.host.fields.keys())
for k in self.fields:
@ -1088,6 +1095,8 @@ class HostController(rest.RestController):
'wipe_osds': ['GET'],
'kube_upgrade_control_plane': ['POST'],
'kube_upgrade_kubelet': ['POST'],
'device_image_update': ['POST'],
'device_image_update_abort': ['POST'],
}
def __init__(self, from_isystem=False):
@ -6842,6 +6851,45 @@ class HostController(rest.RestController):
host_obj.hostname)
return Host.convert_with_links(host_obj)
# POST ihosts/<uuid>/device_image_update
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(Host, types.uuid)
def device_image_update(self, host_uuid):
""" Update device image on the specified host.
:param host_uuid: UUID of the host
"""
LOG.info("device_image_update host_uuid=%s " % host_uuid)
host_obj = objects.host.get_by_uuid(pecan.request.context, host_uuid)
# Set the flag indicating the host is in progress of
# updating device image
host_obj = pecan.request.dbapi.ihost_update(host_uuid,
{'device_image_update': device.DEVICE_IMAGE_UPDATE_IN_PROGRESS})
# Call rpcapi to tell conductor to begin device image update
pecan.request.rpcapi.host_device_image_update(
pecan.request.context, host_uuid)
return Host.convert_with_links(host_obj)
# POST ihosts/<uuid>/device_image_update_abort
@cutils.synchronized(LOCK_NAME)
@wsme_pecan.wsexpose(Host, types.uuid)
def device_image_update_abort(self, host_uuid):
""" Abort device image update on the specified host.
:param host_uuid: UUID of the host
:param install_uuid: install_uuid.
"""
LOG.info("device_image_update_abort host_uuid=%s " % host_uuid)
host_obj = objects.host.get_by_uuid(pecan.request.context, host_uuid)
# Set the flag indicating the host is no longer updating the device
# image
pecan.request.dbapi.ihost_update(host_uuid,
{'device_image_update': device.DEVICE_IMAGE_UPDATE_PENDING})
# Call rpcapi to tell conductor to abort device image update
pecan.request.rpcapi.host_device_image_update_abort(
pecan.request.context, host_uuid)
return Host.convert_with_links(host_obj)
def _create_node(host, xml_node, personality, is_dynamic_ip):
host_node = et.SubElement(xml_node, 'host')

View File

@ -1,4 +1,4 @@
# Copyright (c) 2015-2016 Wind River Systems, Inc.
# Copyright (c) 2015-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -18,6 +18,7 @@ from sysinv.api.controllers.v1 import link
from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils
from sysinv.common import constants
from sysinv.common import device as dconstants
from sysinv.common import exception
from sysinv.common import utils as cutils
from sysinv import objects
@ -103,6 +104,30 @@ class PCIDevice(base.APIBase):
enabled = types.boolean
"Represent the enabled status of the device"
bmc_build_version = wtypes.text
"Represent the BMC build version of the fpga device"
bmc_fw_version = wtypes.text
"Represent the BMC firmware version of the fpga device"
root_key = wtypes.text
"Represent the root key of the fpga device"
revoked_key_ids = wtypes.text
"Represent the key revocation ids of the fpga device"
boot_page = wtypes.text
"Represent the boot page of the fpga device"
bitstream_id = wtypes.text
"Represent the bitstream id of the fpga device"
needs_firmware_update = types.boolean
"Represent whether firmware update is required for the fpga device"
status = wtypes.text
"Represent the status of the fpga device"
links = [link.Link]
"Represent a list containing a self link and associated device links"
@ -123,12 +148,25 @@ class PCIDevice(base.APIBase):
'sriov_totalvfs', 'sriov_numvfs',
'sriov_vfs_pci_address', 'driver',
'host_uuid', 'enabled',
'bmc_build_version', 'bmc_fw_version',
'root_key', 'revoked_key_ids',
'boot_page', 'bitstream_id',
'needs_firmware_update', 'status',
'created_at', 'updated_at'])
# do not expose the id attribute
device.host_id = wtypes.Unset
device.node_id = wtypes.Unset
# if not FPGA device, hide these attributes
if device.pclass_id != dconstants.PCI_DEVICE_CLASS_FPGA:
device.bmc_build_version = wtypes.Unset
device.bmc_fw_version = wtypes.Unset
device.root_key = wtypes.Unset
device.revoked_key_ids = wtypes.Unset
device.boot_page = wtypes.Unset
device.bitstream_id = wtypes.Unset
device.links = [link.Link.make_link('self', pecan.request.host_url,
'pci_devices', device.uuid),
link.Link.make_link('bookmark',
@ -241,6 +279,7 @@ class PCIDeviceController(rest.RestController):
rpc_device = objects.pci_device.get_by_uuid(
pecan.request.context, device_uuid)
return PCIDevice.convert_with_links(rpc_device)
@cutils.synchronized(LOCK_NAME)

View File

@ -215,6 +215,8 @@ class AuditLogging(hooks.PecanHook):
url_path = urlparse(state.request.path_qs).path
def json_post_data(rest_state):
if 'form-data' in rest_state.request.headers.get('Content-Type'):
return " POST: {}".format(rest_state.request.params)
if not hasattr(rest_state.request, 'json'):
return ""
return " POST: {}".format(rest_state.request.json)

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

View File

@ -0,0 +1,26 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# PCI Device Class ID in hexidecimal string
PCI_DEVICE_CLASS_FPGA = '120000'
# Device Image
DEVICE_IMAGE_TMP_PATH = '/tmp/device_images'
DEVICE_IMAGE_PATH = '/opt/platform/device_images'
BITSTREAM_TYPE_ROOT_KEY = 'root-key'
BITSTREAM_TYPE_FUNCTIONAL = 'functional'
BITSTREAM_TYPE_KEY_REVOCATION = 'key-revocation'
# Device Image Status
DEVICE_IMAGE_UPDATE_PENDING = 'pending'
DEVICE_IMAGE_UPDATE_IN_PROGRESS = 'in-progress'
DEVICE_IMAGE_UPDATE_COMPLETED = 'completed'
DEVICE_IMAGE_UPDATE_FAILED = 'failed'
# Device Image Action
APPLY_ACTION = 'apply'
REMOVE_ACTION = 'remove'

View File

@ -387,6 +387,10 @@ class PCIAddrAlreadyExists(Conflict):
"for %(host)s already exists.")
class PCIAddrNotFound(Conflict):
message = _("A Device with PCI address %(pciaddr)s could not be found.")
class LvmLvgAlreadyExists(Conflict):
message = _("LVM Local Volume Group %(name)s for %(host)s already exists.")
@ -1318,6 +1322,83 @@ class FilesystemAlreadyExists(Conflict):
class FilesystemNotFound(NotFound):
message = _("Host FS with id %(fs_id)s not found")
# Device image
class UnsupportedDeviceImageBitstreamType(Conflict):
message = _("Device image with bitstream type '%(bitstream_type)s' "
"is not supported.")
class DeviceImageNotFound(NotFound):
message = _("Device image %(deviceimage_uuid)s could not be found.")
class DeviceImageTypeNotFound(NotFound):
message = _("Device image of type %(bitstream_type)s could not be found.")
class DeviceImageIDNotFound(NotFound):
message = _("Device image with id %(id)s could not be found.")
class DeviceImageNameNotFound(NotFound):
message = _("Device image with name %(name)s could not be found.")
class DeviceImageAlreadyExists(Conflict):
message = _("Device image of name %(name)s already exists.")
class DeviceImageTypeUnsupported(Conflict):
message = _("Device image of type %(bitstream_type)s is not supported.")
# Device Label
class DeviceLabelNotFound(NotFound):
message = _("Device label %(uuid)s could not be found.")
class DeviceLabelAlreadyExists(Conflict):
message = _("Device label %(label)s already "
"exists on this host %(host)s.")
class DeviceLabelNotFoundByKey(NotFound):
message = _("Device label %(label)s could not be found.")
class DeviceLabelInvalid(Invalid):
message = _("Device label is invalid. Reason: %(reason)s")
# Device Image Label
class DeviceImageLabelNotFound(NotFound):
message = _("Device image label %(uuid)s could not be found.")
class DeviceImageLabelAlreadyExists(Conflict):
message = _("Device image is already applied to label %(uuid)s.")
class DeviceImageLabelNotFoundByKey(NotFound):
message = _("Device image %(image_id)s "
"and label ID %(label_id)s not found")
# Device Image State
class DeviceImageStateAlreadyExists(Conflict):
message = _(
"A device to image mapping with id %(uuid)s already exists.")
class DeviceImageStateNotFound(NotFound):
message = _("A device to image mapping with id %(id)s not found")
class DeviceImageStateNotFoundByKey(NotFound):
message = _("Device image %(image_id)s "
"and device ID %(device_id)s not found")
#
# Kubernetes application and Helm related exceptions
#

View File

@ -2216,3 +2216,11 @@ def extract_certs_from_pem(pem_contents):
certs.append(cert)
start = start + index + len(marker)
return certs
def format_image_filename(device_image):
""" Format device image filename """
return "{}-{}-{}-{}.bit".format(device_image.bitstream_type,
device_image.pci_vendor,
device_image.pci_device,
device_image.uuid)

View File

@ -81,6 +81,7 @@ from sysinv.api.controllers.v1 import utils
from sysinv.api.controllers.v1 import vim_api
from sysinv.common import constants
from sysinv.common import ceph as cceph
from sysinv.common import device as dconstants
from sysinv.common import exception
from sysinv.common import image_versions
from sysinv.common import fm
@ -11376,3 +11377,37 @@ class ConductorManager(service.PeriodicService):
kube_upgrade_obj = objects.kube_upgrade.get_one(context)
kube_upgrade_obj.state = kubernetes.KUBE_UPGRADED_NETWORKING
kube_upgrade_obj.save()
def store_bitstream_file(self, context, filename):
"""Store FPGA bitstream file """
image_file_path = os.path.join(dconstants.DEVICE_IMAGE_PATH, filename)
image_tmp_path = os.path.join(dconstants.DEVICE_IMAGE_TMP_PATH, filename)
try:
os.makedirs(dconstants.DEVICE_IMAGE_PATH)
except OSError as oe:
if (oe.errno != errno.EEXIST or
not os.path.isdir(dconstants.DEVICE_IMAGE_PATH)):
LOG.error("Failed to create dir %s" % dconstants.DEVICE_IMAGE_PATH)
raise
shutil.copyfile(image_tmp_path, image_file_path)
LOG.info("copied %s to %s" % (image_tmp_path, image_file_path))
def delete_bitstream_file(self, context, filename):
"""Delete FPGA bitstream file"""
image_file_path = os.path.join(dconstants.DEVICE_IMAGE_PATH, filename)
try:
os.remove(image_file_path)
except OSError:
LOG.exception("Failed to delete bitstream file %s" % image_file_path)
def host_device_image_update(self, context, host_uuid):
"""Update the device image on this host"""
host_obj = objects.host.get_by_uuid(context, host_uuid)
LOG.info("Updating device image on %s" % host_obj.hostname)
def host_device_image_update_abort(self, context, host_uuid):
"""Abort device image update on this host"""
host_obj = objects.host.get_by_uuid(context, host_uuid)
LOG.info("Aborting device image update on %s" % host_obj.hostname)

View File

@ -16,7 +16,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
"""
@ -1891,3 +1891,43 @@ class ConductorAPI(sysinv.openstack.common.rpc.proxy.RpcProxy):
"""
return self.cast(context, self.make_msg('kube_upgrade_networking',
kube_version=kube_version))
def store_bitstream_file(self, context, filename):
"""Asynchronously, have the conductor store the device image
on this host.
:param context: request context
:param filename: name of the bitstream file
"""
return self.cast(context, self.make_msg('store_bitstream_file',
filename=filename))
def delete_bitstream_file(self, context, filename):
"""Asynchronously, have the conductor remove the device image
on this host.
:param context: request context
:param filename: name of the bitstream file
"""
return self.cast(context, self.make_msg('delete_bitstream_file',
filename=filename))
def host_device_image_update(self, context, host_uuid):
"""Asynchronously, have the conductor update the device image
on this host.
:param context: request context
:param host_uuid: uuid or id of the host
"""
return self.cast(context, self.make_msg('host_device_image_update',
host_uuid=host_uuid))
def host_device_image_update_abort(self, context, host_uuid):
"""Asynchronously, have the conductor abort the device image update
on this host.
:param context: request context
:param host_uuid: uuid or id of the host
"""
return self.cast(context, self.make_msg('host_device_image_update_abort',
host_uuid=host_uuid))

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
"""SQLAlchemy storage backend."""
@ -45,6 +45,7 @@ from oslo_utils import uuidutils
from sysinv._i18n import _
from sysinv import objects
from sysinv.common import constants
from sysinv.common import device as dconstants
from sysinv.common import exception
from sysinv.common import utils
from sysinv.db import api
@ -1149,6 +1150,26 @@ def add_host_fs_filter_by_ihost(query, value):
return query.filter(models.ihost.uuid == value)
def add_deviceimage_filter(query, value):
"""Adds a deviceimage-specific filter to a query.
:param query: Initial query to add filter to.
:param value: Value for filtering results by.
:return: Modified query.
"""
if uuidutils.is_uuid_like(value):
return query.filter(or_(models.DeviceImageRootKey.uuid == value,
models.DeviceImageFunctional.uuid == value,
models.DeviceImageKeyRevocation.uuid == value))
elif utils.is_int_like(value):
return query.filter(or_(models.DeviceImageRootKey.id == value,
models.DeviceImageFunctional.id == value,
models.DeviceImageKeyRevocation.id == value))
else:
return add_identity_filter(query, value, use_name=True)
class Connection(api.Connection):
"""SqlAlchemy connection."""
@ -1793,6 +1814,87 @@ class Connection(api.Connection):
filter_by(id=memory_id).\
delete()
@objects.objectify(objects.fpga_device)
def fpga_device_create(self, hostid, values):
if utils.is_int_like(hostid):
host = self.ihost_get(int(hostid))
elif utils.is_uuid_like(hostid):
host = self.ihost_get(hostid.strip())
elif isinstance(hostid, models.ihost):
host = hostid
else:
raise exception.NodeNotFound(node=hostid)
values['host_id'] = host['id']
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
fpga_device = models.FpgaDevice()
fpga_device.update(values)
with _session_for_write() as session:
try:
session.add(fpga_device)
session.flush()
except db_exc.DBDuplicateEntry:
LOG.error("Failed to add FPGA device (uuid: %s), FPGA device with PCI "
"address %s on host %s already exists" %
(values['uuid'],
values['pciaddr'],
values['host_id']))
raise exception.PCIAddrAlreadyExists(pciaddr=values['pciaddr'],
host=values['host_id'])
return self._fpga_device_get(values['pciaddr'], values['host_id'])
def _fpga_device_get(self, pciaddr, hostid=None):
query = model_query(models.FpgaDevice)
if hostid:
query = query.filter_by(host_id=hostid)
query = add_identity_filter(query, pciaddr, use_pciaddr=True)
try:
result = query.one()
except NoResultFound:
raise exception.PCIAddrNotFound(pciaddr=pciaddr)
return result
@objects.objectify(objects.fpga_device)
def fpga_device_get(self, deviceid, hostid=None):
return self._fpga_device_get(deviceid, hostid)
@objects.objectify(objects.fpga_device)
def fpga_device_update(self, device_id, values, forihostid=None):
with _session_for_write() as session:
# May need to reserve in multi controller system; ref sysinv
query = model_query(models.FpgaDevice, read_deleted="no",
session=session)
if forihostid:
query = query.filter_by(host_id=forihostid)
try:
query = add_identity_filter(query, device_id)
result = query.one()
for k, v in values.items():
setattr(result, k, v)
except NoResultFound:
raise exception.InvalidParameterValue(
err="No entry found for device %s" % device_id)
except MultipleResultsFound:
raise exception.InvalidParameterValue(
err="Multiple entries found for device %s" % device_id)
return query.one()
@objects.objectify(objects.fpga_device)
def fpga_device_get_by_host(self, host, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.FpgaDevice)
query = add_device_filter_by_host(query, host)
return _paginate_query(models.FpgaDevice, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.pci_device)
def pci_device_create(self, hostid, values):
@ -8259,3 +8361,423 @@ class Connection(api.Connection):
except NoResultFound:
raise exception.KubeUpgradeNotFound(upgrade_id=upgrade_id)
query.delete()
def _deviceimage_get(self, model_class, deviceimage_id, obj=None):
session = None
if obj:
session = inspect(obj).session
query = model_query(model_class, session=session)
query = add_deviceimage_filter(query, deviceimage_id)
try:
result = query.one()
except NoResultFound:
raise exception.DeviceImageNotFound(
deviceimage_uuid=deviceimage_id)
except MultipleResultsFound:
raise exception.InvalidParameterValue(
err="Multiple entries found for deviceimage %s" % deviceimage_id)
return result
def _deviceimage_get_one(self, deviceimage_id, deviceimage=None):
entity = with_polymorphic(models.DeviceImage, '*')
query = model_query(entity)
query = add_deviceimage_filter(query, deviceimage_id)
if deviceimage is not None:
query = query.filter_by(network_type=deviceimage)
try:
result = query.one()
except NoResultFound:
raise exception.DeviceImageNotFound(
deviceimage_uuid=deviceimage_id)
except MultipleResultsFound:
raise exception.InvalidParameterValue(
err="Multiple entries found for deviceimage %s" % deviceimage_id)
return result
def _deviceimage_create(self, obj, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
with _session_for_write() as session:
# The id is null for ae interfaces with more than one member interface
temp_id = obj.id
obj.update(values)
if obj.id is None:
obj.id = temp_id
try:
session.add(obj)
session.flush()
except db_exc.DBDuplicateEntry:
LOG.error("Failed to add deviceimage (uuid: %s), "
"name %s already exists." %
(values['uuid'], values.get('name')))
raise exception.DeviceImageAlreadyExists(
name=values.get('name'))
return self._deviceimage_get(type(obj), values['uuid'])
@objects.objectify(objects.device_image)
def deviceimage_create(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
bitstream_type = values.get('bitstream_type')
if bitstream_type == dconstants.BITSTREAM_TYPE_ROOT_KEY:
deviceimage = models.DeviceImageRootKey()
elif bitstream_type == dconstants.BITSTREAM_TYPE_FUNCTIONAL:
deviceimage = models.DeviceImageFunctional()
elif bitstream_type == dconstants.BITSTREAM_TYPE_KEY_REVOCATION:
deviceimage = models.DeviceImageKeyRevocation()
else:
raise exception.DeviceImageTypeUnsupported(
bitstream_type=bitstream_type)
return self._deviceimage_create(deviceimage, values)
@objects.objectify(objects.device_image)
def deviceimage_get(self, deviceimage_id):
return self._deviceimage_get_one(deviceimage_id)
def _add_deviceimage_filters(self, query, filters):
if filters is None:
filters = dict()
supported_filters = {'bitstream_type',
'name',
}
unsupported_filters = set(filters).difference(supported_filters)
if unsupported_filters:
msg = _("SqlAlchemy API does not support "
"filtering by %s") % ', '.join(unsupported_filters)
raise ValueError(msg)
for field in supported_filters:
if field in filters:
query = query.filter_by(**{field: filters[field]})
return query
@objects.objectify(objects.device_image)
def deviceimages_get_all(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
with _session_for_read() as session:
deviceimages = with_polymorphic(models.DeviceImage, '*')
query = model_query(deviceimages, session=session)
query = self._add_deviceimage_filters(query, filters)
return _paginate_query(models.DeviceImage, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.device_image)
def deviceimage_update(self, deviceimage_uuid, values):
with _session_for_write() as session:
query = model_query(models.DeviceImage, session=session)
query = add_identity_filter(query, deviceimage_uuid)
count = query.update(values, synchronize_session='fetch')
if count != 1:
raise exception.DeviceImageNotFound(
deviceimage_uuid=deviceimage_uuid)
return query.one()
def deviceimage_destroy(self, deviceimage_uuid):
query = model_query(models.DeviceImage)
query = add_identity_filter(query, deviceimage_uuid)
try:
query.one()
except NoResultFound:
raise exception.DeviceImageNotFound(
deviceimage_uuid=deviceimage_uuid)
query.delete()
def _device_label_get(self, device_label_id):
query = model_query(models.DeviceLabel)
query = add_identity_filter(query, device_label_id)
try:
result = query.one()
except NoResultFound:
raise exception.DeviceLabelNotFound(uuid=device_label_id)
return result
@objects.objectify(objects.device_label)
def device_label_create(self, device_uuid, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
values['device_uuid'] = device_uuid
host_device_label = models.DeviceLabel()
host_device_label.update(values)
with _session_for_write() as session:
try:
session.add(host_device_label)
session.flush()
except db_exc.DBDuplicateEntry:
LOG.error("Failed to add host device label %s. "
"Already exists with this uuid" %
(values['label_key']))
raise exception.DeviceLabelAlreadyExists(
label=values['label_key'], host=values['host_uuid'])
return self._device_label_get(values['uuid'])
@objects.objectify(objects.device_label)
def device_label_get(self, uuid):
query = model_query(models.DeviceLabel)
query = query.filter_by(uuid=uuid)
try:
result = query.one()
except NoResultFound:
raise exception.InvalidParameterValue(
err="No device label entry found for %s" % uuid)
return result
@objects.objectify(objects.device_label)
def device_label_get_all(self, deviceid=None):
query = model_query(models.DeviceLabel, read_deleted="no")
if deviceid:
query = query.filter_by(device_id=deviceid)
return query.all()
@objects.objectify(objects.device_label)
def device_label_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
return _paginate_query(models.DeviceLabel, limit, marker,
sort_key, sort_dir)
@objects.objectify(objects.device_label)
def device_label_get_by_label(self, label_key, label_value,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceLabel)
query = query.filter_by(label_key=label_key,
label_value=label_value)
return query.all()
@objects.objectify(objects.device_label)
def device_label_update(self, uuid, values):
with _session_for_write() as session:
query = model_query(models.DeviceLabel, session=session)
query = query.filter_by(uuid=uuid)
count = query.update(values, synchronize_session='fetch')
if count == 0:
raise exception.DeviceLabelNotFound(uuid)
return query.one()
def device_label_destroy(self, uuid):
with _session_for_write() as session:
query = model_query(models.DeviceLabel, session=session)
query = query.filter_by(uuid=uuid)
try:
query.one()
except NoResultFound:
raise exception.DeviceLabelNotFound(uuid)
query.delete()
@objects.objectify(objects.device_label)
def device_label_get_by_device(self, device_uuid,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceLabel)
query = query.filter_by(pcidevice_uuid=device_uuid)
return _paginate_query(models.DeviceLabel, limit, marker,
sort_key, sort_dir, query)
def _device_label_query(self, device_id, label_key, session=None):
query = model_query(models.DeviceLabel, session=session)
query = query.filter(models.DeviceLabel.pcidevice_id == device_id)
query = query.filter(models.DeviceLabel.label_key == label_key)
try:
result = query.one()
except NoResultFound:
raise exception.DeviceLabelNotFoundByKey(label=label_key)
return result
@objects.objectify(objects.device_label)
def device_label_query(self, device_id, label_key):
return self._device_label_query(device_id, label_key)
def count_hosts_by_device_label(self, device_label):
query = model_query(models.DeviceLabel, read_deleted="no")
query = query.filter(models.DeviceLabel.label_key == device_label)
return query.count()
def _device_image_label_get(self, device_image_label_id):
query = model_query(models.DeviceImageLabel)
query = add_identity_filter(query, device_image_label_id)
try:
result = query.one()
except NoResultFound:
raise exception.DeviceLabelNotFound(uuid=device_image_label_id)
return result
@objects.objectify(objects.device_image_label)
def device_image_label_create(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
device_image_label = models.DeviceImageLabel()
device_image_label.update(values)
with _session_for_write() as session:
try:
session.add(device_image_label)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.DeviceImageLabelAlreadyExists(
uuid=values['uuid'])
return self._device_image_label_get(values['uuid'])
@objects.objectify(objects.device_image_label)
def device_image_label_get(self, uuid):
query = model_query(models.DeviceImageLabel)
query = query.filter_by(uuid=uuid)
try:
result = query.one()
except NoResultFound:
raise exception.InvalidParameterValue(
err="No device image label entry found for %s" % uuid)
return result
@objects.objectify(objects.device_image_label)
def device_image_label_update(self, uuid, values):
with _session_for_write() as session:
query = model_query(models.DeviceImageLabel, session=session)
query = query.filter_by(uuid=uuid)
count = query.update(values, synchronize_session='fetch')
if count == 0:
raise exception.DeviceImageLabelNotFound(uuid)
return query.one()
@objects.objectify(objects.device_image_label)
def device_image_label_get_by_image(self, image_id,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceImageLabel)
query = query.filter_by(image_id=image_id)
return query.all()
@objects.objectify(objects.device_image_label)
def device_image_label_get_by_image_label(self, image_id, label_id,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceImageLabel)
query = query.filter_by(image_id=image_id, label_id=label_id)
try:
return query.one()
except NoResultFound:
raise exception.DeviceImageLabelNotFoundByKey(
image_id=image_id, label_id=label_id)
def device_image_label_destroy(self, id):
with _session_for_write() as session:
query = model_query(models.DeviceImageLabel, session=session)
query = add_identity_filter(query, id)
try:
query.one()
except NoResultFound:
raise exception.DeviceImageLabelNotFound(uuid=id)
query.delete()
def _device_image_state_get(self, id):
query = model_query(models.DeviceImageState)
query = add_identity_filter(query, id)
try:
return query.one()
except NoResultFound:
raise exception.DeviceImageStateNotFound(id=id)
@objects.objectify(objects.device_image_state)
def device_image_state_create(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
device_image_state = models.DeviceImageState()
device_image_state.update(values)
with _session_for_write() as session:
try:
session.add(device_image_state)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.DeviceImageStateAlreadyExists(uuid=values['uuid'])
return self._device_image_state_get(values['uuid'])
@objects.objectify(objects.device_image_state)
def device_image_state_get(self, id):
return self._device_image_state_get(id)
@objects.objectify(objects.device_image_state)
def device_image_state_get_one(self):
query = model_query(models.DeviceImageState)
try:
return query.one()
except NoResultFound:
raise exception.NotFound()
@objects.objectify(objects.device_image_state)
def device_image_state_get_list(self, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceImageState)
return _paginate_query(models.DeviceImageState, limit, marker,
sort_key, sort_dir, query)
@objects.objectify(objects.device_image_state)
def device_image_state_update(self, id, values):
with _session_for_write() as session:
query = model_query(models.DeviceImageState, session=session)
query = add_identity_filter(query, id)
count = query.update(values, synchronize_session='fetch')
if count != 1:
raise exception.DeviceImageStateNotFound(id=id)
return query.one()
def device_image_state_destroy(self, id):
with _session_for_write() as session:
query = model_query(models.DeviceImageState, session=session)
query = add_identity_filter(query, id)
try:
query.one()
except NoResultFound:
raise exception.DeviceImageStateNotFound(id=id)
query.delete()
@objects.objectify(objects.device_image_state)
def device_image_state_get_by_image_device(self, image_id, pcidevice_id,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceImageState)
query = query.filter_by(image_id=image_id,
pcidevice_id=pcidevice_id)
try:
return query.one()
except NoResultFound:
raise exception.DeviceImageStateNotFoundByKey(image_id=image_id,
device_id=pcidevice_id)
@objects.objectify(objects.device_image_state)
def device_image_state_get_all(self, host_id=None, pcidevice_id=None,
image_id=None, status=None,
limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.DeviceImageState)
if host_id:
query = query.filter_by(host_id=host_id)
if pcidevice_id:
query = query.filter_by(pcidevice_id=pcidevice_id)
if image_id:
query = query.filter_by(image_id=image_id)
if status:
query = query.filter_by(status=status)
return query.all()

View File

@ -0,0 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
def upgrade(migrate_engine):
pass
def downgrade(migration_engine):
pass

View File

@ -0,0 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
def upgrade(migrate_engine):
pass
def downgrade(migration_engine):
pass

View File

@ -0,0 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
def upgrade(migrate_engine):
pass
def downgrade(migration_engine):
pass

View File

@ -0,0 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
def upgrade(migrate_engine):
pass
def downgrade(migration_engine):
pass

View File

@ -0,0 +1,14 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
def upgrade(migrate_engine):
pass
def downgrade(migration_engine):
pass

View File

@ -0,0 +1,78 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import Column, MetaData, Table
from sqlalchemy import String, Integer, DateTime, Boolean
from sqlalchemy import ForeignKey, UniqueConstraint
ENGINE = 'InnoDB'
CHARSET = 'utf8'
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
Table('i_host', meta, autoload=True)
pci_devices = Table('pci_devices', meta, autoload=True)
fpga_devices = Table(
'fpga_devices',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('host_id', Integer, ForeignKey('i_host.id',
ondelete='CASCADE')),
Column('pci_id', Integer, ForeignKey('pci_devices.id',
ondelete='CASCADE')),
Column('pciaddr', String(32)),
Column('bmc_build_version', String(32)),
Column('bmc_fw_version', String(32)),
Column('root_key', String(128)),
Column('revoked_key_ids', String(512)),
Column('boot_page', String(16)),
Column('bitstream_id', String(32)),
UniqueConstraint('pciaddr', 'host_id', name='u_pciaddrhost'),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
fpga_devices.create()
Table('ports', meta, autoload=True)
fpga_ports = Table(
'fpga_ports',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('port_id', Integer, ForeignKey('ports.id', ondelete='CASCADE')),
Column('fpga_id', Integer, ForeignKey('fpga_devices.id', ondelete='CASCADE')),
UniqueConstraint('port_id', 'fpga_id', name='u_port_id@fpga_id'),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
fpga_ports.create()
# Add new fields to pci_device table
pci_devices.create_column(Column('status', String(128)))
pci_devices.create_column(Column('needs_firmware_update', Boolean, default=False))
def downgrade(migrate_engine):
# Downgrade is unsupported in this release.
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -0,0 +1,201 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sqlalchemy import DateTime, String, Integer, Boolean, Text
from sqlalchemy import Column, MetaData, Table
from sqlalchemy import ForeignKey, UniqueConstraint
ENGINE = 'InnoDB'
CHARSET = 'utf8'
def upgrade(migrate_engine):
"""
This database upgrade creates a device_images, device_labels and
device_image_state tables.
"""
meta = MetaData()
meta.bind = migrate_engine
device_images = Table(
'device_images',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('bitstream_type', String(255)),
# The pci_vendor and pci_device fields cannot be referenced from the
# pci_devices table. The device images intended for a specific
# vendor/device on a subcloud may not be present on the
# SystemController region
Column('pci_vendor', String(4)),
Column('pci_device', String(4)),
Column('name', String(255)),
Column('description', String(255)),
Column('image_version', String(255)),
Column('applied', Boolean, nullable=False, default=False),
Column('capabilities', Text),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
device_images_rootkey = Table(
'device_images_rootkey',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer,
ForeignKey('device_images.id', ondelete="CASCADE"),
primary_key=True, nullable=False),
Column('key_signature', String(255), nullable=False),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
device_images_functional = Table(
'device_images_functional',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer,
ForeignKey('device_images.id', ondelete="CASCADE"),
primary_key=True, nullable=False),
Column('bitstream_id', String(255), nullable=False),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
device_images_keyrevocation = Table(
'device_images_keyrevocation',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer,
ForeignKey('device_images.id', ondelete="CASCADE"),
primary_key=True, nullable=False),
Column('revoke_key_id', Integer, nullable=False),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
host = Table('i_host', meta, autoload=True)
Table('pci_devices', meta, autoload=True)
Table('fpga_devices', meta, autoload=True)
device_labels = Table(
'device_labels',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('host_id', Integer,
ForeignKey('i_host.id', ondelete='CASCADE')),
Column('pcidevice_id', Integer,
ForeignKey('pci_devices.id', ondelete='CASCADE')),
Column('fpgadevice_id', Integer,
ForeignKey('fpga_devices.id', ondelete='CASCADE')),
Column('label_key', String(384)),
Column('label_value', String(128)),
Column('capabilities', Text),
UniqueConstraint('pcidevice_id', 'label_key',
name='u_pcidevice_id@label_key'),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
device_image_labels = Table(
'device_image_labels',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('image_id', Integer,
ForeignKey('device_images.id', ondelete='CASCADE')),
Column('label_id', Integer,
ForeignKey('device_labels.id', ondelete='CASCADE')),
Column('status', String(128)),
Column('capabilities', Text),
UniqueConstraint('image_id', 'label_id', name='u_image_id@label_id'),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
device_image_state = Table(
'device_image_state',
meta,
Column('created_at', DateTime),
Column('updated_at', DateTime),
Column('deleted_at', DateTime),
Column('id', Integer, primary_key=True, nullable=False),
Column('uuid', String(36), unique=True),
Column('host_id', Integer,
ForeignKey('i_host.id', ondelete='CASCADE')),
Column('pcidevice_id', Integer,
ForeignKey('pci_devices.id', ondelete='CASCADE')),
Column('image_id', Integer,
ForeignKey('device_images.id', ondelete='CASCADE')),
Column('status', String(128)),
Column('update_start_time', DateTime),
Column('capabilities', Text),
mysql_engine=ENGINE,
mysql_charset=CHARSET,
)
tables = (
device_images,
device_images_rootkey,
device_images_functional,
device_images_keyrevocation,
device_labels,
device_image_labels,
device_image_state,
)
for index, table in enumerate(tables):
try:
table.create()
except Exception:
# If an error occurs, drop all tables created so far to return
# to the previously existing state.
meta.drop_all(tables=tables[:index])
raise
# Add the device_image_update attribute
host.create_column(Column('device_image_update', String(64)))
host.create_column(Column('reboot_needed', Boolean, default=False))
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
# Downgrade is unsupported.
raise NotImplementedError('SysInv database downgrade is unsupported.')

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -234,6 +234,9 @@ class ihost(Base):
ttys_dcd = Column(Boolean)
iscsi_initiator_name = Column(String(64))
device_image_update = Column(String(64))
reboot_needed = Column(Boolean, nullable=False, default=False)
forisystemid = Column(Integer,
ForeignKey('i_system.id', ondelete='CASCADE'))
peer_id = Column(Integer,
@ -1459,11 +1462,170 @@ class PciDevice(Base):
enabled = Column(Boolean)
extra_info = Column(Text)
host = relationship("ihost", lazy="joined", join_depth=1)
status = Column(String(128))
needs_firmware_update = Column(Boolean, nullable=False, default=False)
host = relationship("ihost", lazy="joined", join_depth=1)
fpga = relationship("FpgaDevice", lazy="joined", uselist=False, join_depth=1)
UniqueConstraint('pciaddr', 'host_id', name='u_pciaddrhost')
class FpgaDevice(Base):
__tablename__ = 'fpga_devices'
id = Column(Integer, primary_key=True, nullable=False)
uuid = Column(String(36))
host_id = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE'))
pci_id = Column(Integer, ForeignKey('pci_devices.id', ondelete='CASCADE'))
pciaddr = Column(String(32))
bmc_build_version = Column(String(32))
bmc_fw_version = Column(String(32))
root_key = Column(String(128))
revoked_key_ids = Column(String(512))
boot_page = Column(String(16))
bitstream_id = Column(String(32))
host = relationship("ihost", lazy="joined", join_depth=1)
pcidevice = relationship("PciDevice", lazy="joined", join_depth=1)
UniqueConstraint('pciaddr', 'host_id', name='u_pciaddrhost')
class FpgaPorts(Base):
__tablename__ = 'fpga_ports'
id = Column(Integer, primary_key=True, nullable=False)
uuid = Column(String(36), unique=True)
port_id = Column(Integer, ForeignKey('ports.id', ondelete='CASCADE'))
fpga_id = Column(Integer,
ForeignKey('fpga_devices.id', ondelete='CASCADE'))
ports = relationship("Ports", lazy="joined", join_depth=1)
fpga_device = relationship("FpgaDevice", lazy="joined",
backref="fpga_ports", join_depth=1)
UniqueConstraint('port_id', 'fpga_id', name='u_port_id@fpga_id')
class DeviceImage(Base):
__tablename__ = 'device_images'
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
bitstream_type = Column(String(255))
pci_vendor = Column(String(4))
pci_device = Column(String(4))
name = Column(String(255))
description = Column(String(255))
image_version = Column(String(255))
applied = Column(Boolean, nullable=False, default=False)
capabilities = Column(JSONEncodedDict)
__mapper_args__ = {
'polymorphic_identity': 'deviceimage',
'polymorphic_on': bitstream_type,
'with_polymorphic': '*',
}
class DeviceImageCommon(object):
@declared_attr
def id(cls):
return Column(Integer,
ForeignKey('device_images.id', ondelete="CASCADE"),
primary_key=True, nullable=False)
class DeviceImageRootKey(DeviceImageCommon, DeviceImage):
__tablename__ = 'device_images_rootkey'
key_signature = Column(String(255), nullable=True)
__mapper_args__ = {
'polymorphic_identity': 'root-key',
}
class DeviceImageFunctional(DeviceImageCommon, DeviceImage):
__tablename__ = 'device_images_functional'
bitstream_id = Column(String(255), nullable=True)
__mapper_args__ = {
'polymorphic_identity': 'functional',
}
class DeviceImageKeyRevocation(DeviceImageCommon, DeviceImage):
__tablename__ = 'device_images_keyrevocation'
revoke_key_id = Column(Integer, nullable=True)
__mapper_args__ = {
'polymorphic_identity': 'key-revocation',
}
class DeviceLabel(Base):
__tablename__ = 'device_labels'
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
host_id = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE'))
pcidevice_id = Column(Integer, ForeignKey('pci_devices.id',
ondelete='CASCADE'))
fpgadevice_id = Column(Integer, ForeignKey('fpga_devices.id',
ondelete='CASCADE'))
capabilities = Column(JSONEncodedDict)
host = relationship("ihost", lazy="joined", join_depth=1)
pcidevice = relationship("PciDevice", lazy="joined", join_depth=1)
fpgadevice = relationship("FpgaDevice", lazy="joined", join_depth=1)
label_key = Column(String(384))
label_value = Column(String(128))
UniqueConstraint('pcidevice_id', 'label_key', name='u_pcidevice_id@label_key')
class DeviceImageLabel(Base):
__tablename__ = 'device_image_labels'
id = Column(Integer, primary_key=True, nullable=False)
uuid = Column(String(36), unique=True)
image_id = Column(
Integer, ForeignKey('device_images.id', ondelete='CASCADE'))
label_id = Column(
Integer, ForeignKey('device_labels.id', ondelete='CASCADE'))
status = Column(String(128))
capabilities = Column(JSONEncodedDict)
image = relationship(
"DeviceImage", lazy="joined", backref="device_image_labels")
label = relationship(
"DeviceLabel", lazy="joined", backref="device_image_labels")
UniqueConstraint('image_id', 'label_id', name='u_image_id@label_id')
class DeviceImageState(Base):
__tablename__ = 'device_image_state'
id = Column(Integer, primary_key=True, nullable=False)
uuid = Column(String(36), unique=True)
host_id = Column(Integer, ForeignKey('i_host.id', ondelete='CASCADE'))
pcidevice_id = Column(
Integer, ForeignKey('pci_devices.id', ondelete='CASCADE'))
image_id = Column(
Integer, ForeignKey('device_images.id', ondelete='CASCADE'))
status = Column(String(128))
update_start_time = Column(DateTime(timezone=False))
capabilities = Column(JSONEncodedDict)
host = relationship("ihost", lazy="joined", join_depth=1)
pcidevice = relationship(
"PciDevice", lazy="joined", backref="device_image_state")
image = relationship(
"DeviceImage", lazy="joined", backref="device_image_state")
class SoftwareUpgrade(Base):
__tablename__ = 'software_upgrade'

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
#
# Copyright (c) 2013-2019 Wind River Systems, Inc.
# Copyright (c) 2013-2020 Wind River Systems, Inc.
#
@ -28,7 +28,12 @@ from sysinv.objects import community
from sysinv.objects import controller_fs
from sysinv.objects import cpu
from sysinv.objects import datanetwork
from sysinv.objects import device_image
from sysinv.objects import device_image_label
from sysinv.objects import device_image_state
from sysinv.objects import device_label
from sysinv.objects import disk
from sysinv.objects import fpga_device
from sysinv.objects import partition
from sysinv.objects import dns
from sysinv.objects import drbdconfig
@ -195,6 +200,11 @@ kube_upgrade = kube_upgrade.KubeUpgrade
kube_version = kube_version.KubeVersion
datanetwork = datanetwork.DataNetwork
host_fs = host_fs.HostFS
device_image = device_image.DeviceImage
device_image_label = device_image_label.DeviceImageLabel
device_image_state = device_image_state.DeviceImageState
device_label = device_label.DeviceLabel
fpga_device = fpga_device.FPGADevice
__all__ = (system,
cluster,
@ -268,6 +278,10 @@ __all__ = (system,
datanetwork,
interface_network,
host_fs,
device_image,
device_image_label,
device_label,
fpga_device,
# alias objects for RPC compatibility
ihost,
ilvg,

View File

@ -0,0 +1,45 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class DeviceImage(base.SysinvObject):
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {'id': int,
'uuid': utils.uuid_or_none,
'bitstream_type': utils.str_or_none,
'pci_vendor': utils.str_or_none,
'pci_device': utils.str_or_none,
'bitstream_id': utils.str_or_none,
'key_signature': utils.str_or_none,
'revoke_key_id': utils.int_or_none,
'name': utils.str_or_none,
'description': utils.str_or_none,
'image_version': utils.str_or_none,
'applied': utils.bool_or_none,
'capabilities': utils.dict_or_none,
}
_optional_fields = {'bitstream_id',
'key_signature',
'revoke_key_id',
'name',
'description',
'image_version'}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.deviceimage_get(uuid)
def save_changes(self, context, updates):
self.dbapi.device_image_update(self.uuid, # pylint: disable=no-member
updates)

View File

@ -0,0 +1,40 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class DeviceImageLabel(base.SysinvObject):
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {'id': int,
'uuid': utils.uuid_or_none,
'image_id': utils.int_or_none,
'image_uuid': utils.uuid_or_none,
'label_id': utils.int_or_none,
'label_uuid': utils.uuid_or_none,
'status': utils.str_or_none,
'capabilities': utils.dict_or_none,
}
_foreign_fields = {
'image_id': 'image:id',
'label_id': 'label:id',
'image_uuid': 'image:uuid',
'label_uuid': 'label:uuid',
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.device_image_label_get(uuid)
def save_changes(self, context, updates):
self.dbapi.device_image_label_update(self.uuid, # pylint: disable=no-member
updates)

View File

@ -0,0 +1,42 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class DeviceImageState(base.SysinvObject):
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {'id': int,
'uuid': utils.uuid_or_none,
'host_id': utils.int_or_none,
'host_uuid': utils.uuid_or_none,
'pcidevice_id': utils.int_or_none,
'pcidevice_uuid': utils.uuid_or_none,
'image_id': utils.int_or_none,
'image_uuid': utils.uuid_or_none,
'status': utils.str_or_none,
'update_start_time': utils.datetime_or_str_or_none,
'capabilities': utils.dict_or_none,
}
_foreign_fields = {
'host_uuid': 'host:uuid',
'pcidevice_uuid': 'pcidevice:uuid',
'image_uuid': 'image:uuid',
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.device_image_state_get(uuid)
def save_changes(self, context, updates):
self.dbapi.device_image_state_update(self.uuid, # pylint: disable=no-member
updates)

View File

@ -0,0 +1,43 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class DeviceLabel(base.SysinvObject):
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {
'id': int,
'uuid': utils.str_or_none,
'host_id': utils.str_or_none,
'host_uuid': utils.str_or_none,
'label_key': utils.str_or_none,
'label_value': utils.str_or_none,
'pcidevice_id': utils.int_or_none,
'pcidevice_uuid': utils.str_or_none,
'fpgadevice_id': utils.int_or_none,
'fpgadevice_uuid': utils.str_or_none,
'capabilities': utils.dict_or_none,
}
_foreign_fields = {
'host_uuid': 'host:uuid',
'pcidevice_uuid': 'pcidevice:uuid',
'fpgadevice_uuid': 'fpgadevice:uuid',
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.device_label_get(uuid)
def save_changes(self, context, updates):
self.dbapi.device_label_update(self.uuid, # pylint: disable=no-member
updates)

View File

@ -0,0 +1,41 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
class FPGADevice(base.SysinvObject):
dbapi = db_api.get_instance()
fields = {
'id': int,
'uuid': utils.str_or_none,
'host_id': utils.int_or_none,
'host_uuid': utils.str_or_none,
'pci_id': utils.int_or_none,
'pciaddr': utils.str_or_none,
'bmc_build_version': utils.str_or_none,
'bmc_fw_version': utils.str_or_none,
'root_key': utils.str_or_none,
'revoked_key_ids': utils.str_or_none,
'boot_page': utils.str_or_none,
'bitstream_id': utils.str_or_none,
}
_foreign_fields = {
'host_uuid': 'host:uuid'
}
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
return cls.dbapi.fpga_device_get(uuid)
def save_changes(self, context, updates):
self.dbapi.fpga_device_update(self.uuid, # pylint: disable=no-member
updates)

View File

@ -89,6 +89,8 @@ class Host(base.SysinvObject):
'install_state': utils.str_or_none,
'install_state_info': utils.str_or_none,
'iscsi_initiator_name': utils.str_or_none,
'device_image_update': utils.str_or_none,
'reboot_needed': utils.bool_or_none,
}
_foreign_fields = {

View File

@ -1,13 +1,9 @@
#
# Copyright (c) 2016 Wind River Systems, Inc.
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding=utf-8
#
from sysinv.db import api as db_api
from sysinv.objects import base
from sysinv.objects import utils
@ -39,10 +35,34 @@ class PCIDevice(base.SysinvObject):
'driver': utils.str_or_none,
'enabled': utils.bool_or_none,
'extra_info': utils.str_or_none,
'bmc_build_version': utils.str_or_none,
'bmc_fw_version': utils.str_or_none,
'root_key': utils.str_or_none,
'revoked_key_ids': utils.str_or_none,
'boot_page': utils.str_or_none,
'bitstream_id': utils.str_or_none,
'status': utils.str_or_none,
'needs_firmware_update': utils.bool_or_none,
}
_foreign_fields = {
'host_uuid': 'host:uuid'
'host_uuid': 'host:uuid',
'bmc_build_version': 'fpga:bmc_build_version',
'bmc_fw_version': 'fpga:bmc_fw_version',
'root_key': 'fpga:root_key',
'revoked_key_ids': 'fpga:revoked_key_ids',
'boot_page': 'fpga:boot_page',
'bitstream_id': 'fpga:bitstream_id',
}
_optional_fields = {
'bmc_build_version',
'bmc_fw_version',
'root_key',
'revoked_key_ids',
'boot_page',
'bitstream_id',
}
@base.remotable_classmethod

View File

@ -0,0 +1 @@
123456789

View File

@ -0,0 +1,519 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Tests for the API /device_images/ methods.
"""
import json
import mock
import os
from oslo_utils import uuidutils
from six.moves import http_client
from sysinv.common import constants
from sysinv.common import device as dconstants
from sysinv.tests.api import base
from sysinv.tests.db import base as dbbase
from sysinv.tests.db import utils as dbutils
class FakeConductorAPI(object):
def __init__(self):
self.store_bitstream_file = mock.MagicMock()
self.delete_bitstream_file = mock.MagicMock()
class TestDeviceImage(base.FunctionalTest, dbbase.BaseHostTestCase):
# API_HEADERS are a generic header passed to most API calls
API_HEADERS = {'User-Agent': 'sysinv-test'}
# API_PREFIX is the prefix for the URL
API_PREFIX = '/device_images'
# RESULT_KEY is the python table key for the list of results
RESULT_KEY = 'device_images'
# expected_api_fields are attributes that should be populated by
# an API query
expected_api_fields = ['id',
'uuid',
'bitstream_type',
'pci_vendor',
'pci_device',
'bitstream_id',
'key_signature',
'revoke_key_id',
]
# hidden_api_fields are attributes that should not be populated by
# an API query
hidden_api_fields = ['']
def setUp(self):
super(TestDeviceImage, self).setUp()
# Mock the Conductor API
self.fake_conductor_api = FakeConductorAPI()
p = mock.patch('sysinv.conductor.rpcapi.ConductorAPI')
self.mock_conductor_api = p.start()
self.mock_conductor_api.return_value = self.fake_conductor_api
self.addCleanup(p.stop)
def get_single_url(self, uuid):
return '%s/%s' % (self.API_PREFIX, uuid)
class TestListDeviceImage(TestDeviceImage):
def setUp(self):
super(TestListDeviceImage, self).setUp()
def test_one(self):
device_image = dbutils.create_test_device_image(
bitstream_type=dconstants.BITSTREAM_TYPE_FUNCTIONAL,
pci_vendor='80ee',
pci_device='beef',
bitstream_id='12345',
)
result = self.get_json('/device_images/%s' % device_image['uuid'])
# Verify that the upgrade has the expected attributes
self.assertEqual(result['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(result['pci_vendor'], '80ee')
self.assertEqual(result['pci_device'], 'beef')
self.assertEqual(result['bitstream_id'], '12345')
def test_list_all(self):
dbutils.create_test_device_image(
bitstream_type=dconstants.BITSTREAM_TYPE_FUNCTIONAL,
pci_vendor='80ee',
pci_device='beef',
bitstream_id='12345',
)
data = self.get_json('/device_images')
self.assertEqual(1, len(data['device_images']))
self.assertEqual(data['device_images'][0]['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(data['device_images'][0]['pci_vendor'], '80ee')
self.assertEqual(data['device_images'][0]['pci_device'], 'beef')
self.assertEqual(data['device_images'][0]['bitstream_id'], '12345')
class TestPostDeviceImage(TestDeviceImage, dbbase.ControllerHostTestCase):
def test_create_functional_image(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_FUNCTIONAL,
'pci_vendor': '80ee',
'pci_device': 'beef',
'bitstream_id': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images',
data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=False)
self.assertEqual(result.status_code, http_client.OK)
# Verify that the images were downloaded
self.fake_conductor_api.store_bitstream_file.\
assert_called_with(mock.ANY, mock.ANY)
resp = json.loads(result.body)
self.assertIn('device_image', resp)
resp_dict = resp.get('device_image')
# Verify that the device image has the expected attributes
self.assertEqual(resp_dict['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(resp_dict['pci_vendor'], '80ee')
self.assertEqual(resp_dict['pci_device'], 'beef')
self.assertEqual(resp_dict['bitstream_id'], '12345')
def test_create_root_key_image(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_ROOT_KEY,
'pci_vendor': '80ee',
'pci_device': 'beef',
'key_signature': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images',
data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=False)
self.assertEqual(result.status_code, http_client.OK)
# Verify that the images were downloaded
self.fake_conductor_api.store_bitstream_file.\
assert_called_with(mock.ANY, mock.ANY)
resp = json.loads(result.body)
self.assertIn('device_image', resp)
resp_dict = resp.get('device_image')
# Verify that the device image has the expected attributes
self.assertEqual(resp_dict['bitstream_type'],
dconstants.BITSTREAM_TYPE_ROOT_KEY)
self.assertEqual(resp_dict['pci_vendor'], '80ee')
self.assertEqual(resp_dict['pci_device'], 'beef')
self.assertEqual(resp_dict['key_signature'], '12345')
def test_create_revoke_key_image(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_KEY_REVOCATION,
'pci_vendor': '80ee',
'pci_device': 'beef',
'revoke_key_id': 12345,
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images',
data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=False)
self.assertEqual(result.status_code, http_client.OK)
# Verify that the images were downloaded
self.fake_conductor_api.store_bitstream_file.\
assert_called_with(mock.ANY, mock.ANY)
resp = json.loads(result.body)
self.assertIn('device_image', resp)
resp_dict = resp.get('device_image')
# Verify that the device image has the expected attributes
self.assertEqual(resp_dict['bitstream_type'],
dconstants.BITSTREAM_TYPE_KEY_REVOCATION)
self.assertEqual(resp_dict['pci_vendor'], '80ee')
self.assertEqual(resp_dict['pci_device'], 'beef')
self.assertEqual(resp_dict['revoke_key_id'], 12345)
def test_create_functional_image_failure(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_FUNCTIONAL,
'pci_vendor': '80ee',
'pci_device': 'beef',
'revoke_key_id': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images', data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=True)
self.assertIn("bitstream_id is required for functional bitstream type",
str(result))
def test_create_root_key_image_failure(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_ROOT_KEY,
'pci_vendor': '80ee',
'pci_device': 'beef',
'revoke_key_id': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images', data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=True)
self.assertIn("key_signature is required for root key bitstream type",
str(result))
def test_create_revoke_key_image_failure(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': dconstants.BITSTREAM_TYPE_KEY_REVOCATION,
'pci_vendor': '80ee',
'pci_device': 'beef',
'bitstream_id': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images', data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=True)
self.assertIn("revoke_key_id is required for key revocation bitstream"
" type", str(result))
def test_create_bitstream_type_invalid(self):
# Test creation of device image
bitstream_file = os.path.join(os.path.dirname(__file__), "data",
'bitstream.bit')
data = {
'bitstream_type': 'wrong_type',
'pci_vendor': '80ee',
'pci_device': 'beef',
'bitstream_id': '12345',
}
upload_file = [('file', bitstream_file)]
result = self.post_with_files('/device_images', data,
upload_files=upload_file,
headers=self.API_HEADERS,
expect_errors=True)
self.assertIn("Bitstream type wrong_type not supported", str(result))
class TestPatch(TestDeviceImage):
def setUp(self):
super(TestPatch, self).setUp()
self.controller = dbutils.create_test_ihost(
id='1',
uuid=None,
forisystemid=self.system.id,
hostname='controller-0',
personality=constants.CONTROLLER,
subfunctions=constants.CONTROLLER,
invprovision=constants.PROVISIONED
)
# Create a pci_device and fpga_device object
self.pci_device = dbutils.create_test_pci_devices(
host_id=self.controller.id,
pclass='Processing accelerators',
pclass_id='120000',)
self.fpga_device = dbutils.create_test_fpga_device(
host_id=self.controller.id,
pci_id=self.pci_device.id)
# Create a device image
self.device_image = dbutils.create_test_device_image(
bitstream_type=dconstants.BITSTREAM_TYPE_FUNCTIONAL,
pci_vendor='80ee',
pci_device='beef',
bitstream_id='12345')
self.device_image2 = dbutils.create_test_device_image(
bitstream_type=dconstants.BITSTREAM_TYPE_FUNCTIONAL,
pci_vendor='80ee',
pci_device='beef',
bitstream_id='6789')
def test_device_image_apply_all_hosts(self):
# Test applying device image to all hosts with fpga devices
# Apply the device image
path = '/device_images/%s?action=apply' % self.device_image.uuid
response = self.patch_json(path, {},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(response.json['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(response.json['pci_vendor'], '80ee')
self.assertEqual(response.json['pci_device'], 'beef')
self.assertEqual(response.json['bitstream_id'], '12345')
# Verify that an entry of image to device mapping is updated
dev_img_state = self.dbapi.device_image_state_get_by_image_device(
self.device_image.id, self.pci_device.id)
self.assertEqual(dconstants.DEVICE_IMAGE_UPDATE_PENDING,
dev_img_state.status)
# Verify that needs_firmware_update flag is updated in pci_device
pci_dev = self.dbapi.pci_device_get(self.pci_device.id)
self.assertEqual(pci_dev['needs_firmware_update'], True)
def test_device_image_apply_invalid_image(self):
# Test applying device image with non-existing image
# Apply the device image
path = '/device_images/%s?action=apply' % uuidutils.generate_uuid()
response = self.patch_json(path, {},
headers=self.API_HEADERS,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn("image does not exist",
response.json['error_message'])
def test_device_image_apply_with_label(self):
# Test applying device image to pci devices with specified label
# Assign label to a device
self.post_json('/device_labels',
{'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1'},
headers=self.API_HEADERS)
# Apply the device image with label
path = '/device_images/%s?action=apply' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(response.json['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(response.json['pci_vendor'], '80ee')
self.assertEqual(response.json['pci_device'], 'beef')
self.assertEqual(response.json['bitstream_id'], '12345')
self.assertEqual(response.json['applied_labels'], {'key1': 'value1'})
# Verify that the image to device mapping is updated
dev_img_state = self.dbapi.device_image_state_get_by_image_device(
self.device_image.id, self.pci_device.id)
self.assertEqual(dconstants.DEVICE_IMAGE_UPDATE_PENDING,
dev_img_state.status)
# Verify that needs_firmware_update flag is updated in pci_device
pci_dev = self.dbapi.pci_device_get(self.pci_device.id)
self.assertEqual(pci_dev['needs_firmware_update'], True)
def test_device_image_apply_invalid_label(self):
# Test applying device image with non-existing device label
# Apply the device image
path = '/device_images/%s?action=apply' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn("Device image apply failed: label key1=value1 does not"
" exist", response.json['error_message'])
def test_device_image_apply_overwrite_functional(self):
# Test applying second device image with label
# Assign label to a device
self.post_json('/device_labels',
{'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1'},
headers=self.API_HEADERS)
# Apply the device image with label
path = '/device_images/%s?action=apply' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
# Apply a second functional device image with label
path = '/device_images/%s?action=apply' % self.device_image2.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS,
expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
def test_device_image_remove_all_hosts(self):
# Test removing device image for all hosts with fpga devices
# Remove the device image
path = '/device_images/%s?action=remove' % self.device_image.uuid
response = self.patch_json(path, {},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(response.json['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(response.json['pci_vendor'], '80ee')
self.assertEqual(response.json['pci_device'], 'beef')
self.assertEqual(response.json['bitstream_id'], '12345')
# Verify that needs_firmware_update flag is updated in pci_device
pci_dev = self.dbapi.pci_device_get(self.pci_device.id)
self.assertEqual(pci_dev['needs_firmware_update'], False)
def test_device_image_remove_by_label(self):
# Test removing device image by device label
# Assign label to a device
self.post_json('/device_labels',
{'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1'},
headers=self.API_HEADERS)
# Apply the device image with label
path = '/device_images/%s?action=apply' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
# Remove the device image with label
path = '/device_images/%s?action=remove' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.assertEqual(response.json['bitstream_type'],
dconstants.BITSTREAM_TYPE_FUNCTIONAL)
self.assertEqual(response.json['pci_vendor'], '80ee')
self.assertEqual(response.json['pci_device'], 'beef')
self.assertEqual(response.json['bitstream_id'], '12345')
def test_device_image_remove_by_label_not_applied(self):
# Test removing device image by label where device label is not applied
# Assign label to a device
self.post_json('/device_labels',
{'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1'},
headers=self.API_HEADERS)
# Remove the device image with label
path = '/device_images/%s?action=remove' % self.device_image.uuid
response = self.patch_json(path, {'key1': 'value1'},
headers=self.API_HEADERS,
expect_errors=True)
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertIn("Device image %s not associated with label key1=value1" %
self.device_image.uuid, response.json['error_message'])
class TestDelete(TestDeviceImage):
def test_delete(self):
# Test deleting a device image
# Create the device image
device_image = dbutils.create_test_device_image(
bitstream_type=dconstants.BITSTREAM_TYPE_FUNCTIONAL,
pci_vendor='80ee',
pci_device='beef',
bitstream_id='12345')
# Delete the device image
self.delete('/device_images/%s' % device_image.uuid,
headers={'User-Agent': 'sysinv-test'})
# Verify the device image no longer exists
response = self.get_json('/device_images/%s' % device_image.uuid,
expect_errors=True)
self.assertEqual(response.status_int, 404)
self.assertEqual(response.content_type, 'application/json')
self.assertTrue(response.json['error_message'])
def test_delete_not_exist(self):
# Test deleting a device image
# Delete the device image
uuid = uuidutils.generate_uuid()
response = self.delete('/device_images/%s' % uuid,
headers={'User-Agent': 'sysinv-test'},
expect_errors=True)
self.assertEqual(response.status_int, 404)
self.assertEqual(response.content_type, 'application/json')
self.assertTrue(response.json['error_message'])
self.assertIn("Device image %s could not be found" % uuid,
response.json['error_message'])

View File

@ -0,0 +1,143 @@
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import json
from six.moves import http_client
from six.moves.urllib.parse import urlencode
from sysinv.db import api as dbapi
from sysinv.tests.api import base
from sysinv.tests.db import base as dbbase
from sysinv.tests.db import utils as dbutils
class DeviceLabelTestCase(base.FunctionalTest, dbbase.ControllerHostTestCase):
def setUp(self):
super(DeviceLabelTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
# Create a pci_device and fpga_device object
self.pci_device = dbutils.create_test_pci_devices(
host_id=self.host.id,
pclass='Processing accelerators',
pclass_id='120000',)
self.fpga_device = dbutils.create_test_fpga_device(
host_id=self.host.id,
pci_id=self.pci_device.id)
self.generic_labels = {
'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1',
'key2': 'value2'
}
def _get_path(self, params=None):
path = '/device_labels'
if params:
path += '?' + urlencode(params)
return path
def validate_labels(self, input_data, response_data):
for t in response_data:
for k, v in t.items():
if k in input_data.keys():
self.assertEqual(v, input_data[k])
def assign_labels(self, input_data, parameters=None):
response = self.post_json('%s' % self._get_path(parameters), input_data)
self.assertEqual(http_client.OK, response.status_int)
return response
def assign_labels_failure(self, input_data, parameters=None):
response = self.post_json('%s' % self._get_path(parameters), input_data,
expect_errors=True)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
self.assertTrue(response.json['error_message'])
def get_device_labels(self):
response = self.get_json("/device_labels")
return response['device_labels']
class DeviceLabelAssignTestCase(DeviceLabelTestCase):
def setUp(self):
super(DeviceLabelAssignTestCase, self).setUp()
def test_create_device_labels(self):
self.assign_labels(self.generic_labels)
response_data = self.get_device_labels()
self.validate_labels(self.generic_labels, response_data)
def test_overwrite_device_labels_success(self):
self.assign_labels(self.generic_labels)
new_input_values = {
'pcidevice_uuid': self.pci_device.uuid,
'key1': 'string1',
'key2': 'string2'
}
self.assign_labels(new_input_values, parameters={'overwrite': True})
response_data = self.get_device_labels()
self.validate_labels(new_input_values, response_data)
def test_overwrite_device_labels_failure(self):
self.assign_labels(self.generic_labels)
new_input_values = {
'pcidevice_uuid': self.pci_device.uuid,
'key1': 'string1',
'key2': 'string2'
}
# Default value should be overwrite=False
self.assign_labels_failure(new_input_values)
# Test explicit overwrite=False
self.assign_labels_failure(new_input_values, parameters={'overwrite': False})
# Labels should be unchanged from initial values
response_data = self.get_device_labels()
self.validate_labels(self.generic_labels, response_data)
def test_create_validated_device_labels_success(self):
label1 = {
'pcidevice_uuid': self.pci_device.uuid,
'key1': 'value1',
}
self.assign_labels(label1)
label2 = {
'pcidevice_uuid': self.pci_device.uuid,
'key2': 'value2',
}
self.assign_labels(label2)
input_data = {}
for input_label in [label1, label2]:
input_data.update(input_label)
response_data = self.get_device_labels()
self.validate_labels(input_data, response_data)
class DeviceLabelRemoveTestCase(DeviceLabelTestCase):
def setUp(self):
super(DeviceLabelRemoveTestCase, self).setUp()
def test_remove_device_labels(self):
# Assign labels to a device
response = self.assign_labels(self.generic_labels)
resp = json.loads(response.body)
self.assertIn('device_labels', resp)
resp_dict = resp.get('device_labels')
uuid = resp_dict[0]['uuid']
# Remove a label from the device
self.delete('/device_labels/%s' % uuid,
headers={'User-Agent': 'sysinv-test'})
# Verify the device label no longer exists
response = self.get_json('/device_labels/%s' % uuid,
expect_errors=True)
self.assertEqual(response.status_int, http_client.BAD_REQUEST)
self.assertEqual(response.content_type, 'application/json')
self.assertTrue(response.json['error_message'])

View File

@ -1327,6 +1327,38 @@ def create_test_pci_devices(**kw):
return dbapi.pci_device_create(pci_devices['host_id'], pci_devices)
def get_test_fpga_device(**kw):
fpga_device = {
'id': kw.get('id', 2345),
'host_id': kw.get('host_id', 2),
'pci_id': kw.get('pci_id', 2),
'pciaddr': kw.get('pciaddr', '0000:00:02.0'),
'bmc_build_version': kw.get('bmc_build_version'),
'bmc_fw_version': kw.get('bmc_fw_version'),
'root_key': kw.get('root_key'),
'revoked_key_ids': kw.get('revoked_key_ids'),
'boot_page': kw.get('boot_page'),
'bitstream_id': kw.get('bitstream_id'),
'needs_firmware_update': kw.get('needs_firmware_update', False),
'status': kw.get('status'),
}
return fpga_device
def create_test_fpga_device(**kw):
"""Create test fpga devices entry in DB and return FPGADevice DB object.
Function to be used to create test fpga device objects in the database.
:param kw: kwargs with overriding values for fpga device attributes.
:returns: Test FPGADevice DB object.
"""
fpga_device = get_test_fpga_device(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in kw:
del fpga_device['id']
dbapi = db_api.get_instance()
return dbapi.fpga_device_create(fpga_device['host_id'], fpga_device)
def get_test_label(**kw):
label = {
'host_id': kw.get('host_id'),
@ -1393,3 +1425,44 @@ def create_test_certificate(**kw):
del certificate['id']
dbapi = db_api.get_instance()
return dbapi.certificate_create(certificate)
# Create test device image object
def get_test_device_image(**kw):
device_image = {
'id': kw.get('id'),
'uuid': kw.get('uuid'),
'bitstream_type': kw.get('bitstream_type'),
'pci_vendor': kw.get('pci_vendor'),
'pci_device': kw.get('pci_device'),
'bitstream_id': kw.get('bitstream_id'),
'key_signature': kw.get('key_signature'),
'revoke_key_id': kw.get('revoke_key_id'),
'name': kw.get('name'),
'description': kw.get('description'),
'version': kw.get('version'),
}
return device_image
def post_get_test_device_image(**kw):
device_image = get_test_device_image(**kw)
del device_image['id']
del device_image['uuid']
return device_image
def create_test_device_image(**kw):
"""Create test device image in DB and return device_image object.
Function to be used to create test device image objects in the database.
:param kw: kwargs with overriding values for device_image's attributes.
:returns: Test device_image DB object.
"""
device_image = get_test_device_image(**kw)
# Let DB generate ID if it isn't specified explicitly
if 'id' not in device_image:
del device_image['id']
if 'uuid' in device_image:
del device_image['uuid']
dbapi = db_api.get_instance()
return dbapi.deviceimage_create(device_image)