389 lines
17 KiB
Python
389 lines
17 KiB
Python
# Copyright 2019 Intel.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from io import BytesIO
|
|
import json
|
|
import requests
|
|
from requests import structures
|
|
from requests import utils
|
|
from unittest import mock
|
|
|
|
from testtools.matchers import HasLength
|
|
|
|
from cyborg.common import constants
|
|
from cyborg.common import exception
|
|
from cyborg import objects
|
|
from cyborg.tests.unit.db import base
|
|
from cyborg.tests.unit import fake_deployable
|
|
from cyborg.tests.unit import fake_extarq
|
|
|
|
|
|
class TestFPGAExtARQObject(base.DbTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestFPGAExtARQObject, self).setUp()
|
|
self.fake_fpga_db_extarqs = fake_extarq.get_fake_fpga_db_extarqs()
|
|
self.fake_obj_fpga_extarqs = fake_extarq.get_fake_fpga_extarq_objs()
|
|
classes = ["no_program", "bitstream_program",
|
|
"function_program", "bad_program"]
|
|
self.class_fgpa_objects = dict(
|
|
zip(classes, self.fake_obj_fpga_extarqs))
|
|
self.class_fgpa_dbs = dict(
|
|
zip(classes, self.fake_fpga_db_extarqs))
|
|
self.bitstream_id = self.class_fgpa_objects["bitstream_program"][
|
|
"device_profile_group"][constants.ACCEL_BITSTREAM_ID]
|
|
self.function_id = self.class_fgpa_objects["function_program"][
|
|
"device_profile_group"][constants.ACCEL_FUNCTION_ID]
|
|
self.images_md = {
|
|
"/images":
|
|
[
|
|
{"id": self.bitstream_id,
|
|
"tags": ["trait:CUSTOM_FPGA_INTEL"],
|
|
constants.ACCEL_FUNCTION_ID: self.function_id},
|
|
]
|
|
}
|
|
self.deployable_uuids = ['0acbf8d6-e02a-4394-aae3-57557d209498']
|
|
self.bdf = {"domain": "0000", "bus": "00",
|
|
"device": "01", "function": "1"}
|
|
self.cpid = {
|
|
"id": 0,
|
|
"uuid": "e4a66b0d-b377-40d6-9cdc-6bf7e720e596",
|
|
"device_id": "1",
|
|
"cpid_type": "PCI",
|
|
"cpid_info": json.dumps(self.bdf).encode('utf-8')
|
|
}
|
|
|
|
def response(self, status_code=200, content='', headers=None,
|
|
reason=None, elapsed=0, request=None, stream=False):
|
|
res = requests.Response()
|
|
res.status_code = status_code
|
|
if isinstance(content, (dict, list)):
|
|
content = json.dumps(content).encode('utf-8')
|
|
if isinstance(content, str):
|
|
content = content.encode('utf-8')
|
|
res._content = content
|
|
res._content_consumed = content
|
|
res.headers = structures.CaseInsensitiveDict(headers or {})
|
|
res.encoding = utils.get_encoding_from_headers(res.headers)
|
|
res.reason = reason
|
|
res.request = request
|
|
if hasattr(request, 'url'):
|
|
res.url = request.url
|
|
if isinstance(request.url, bytes):
|
|
res.url = request.url.decode('utf-8')
|
|
|
|
if stream:
|
|
res.raw = BytesIO(content)
|
|
else:
|
|
res.raw = BytesIO(b'')
|
|
# normally this closes the underlying connection,
|
|
# but we have nothing to free.
|
|
res.close = lambda *args, **kwargs: None
|
|
return res
|
|
|
|
def images_get(self, url, *args, **kw):
|
|
images = {"images": []}
|
|
params = kw.get("params", {})
|
|
bit_id = None
|
|
res = None
|
|
if url != "/images":
|
|
bit_id = url.rsplit("/", 1)[-1]
|
|
params["id"] = bit_id
|
|
tags = kw.pop("tags", [])
|
|
for image in self.images_md["/images"]:
|
|
if tags <= image["tags"] and params.items() <= image.items():
|
|
images["images"].append(image)
|
|
res = self.response(content=image)
|
|
return res if bit_id else self.response(content=images)
|
|
|
|
@mock.patch('cyborg.objects.ExtARQ._from_db_object')
|
|
def test_get_bitstream_id_return_None(self, mock_from_db_obj):
|
|
db_extarq = self.fake_fpga_db_extarqs[0]
|
|
uuid = db_extarq['uuid']
|
|
mock_from_db_obj.return_value = self.fake_obj_fpga_extarqs[0]
|
|
with mock.patch.object(
|
|
self.dbapi, 'extarq_get') as mock_extarq_get:
|
|
mock_extarq_get.return_value = db_extarq
|
|
obj_extarq = objects.FPGAExtARQ.get(self.context, uuid)
|
|
mock_extarq_get.assert_called_once_with(self.context, uuid)
|
|
self.assertEqual(obj_extarq.arq.uuid, uuid)
|
|
self.assertIsNone(obj_extarq._get_bitstream_id())
|
|
|
|
@mock.patch('cyborg.objects.ExtARQ._from_db_object')
|
|
def test_get_bitstream_id_return_UUID(self, mock_from_db_obj):
|
|
db_extarq = self.fake_fpga_db_extarqs[1]
|
|
bit_id = db_extarq['device_profile_group'][
|
|
constants.ACCEL_BITSTREAM_ID]
|
|
uuid = db_extarq['uuid']
|
|
mock_from_db_obj.return_value = self.fake_obj_fpga_extarqs[1]
|
|
with mock.patch.object(
|
|
self.dbapi, 'extarq_get') as mock_extarq_get:
|
|
mock_extarq_get.return_value = db_extarq
|
|
obj_extarq = objects.FPGAExtARQ.get(self.context, uuid)
|
|
mock_extarq_get.assert_called_once_with(self.context, uuid)
|
|
self.assertEqual(obj_extarq.arq.uuid, uuid)
|
|
self.assertEqual(obj_extarq._get_bitstream_id(), bit_id)
|
|
|
|
@mock.patch('cyborg.objects.ExtARQ._from_db_object')
|
|
def test_get_function_id_return_UUID(self, mock_from_db_obj):
|
|
db_extarq = self.fake_fpga_db_extarqs[2]
|
|
fun_id = db_extarq['device_profile_group'][constants.ACCEL_FUNCTION_ID]
|
|
uuid = db_extarq['uuid']
|
|
mock_from_db_obj.return_value = self.fake_obj_fpga_extarqs[2]
|
|
with mock.patch.object(
|
|
self.dbapi, 'extarq_get') as mock_extarq_get:
|
|
mock_extarq_get.return_value = db_extarq
|
|
obj_extarq = objects.FPGAExtARQ.get(self.context, uuid)
|
|
mock_extarq_get.assert_called_once_with(self.context, uuid)
|
|
self.assertEqual(obj_extarq.arq.uuid, uuid)
|
|
self.assertEqual(obj_extarq._get_function_id(), fun_id)
|
|
|
|
@mock.patch('cyborg.objects.ExtARQ._from_db_object_list')
|
|
def test_list(self, mock_from_db_obj_list):
|
|
db_extarqs = self.fake_fpga_db_extarqs
|
|
mock_from_db_obj_list.return_value = self.fake_obj_fpga_extarqs
|
|
with mock.patch.object(self.dbapi, 'extarq_list',
|
|
autospec=True) as mock_get_list:
|
|
mock_get_list.return_value = db_extarqs
|
|
obj_extarqs = objects.FPGAExtARQ.list(self.context)
|
|
self.assertEqual(1, mock_get_list.call_count)
|
|
self.assertThat(obj_extarqs, HasLength(4))
|
|
self.assertIsInstance(obj_extarqs[0], objects.FPGAExtARQ)
|
|
for i, obj_extarq in enumerate(obj_extarqs):
|
|
self.assertEqual(obj_extarq.arq.uuid, db_extarqs[i]['uuid'])
|
|
|
|
@mock.patch('openstack.connection.Connection')
|
|
def test_get_bitstream_md_from_bitstream_id(self, mock_conn):
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
obj_extarq = self.class_fgpa_objects["bitstream_program"]
|
|
md = obj_extarq._get_bitstream_md_from_bitstream_id(self.bitstream_id)
|
|
self.assertDictEqual(self.images_md["/images"][0], md)
|
|
|
|
@mock.patch('openstack.connection.Connection')
|
|
def test_get_bitstream_md_from_function_id(self, mock_conn):
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
md = obj_extarq._get_bitstream_md_from_function_id(self.function_id)
|
|
self.assertDictEqual(self.images_md["/images"][0], md)
|
|
|
|
@mock.patch('openstack.connection.Connection')
|
|
@mock.patch('cyborg.objects.ExtARQ.update_check_state')
|
|
def test_needs_programming(self, mock_check_state, mock_conn):
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
need = obj_extarq._needs_programming(self.context, fake_dep)
|
|
self.assertTrue(need)
|
|
|
|
# bitstream_id is require
|
|
obj_extarq = self.class_fgpa_objects["bitstream_program"]
|
|
need = obj_extarq._needs_programming(self.context, fake_dep)
|
|
self.assertTrue(need)
|
|
|
|
# Both bitstream_id and function_id are require
|
|
obj_extarq = self.class_fgpa_objects["bad_program"]
|
|
self.assertRaises(
|
|
exception.InvalidParameterValue, obj_extarq._needs_programming,
|
|
self.context, fake_dep)
|
|
|
|
# None of bitstream_id or function_id is require
|
|
obj_extarq = self.class_fgpa_objects["no_program"]
|
|
need = obj_extarq._needs_programming(self.context, fake_dep)
|
|
self.assertFalse(need)
|
|
|
|
@mock.patch('openstack.connection.Connection')
|
|
@mock.patch('cyborg.objects.ExtARQ.update_check_state')
|
|
def test_get_bitstream_md(self, mock_check_state, mock_conn):
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
bs_id = obj_extarq._get_bitstream_id()
|
|
fun_id = obj_extarq._get_function_id()
|
|
md = obj_extarq.get_bitstream_md(
|
|
self.context, fake_dep, fun_id, bs_id)
|
|
self.assertDictEqual(self.images_md["/images"][0], md)
|
|
|
|
# bitstream_id is require
|
|
obj_extarq = self.class_fgpa_objects["bitstream_program"]
|
|
bs_id = obj_extarq._get_bitstream_id()
|
|
fun_id = obj_extarq._get_function_id()
|
|
md = obj_extarq.get_bitstream_md(
|
|
self.context, fake_dep, fun_id, bs_id)
|
|
self.assertDictEqual(self.images_md["/images"][0], md)
|
|
|
|
# Both bitstream_id and function_id are require
|
|
obj_extarq = self.class_fgpa_objects["bad_program"]
|
|
bs_id = obj_extarq._get_bitstream_id()
|
|
fun_id = obj_extarq._get_function_id()
|
|
self.assertRaises(
|
|
exception.InvalidParameterValue, obj_extarq.get_bitstream_md,
|
|
self.context, fake_dep, fun_id, bs_id)
|
|
|
|
# None of bitstream_id or function_id is require
|
|
obj_extarq = self.class_fgpa_objects["no_program"]
|
|
bs_id = obj_extarq._get_bitstream_id()
|
|
fun_id = obj_extarq._get_function_id()
|
|
md = obj_extarq.get_bitstream_md(
|
|
self.context, fake_dep, fun_id, bs_id)
|
|
self.assertIsNone(md)
|
|
|
|
@mock.patch('openstack.connection.Connection')
|
|
@mock.patch('cyborg.objects.ExtARQ.update_check_state')
|
|
def test_need_extra_bind_job(self, mock_check_state, mock_conn):
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
need = obj_extarq._need_extra_bind_job(self.context, fake_dep)
|
|
self.assertTrue(need)
|
|
|
|
# bitstream_id is require
|
|
obj_extarq = self.class_fgpa_objects["bitstream_program"]
|
|
need = obj_extarq._need_extra_bind_job(self.context, fake_dep)
|
|
self.assertTrue(need)
|
|
|
|
# Both bitstream_id and function_id are require
|
|
obj_extarq = self.class_fgpa_objects["bad_program"]
|
|
self.assertRaises(
|
|
exception.InvalidParameterValue, obj_extarq._need_extra_bind_job,
|
|
self.context, fake_dep)
|
|
|
|
# None of bitstream_id or function_id is require
|
|
obj_extarq = self.class_fgpa_objects["no_program"]
|
|
need = obj_extarq._need_extra_bind_job(self.context, fake_dep)
|
|
self.assertFalse(need)
|
|
|
|
@mock.patch('cyborg.agent.rpcapi.AgentAPI.fpga_program')
|
|
@mock.patch('openstack.connection.Connection')
|
|
@mock.patch('cyborg.objects.ExtARQ.update_check_state')
|
|
@mock.patch('cyborg.objects.Deployable.get_cpid_list')
|
|
def test_do_programming(self, mock_cpid_list, mock_check_state, mock_conn,
|
|
mock_program):
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
obj_extarq.arq.hostname = 'newtestnode1'
|
|
fake_dep.driver_name = "intel_fpga"
|
|
|
|
mock_cpid_list.return_value = [self.cpid]
|
|
bs_id = obj_extarq._get_bitstream_id()
|
|
obj_extarq._do_programming(self.context, fake_dep, bs_id)
|
|
mock_program.assert_called_once_with(
|
|
self.context, 'newtestnode1', self.cpid, bs_id, "intel_fpga")
|
|
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'__init__')
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'add_traits_to_rp')
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'delete_traits_with_prefixes')
|
|
def test_update_placement(
|
|
self, mock_delete_traits, mock_add_traits, mock_placement_init):
|
|
mock_placement_init.return_value = None
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
obj_extarq.arq.hostname = 'newtestnode1'
|
|
# fake_dep.driver_name = "intel_fpga"
|
|
fun_id = obj_extarq._get_function_id()
|
|
rp_uuid = obj_extarq.arq.device_rp_uuid
|
|
obj_extarq._update_placement(
|
|
self.context, fun_id, fake_dep.driver_name)
|
|
|
|
function_id = fun_id.upper().replace('-', '_-')
|
|
vendor = fake_dep.driver_name.upper()
|
|
trait_names = ["_".join((
|
|
constants.FPGA_FUNCTION_ID, vendor, function_id))]
|
|
mock_add_traits.assert_called_once_with(rp_uuid, trait_names)
|
|
mock_delete_traits.assert_called_once_with(
|
|
self.context, rp_uuid, [constants.FPGA_FUNCTION_ID])
|
|
|
|
@mock.patch('cyborg.agent.rpcapi.AgentAPI.fpga_program')
|
|
@mock.patch('cyborg.objects.Deployable.get_cpid_list')
|
|
@mock.patch('cyborg.objects.Deployable.update')
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'__init__')
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'add_traits_to_rp')
|
|
@mock.patch('cyborg.common.placement_client.PlacementClient.'
|
|
'delete_traits_with_prefixes')
|
|
@mock.patch('openstack.connection.Connection')
|
|
@mock.patch('cyborg.objects.ExtARQ.update_check_state')
|
|
@mock.patch('cyborg.objects.ExtARQ.bind')
|
|
def test_bind(
|
|
self, mock_bind, mock_check_state, mock_conn, mock_delete_traits,
|
|
mock_add_traits, mock_placement_init, mock_dp_update,
|
|
mock_cpid_list, mock_program):
|
|
|
|
mock_placement_init.return_value = None
|
|
mock_conn.return_value = type(
|
|
"Connection", (object,),
|
|
{"image": type("image", (object,), {"get": self.images_get})})
|
|
|
|
dep_uuid = self.deployable_uuids[0]
|
|
fake_dep = fake_deployable.fake_deployable_obj(
|
|
self.context, uuid=dep_uuid)
|
|
|
|
mock_cpid_list.return_value = [self.cpid]
|
|
|
|
# None of bitstream_id or function_id is require
|
|
obj_extarq = self.class_fgpa_objects["no_program"]
|
|
obj_extarq.bind(self.context, fake_dep)
|
|
self.assertFalse(mock_program.called)
|
|
self.assertFalse(mock_placement_init.called)
|
|
self.assertFalse(mock_dp_update.called)
|
|
mock_bind.assert_called_once_with(self.context, fake_dep)
|
|
|
|
self.cpid["cpid_info"] = json.dumps(self.bdf).encode('utf-8')
|
|
# function_id is require
|
|
obj_extarq = self.class_fgpa_objects["function_program"]
|
|
obj_extarq.bind(self.context, fake_dep)
|
|
mock_bind.assert_called_with(self.context, fake_dep)
|
|
self.assertEqual(mock_bind.call_count, 2)
|
|
|
|
self.cpid["cpid_info"] = json.dumps(self.bdf).encode('utf-8')
|
|
# bitstream_id is require
|
|
obj_extarq = self.class_fgpa_objects["bitstream_program"]
|
|
obj_extarq.bind(self.context, fake_dep)
|
|
mock_bind.assert_called_with(self.context, fake_dep)
|
|
self.assertEqual(mock_bind.call_count, 3)
|