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:
parent
bc9cde71a0
commit
d141e954fa
|
@ -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
|
||||
------------------
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="cgts-client"
|
||||
TIS_PATCH_VER=75
|
||||
TIS_PATCH_VER=76
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
#################
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -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)
|
|
@ -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))
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
SRC_DIR="sysinv"
|
||||
TIS_PATCH_VER=345
|
||||
TIS_PATCH_VER=346
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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})
|
|
@ -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)
|
|
@ -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
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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'
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.')
|
|
@ -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.')
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
123456789
|
|
@ -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'])
|
|
@ -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'])
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue