# Copyright 2019 Beijing Lenovo Software Ltd. # 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 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_attach_handle from cyborg.tests.unit import fake_deployable from cyborg.tests.unit import fake_device_profile from cyborg.tests.unit import fake_extarq class TestExtARQObject(base.DbTestCase): def setUp(self): super(TestExtARQObject, self).setUp() self.fake_db_extarqs = fake_extarq.get_fake_db_extarqs() self.fake_obj_extarqs = fake_extarq.get_fake_extarq_objs() self.fake_obj_fpga_extarqs = fake_extarq.get_fake_fpga_extarq_objs() self.fake_obj_ahs = fake_attach_handle.get_fake_attach_handle_objs() self.deployable_uuids = ['0acbf8d6-e02a-4394-aae3-57557d209498'] @mock.patch('cyborg.objects.ExtARQ._from_db_object') def test_get(self, mock_from_db_obj): db_extarq = self.fake_db_extarqs[0] uuid = db_extarq['uuid'] mock_from_db_obj.return_value = self.fake_obj_extarqs[0] with mock.patch.object(self.dbapi, 'extarq_get', autospec=True) as mock_extarq_get: mock_extarq_get.return_value = db_extarq obj_extarq = objects.ExtARQ.get(self.context, uuid) mock_extarq_get.assert_called_once_with(self.context, uuid) self.assertEqual(obj_extarq.arq.uuid, uuid) @mock.patch('cyborg.objects.ExtARQ._from_db_object') def test_list(self, mock_from_db_obj): db_extarq = self.fake_db_extarqs[0] mock_from_db_obj.return_value = self.fake_obj_extarqs[0] with mock.patch.object(self.dbapi, 'extarq_list', autospec=True) as mock_get_list: mock_get_list.return_value = [db_extarq] obj_extarqs = objects.ExtARQ.list(self.context) self.assertEqual(1, mock_get_list.call_count) self.assertThat(obj_extarqs, HasLength(1)) self.assertIsInstance(obj_extarqs[0], objects.ExtARQ) for obj_extarq in obj_extarqs: self.assertEqual(obj_extarqs[0].arq.uuid, db_extarq['uuid']) @mock.patch('cyborg.objects.ExtARQ._from_db_object') def test_create(self, mock_from_db_obj): db_extarq = self.fake_db_extarqs[0] mock_from_db_obj.return_value = self.fake_obj_extarqs[0] with mock.patch.object(self.dbapi, 'extarq_create', autospec=True) as mock_extarq_create: mock_extarq_create.return_value = db_extarq extarq = objects.ExtARQ(self.context, **db_extarq) extarq.arq = objects.ARQ(self.context, **db_extarq) extarq.create(self.context) mock_extarq_create.assert_called_once() @mock.patch('openstack.connection.Connection') @mock.patch('cyborg.common.nova_client.NovaAPI.notify_binding') @mock.patch('cyborg.objects.ExtARQ.bind') @mock.patch('cyborg.objects.ExtARQ.get') def test_apply_patch_to_bad_arq_state( self, mock_get, mock_bind, mock_notify_bind, mock_conn): good_states = constants.ARQ_STATES_TRANSFORM_MATRIX[ constants.ARQ_BIND_STARTED] mock_get.return_value = obj_extarq = self.fake_obj_extarqs[0] uuid = obj_extarq.arq.uuid instance_uuid = obj_extarq.arq.instance_uuid valid_fields = { uuid: {'hostname': obj_extarq.arq.hostname, 'device_rp_uuid': obj_extarq.arq.device_rp_uuid, 'instance_uuid': instance_uuid} } patch_list = { str(uuid): [ {"path": "/hostname", "op": "add", "value": obj_extarq.arq.hostname}, {"path": "/device_rp_uuid", "op": "add", "value": obj_extarq.arq.device_rp_uuid}, {"path": "/instance_uuid", "op": "add", "value": instance_uuid} ] } for state in set(constants.ARQ_STATES) - set(good_states): obj_extarq.arq.state = state mock_get.return_value = obj_extarq self.assertRaises( exception.ARQBadState, objects.ExtARQ.apply_patch, self.context, patch_list, valid_fields) mock_notify_bind.assert_not_called() @mock.patch('cyborg.objects.ExtARQ.save') @mock.patch('cyborg.objects.extarq.ext_arq_job.ExtARQJobMixin.' 'get_arq_bind_statuses') @mock.patch('openstack.connection.Connection') @mock.patch('cyborg.common.nova_client.NovaAPI.notify_binding') @mock.patch('cyborg.objects.ExtARQ._allocate_attach_handle') @mock.patch('cyborg.objects.ExtARQ.get') @mock.patch('cyborg.objects.ExtARQ.list') @mock.patch('cyborg.objects.ExtARQ.update_check_state') @mock.patch('cyborg.objects.deployable.Deployable.get_by_device_rp_uuid') def test_apply_patch_for_common_extarq( self, mock_get_dep, mock_check_state, mock_list, mock_get, mock_attach_handle, mock_notify_bind, mock_conn, mock_get_bind_st, mock_save): good_states = constants.ARQ_STATES_TRANSFORM_MATRIX[ constants.ARQ_BIND_STARTED] obj_extarq = self.fake_obj_extarqs[0] obj_extarq.arq.state = good_states[0] # NOTE(Sundar): Since update_check_state is mocked, ARQ state # remains as 'Initial'. So, we mock get_arq_bind_statuses to # prevent that from raising exception. mock_get_bind_st.return_value = [ (obj_extarq.arq.uuid, constants.ARQ_BIND_STATUS_FINISH)] # TODO(Shaohe) we should control the state of arq to make # better testcase. # bound_extarq = copy.deepcopy(obj_extarq) # bound_extarq.arq.state = constants.ARQ_BOUND # mock_get.side_effect = [obj_extarq, bound_extarq] mock_get.side_effect = [obj_extarq] * 2 mock_list.return_value = [obj_extarq] uuid = obj_extarq.arq.uuid instance_uuid = obj_extarq.arq.instance_uuid dep_uuid = self.deployable_uuids[0] fake_dep = fake_deployable.fake_deployable_obj(self.context, uuid=dep_uuid) mock_get_dep.return_value = fake_dep valid_fields = { uuid: {'hostname': obj_extarq.arq.hostname, 'device_rp_uuid': obj_extarq.arq.device_rp_uuid, 'instance_uuid': instance_uuid} } patch_list = { str(uuid): [ {"path": "/hostname", "op": "add", "value": obj_extarq.arq.hostname}, {"path": "/device_rp_uuid", "op": "add", "value": obj_extarq.arq.device_rp_uuid}, {"path": "/instance_uuid", "op": "add", "value": instance_uuid} ] } objects.ExtARQ.apply_patch(self.context, patch_list, valid_fields) # NOTE(Shaohe) we set the fake_obj_extarqs state is ARQ_INITIAL # TODO(Shaohe) we should control the state of arq to make # complete status testcase. self.assertEqual(obj_extarq.arq.state, 'Initial') mock_notify_bind.assert_called_once_with( instance_uuid, [(obj_extarq.arq.uuid, constants.ARQ_BIND_STATUS_FINISH)]) self.assertEqual(obj_extarq.deployable_id, fake_dep.id) mock_save.assert_called_once() @mock.patch('cyborg.objects.extarq.ext_arq_job.ExtARQJobMixin.' 'get_arq_bind_statuses') @mock.patch('openstack.connection.Connection') @mock.patch('cyborg.common.nova_client.NovaAPI.notify_binding') @mock.patch('cyborg.objects.ExtARQ._allocate_attach_handle') @mock.patch('cyborg.objects.ExtARQ.get') @mock.patch('cyborg.objects.ExtARQ.list') @mock.patch('cyborg.objects.ExtARQ.update_check_state') @mock.patch('cyborg.objects.deployable.Deployable.get_by_device_rp_uuid') @mock.patch('cyborg.common.utils.ThreadWorks.spawn') def test_apply_patch_start_fpga_arq_job( self, mock_spawn, mock_get_dep, mock_check_state, mock_list, mock_get, mock_attach_handle, mock_notify_bind, mock_conn, mock_get_bind_st): good_states = constants.ARQ_STATES_TRANSFORM_MATRIX[ constants.ARQ_BIND_STARTED] obj_extarq = self.fake_obj_extarqs[2] obj_fpga_extarq = self.fake_obj_fpga_extarqs[1] obj_fpga_extarq.state = self.fake_obj_fpga_extarqs[1] obj_extarq.arq.state = good_states[0] obj_fpga_extarq.arq.state = good_states[0] # TODO(Shaohe) we should control the state of arq to make # better testcase. # bound_extarq = copy.deepcopy(obj_extarq) # bound_extarq.arq.state = constants.ARQ_BOUND # mock_get.side_effect = [obj_extarq, bound_extarq] mock_get.side_effect = [obj_extarq, obj_fpga_extarq] mock_list.return_value = [obj_extarq] uuid = obj_extarq.arq.uuid instance_uuid = obj_extarq.arq.instance_uuid # mock_job_get_ext_arq.side_effect = obj_extarq dep_uuid = self.deployable_uuids[0] fake_dep = fake_deployable.fake_deployable_obj(self.context, uuid=dep_uuid) mock_get_bind_st.return_value = [ (obj_extarq.arq.uuid, constants.ARQ_BIND_STATUS_FINISH)] mock_get_dep.return_value = fake_dep mock_spawn.return_value = None valid_fields = { uuid: {'hostname': obj_extarq.arq.hostname, 'device_rp_uuid': obj_extarq.arq.device_rp_uuid, 'instance_uuid': instance_uuid} } patch_list = { str(uuid): [ {"path": "/hostname", "op": "add", "value": obj_extarq.arq.hostname}, {"path": "/device_rp_uuid", "op": "add", "value": obj_extarq.arq.device_rp_uuid}, {"path": "/instance_uuid", "op": "add", "value": instance_uuid} ] } objects.ExtARQ.apply_patch(self.context, patch_list, valid_fields) # NOTE(Shaohe) we set the fake_obj_extarqs state is ARQ_INITIAL # TODO(Shaohe) we should control the state of arq to make # better testcase. self.assertEqual(obj_extarq.arq.state, 'Initial') mock_notify_bind.assert_called_once_with( instance_uuid, [(obj_extarq.arq.uuid, constants.ARQ_BIND_STATUS_FINISH)]) # NOTE(Shaohe) check it spawn to start a job. mock_spawn.assert_called_once_with( obj_fpga_extarq.bind, self.context, fake_dep) @mock.patch('openstack.connection.Connection') @mock.patch('cyborg.common.nova_client.NovaAPI.notify_binding') @mock.patch('cyborg.objects.ExtARQ._allocate_attach_handle') @mock.patch('cyborg.objects.ExtARQ.get') @mock.patch('cyborg.objects.ExtARQ.list') @mock.patch('cyborg.objects.ExtARQ.update_check_state') @mock.patch('cyborg.objects.deployable.Deployable.get_by_device_rp_uuid') @mock.patch('cyborg.common.utils.ThreadWorks.spawn_master') def test_apply_patch_fpga_arq_monitor_job( self, mock_master, mock_get_dep, mock_check_state, mock_list, mock_get, mock_attach_handle, mock_notify_bind, mock_conn): good_states = constants.ARQ_STATES_TRANSFORM_MATRIX[ constants.ARQ_BIND_STARTED] obj_extarq = self.fake_obj_extarqs[2] obj_fpga_extarq = self.fake_obj_fpga_extarqs[1] obj_fpga_extarq.state = self.fake_obj_fpga_extarqs[1] obj_extarq.arq.state = good_states[0] obj_fpga_extarq.arq.state = good_states[0] # TODO(Shaohe) we should control the state of arq to make # better testcase. # bound_extarq = copy.deepcopy(obj_extarq) # bound_extarq.arq.state = constants.ARQ_BOUND # mock_get.side_effect = [obj_extarq, bound_extarq] mock_get.side_effect = [obj_extarq, obj_fpga_extarq] mock_list.return_value = [obj_extarq] uuid = obj_extarq.arq.uuid instance_uuid = obj_extarq.arq.instance_uuid dep_uuid = self.deployable_uuids[0] fake_dep = fake_deployable.fake_deployable_obj(self.context, uuid=dep_uuid) mock_get_dep.return_value = fake_dep valid_fields = { uuid: {'hostname': obj_extarq.arq.hostname, 'device_rp_uuid': obj_extarq.arq.device_rp_uuid, 'instance_uuid': instance_uuid} } patch_list = { str(uuid): [ {"path": "/hostname", "op": "add", "value": obj_extarq.arq.hostname}, {"path": "/device_rp_uuid", "op": "add", "value": obj_extarq.arq.device_rp_uuid}, {"path": "/instance_uuid", "op": "add", "value": instance_uuid} ] } objects.ExtARQ.apply_patch(self.context, patch_list, valid_fields) mock_master.assert_called_once() @mock.patch('cyborg.objects.ExtARQ.get') @mock.patch('cyborg.objects.ExtARQ._from_db_object') def test_destroy(self, mock_from_db_obj, mock_obj_extarq): db_extarq = self.fake_db_extarqs[0] uuid = db_extarq['uuid'] mock_from_db_obj.return_value = db_extarq mock_obj_extarq.return_value = self.fake_obj_extarqs[0] with mock.patch.object(self.dbapi, 'extarq_get', autospec=True) as mock_extarq_get: mock_extarq_get.return_value = db_extarq with mock.patch.object(self.dbapi, 'extarq_delete', autospec=True) as mock_extarq_delete: extarq = objects.ExtARQ.get(self.context, uuid) extarq.destroy(self.context) mock_extarq_delete.assert_called_once_with(self.context, uuid) @mock.patch('cyborg.objects.ExtARQ.update_check_state') def test_allocate_attach_handle(self, mock_check_state): obj_extarq = self.fake_obj_extarqs[0] dep_uuid = self.deployable_uuids[0] fake_dep = fake_deployable.fake_deployable_obj(self.context, uuid=dep_uuid) self.assertRaises( exception.ResourceNotFound, obj_extarq._allocate_attach_handle, self.context, fake_dep) mock_check_state.assert_called_once_with( self.context, constants.ARQ_BIND_FAILED) @mock.patch('logging.LoggerAdapter.error') @mock.patch('cyborg.objects.attach_handle.AttachHandle.allocate') @mock.patch('cyborg.objects.ExtARQ.update_check_state') def test_allocate_attach_handle_with_error_log( self, mock_check_state, mock_allocate, mock_log): obj_extarq = self.fake_obj_extarqs[0] dep_uuid = self.deployable_uuids[0] e = exception.ResourceNotFound( resource='AttachHandle', msg="Just for Test") msg = ("Failed to allocate attach handle for ARQ %s" "from deployable %s. Reason: %s") mock_allocate.side_effect = e fake_dep = fake_deployable.fake_deployable_obj(self.context, uuid=dep_uuid) self.assertRaises( exception.ResourceNotFound, obj_extarq._allocate_attach_handle, self.context, fake_dep) mock_log.assert_called_once_with( msg, obj_extarq.arq.uuid, fake_dep.uuid, str(e)) mock_check_state.assert_called_once_with( self.context, constants.ARQ_BIND_FAILED) @mock.patch('cyborg.objects.ExtARQ.update_check_state') @mock.patch('cyborg.objects.attach_handle.AttachHandle.get_by_id') @mock.patch('cyborg.objects.attach_handle.AttachHandle.deallocate') def test_deallocate_attach_handle( self, mock_deallocate, mock_ah, mock_check_state): obj_extarq = self.fake_obj_extarqs[0] mock_ah.return_value = self.fake_obj_ahs[0] obj_extarq._deallocate_attach_handle(self.context, mock_ah.id) mock_check_state.assert_not_called() @mock.patch('logging.LoggerAdapter.error') @mock.patch('cyborg.objects.ExtARQ.update_check_state') @mock.patch('cyborg.objects.attach_handle.AttachHandle.get_by_id') @mock.patch('cyborg.objects.attach_handle.AttachHandle.deallocate') def test_deallocate_attach_handle_with_error_log( self, mock_ah, mock_deallocate, mock_check_state, mock_log): obj_extarq = self.fake_obj_extarqs[0] mock_ah.return_value = self.fake_obj_ahs[0] e = exception.ResourceNotFound( resource='AttachHandle', msg="Just for Test") msg = ("Failed to deallocate attach handle %s for ARQ %s." "Reason: %s") mock_deallocate.side_effect = e self.assertRaises( exception.ResourceNotFound, obj_extarq._deallocate_attach_handle, self.context, mock_ah.id) mock_log.assert_called_once_with( msg, mock_ah.id, obj_extarq.arq.uuid, str(e)) mock_check_state.assert_called_once_with( self.context, constants.ARQ_UNBIND_FAILED) @mock.patch('cyborg.objects.ExtARQ.get') @mock.patch('cyborg.objects.ExtARQ._from_db_object') def test_save(self, mock_from_db_obj, mock_obj_extarq): db_extarq = self.fake_db_extarqs[0] uuid = db_extarq['uuid'] mock_from_db_obj.return_value = db_extarq mock_obj_extarq.return_value = self.fake_obj_extarqs[0] with mock.patch.object(self.dbapi, 'extarq_update', autospec=True) as mock_extarq_update: obj_extarq = objects.ExtARQ.get(self.context, uuid) obj_extarq.arq.hostname = 'newtestnode1' fake_arq_updated = db_extarq fake_arq_updated['hostname'] = obj_extarq.arq.hostname mock_extarq_update.return_value = fake_arq_updated obj_extarq.save(self.context) mock_extarq_update.assert_called_once() def test_get_arq_bind_statuses(self): # ARQ state is 'Bound' by default in the fake extarqs arq_list = [extarq.arq for extarq in self.fake_obj_extarqs] bind_status = constants.ARQ_BIND_STATES_STATUS_MAP[ constants.ARQ_BOUND] expected = [(arq.uuid, bind_status) for arq in arq_list] result = objects.ExtARQ.get_arq_bind_statuses(arq_list) self.assertEqual(expected, result) def _test_get_arq_bind_statuses_exception(self): extarqs = fake_extarq.get_fake_extarq_objs() arq_list = [extarq.arq for extarq in extarqs] for arq in arq_list: arq['state'] = constants.ARQ_INITIAL self.assertRaises(exception.ARQBadState, objects.ExtARQ.get_arq_bind_statuses, arq_list) @mock.patch('cyborg.objects.device_profile.DeviceProfile.get_by_name') @mock.patch('cyborg.objects.deployable.Deployable.get_by_id') @mock.patch('cyborg.db.sqlalchemy.api.Connection.' 'attach_handle_get_by_id') @mock.patch('cyborg.db.sqlalchemy.api.Connection.' 'device_profile_get_by_id') def test_fill_obj_extarq_fields(self, mock_get_devprof, mock_get_ah, mock_get_obj_dep, mock_obj_devprof): in_db_extarq = self.fake_db_extarqs[0] # Since state is not 'Bound', attach_handle_get_by_id is not called. in_db_extarq['state'] = 'Initial' in_db_extarq['deployable_id'] = None db_devprof = fake_device_profile.get_db_devprofs()[0] obj_devprof = fake_device_profile.get_obj_devprofs()[0] mock_get_devprof.return_value = db_devprof mock_obj_devprof.return_value = obj_devprof out_db_extarq = objects.ExtARQ._fill_obj_extarq_fields( self.context, in_db_extarq) self.assertEqual(out_db_extarq['device_profile_name'], db_devprof['name']) self.assertEqual(out_db_extarq['attach_handle_type'], '') self.assertEqual(out_db_extarq['attach_handle_info'], '') self.assertEqual( out_db_extarq['deployable_uuid'], '00000000-0000-0000-0000-000000000000') devprof_group_id = out_db_extarq['device_profile_group_id'] self.assertEqual(out_db_extarq['device_profile_group'], obj_devprof['groups'][devprof_group_id]) @mock.patch('cyborg.objects.device_profile.DeviceProfile.get_by_name') @mock.patch('cyborg.objects.deployable.Deployable.get_by_id') @mock.patch('cyborg.db.sqlalchemy.api.Connection.' 'attach_handle_get_by_id') @mock.patch('cyborg.db.sqlalchemy.api.Connection.' 'device_profile_get_by_id') def test_fill_obj_extarq_fields_with_dep_id( self, mock_get_devprof, mock_get_ah, mock_get_obj_dep, mock_obj_devprof): in_db_extarq = self.fake_db_extarqs[0] # Since state is not 'Bound', attach_handle_get_by_id is not called. in_db_extarq['state'] = 'Initial' in_db_extarq['deployable_id'] = '1' db_devprof = fake_device_profile.get_db_devprofs()[0] obj_devprof = fake_device_profile.get_obj_devprofs()[0] mock_get_devprof.return_value = db_devprof mock_obj_devprof.return_value = obj_devprof mock_get_obj_dep.return_value = \ fake_deployable.fake_deployable_obj(self.context, uuid=self.deployable_uuids[0]) out_db_extarq = objects.ExtARQ._fill_obj_extarq_fields( self.context, in_db_extarq) self.assertEqual(out_db_extarq['device_profile_name'], db_devprof['name']) self.assertEqual(out_db_extarq['attach_handle_type'], '') self.assertEqual(out_db_extarq['attach_handle_info'], '') devprof_group_id = out_db_extarq['device_profile_group_id'] self.assertEqual(out_db_extarq['device_profile_group'], obj_devprof['groups'][devprof_group_id]) def test_obj_make_compatible(self): arq_obj = objects.ExtARQ(deployable_id=1) primitive = arq_obj.obj_to_primitive() arq_obj.obj_make_compatible(primitive['cyborg_object.data'], '1.1') self.assertNotIn('deployable_id', primitive['cyborg_object.data']) primitive = arq_obj.obj_to_primitive() arq_obj.obj_make_compatible(primitive['cyborg_object.data'], '1.2') self.assertIn('deployable_id', primitive['cyborg_object.data'])