Notify Nova when all ARQs are resolved for an instance.

Once all the ARQs for an instance have either bound or failed to bind,
Cyborg sends a notification event to Nova indicating success
or failure.

Change-Id: I05cce190d328ae16738959ba806d13a5ae99053c
This commit is contained in:
Xinran Wang 2019-08-19 07:47:32 +00:00 committed by Sundar Nadathur
parent bc2483960c
commit fdeceed353
6 changed files with 220 additions and 90 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ doc/source/_static/cyborg.policy.yaml.sample
etc/cyborg/policy.json.sample
etc/cyborg/cyborg.conf.sample
.idea/*
.vscode/*
# Editors
*.sublime-workspace

View File

@ -0,0 +1,59 @@
# Copyright 2019 Intel, Inc.
#
# 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 cyborg.conf import CONF
from openstack import connection
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class NovaAPI(object):
def __init__(self):
default_user = "devstack-admin"
try:
auth_user = CONF.compute.username
except Exception:
auth_user = default_user
self.conn = connection.Connection(cloud=auth_user)
self.nova_client = self.conn.compute
def _get_acc_changed_event(self, instance_uuid, dev_profile_name, status):
return [{'name': 'accelerator-requests-bound',
'server_uuid': instance_uuid,
'tag': dev_profile_name,
'status': status}
]
def _send_events(self, events):
url = "/os-server-external-events"
body = {"events": events}
response = self.nova_client.post(url, json=body)
if response.ok:
LOG.info("Sucessfully send events to Nova, events: %(events)s",
{"events": events})
return True
else:
raise Exception(
"Failed to send events %s: HTTP %d: %s" %
(events, response.status_code, response.text))
return False
def notify_binding(self, instance_uuid, dev_profile_name, status):
events = self._get_acc_changed_event(instance_uuid, dev_profile_name,
status)
result = self._send_events(events)
if not result:
LOG.error("Failed to notify Nova service.")
return result

View File

@ -22,6 +22,7 @@ from cyborg.agent.rpcapi import AgentAPI
from cyborg.db import api as dbapi
from cyborg.common import constants
from cyborg.common import exception
from cyborg.common import nova_client
from cyborg.common import placement_client
from cyborg import objects
from cyborg.objects import base
@ -306,15 +307,36 @@ class ExtARQ(base.CyborgObject, object_base.VersionedObjectDictCompat):
@classmethod
def apply_patch(cls, context, patch_list, valid_fields):
"""Apply JSON patch. See api/controllers/v1/arqs.py. """
device_profile_name = None
instance_uuid = None
bind_action = False
status = "completed"
for arq_uuid, patch in patch_list.items():
extarq = ExtARQ.get(context, arq_uuid)
if not device_profile_name:
device_profile_name = extarq.arq.device_profile_name
if not instance_uuid:
instance_uuid = valid_fields[arq_uuid]['instance_uuid']
if patch[0]['op'] == 'add': # All ops are 'add'
# True if do binding, False if do unbinding.
bind_action = True
extarq.bind(context,
valid_fields[arq_uuid]['hostname'],
valid_fields[arq_uuid]['device_rp_uuid'],
valid_fields[arq_uuid]['instance_uuid'])
if extarq.arq.state == constants.ARQ_BIND_FAILED:
status = "failed"
elif extarq.arq.state == constants.ARQ_BOUND:
continue
else:
raise exception.ARQInvalidState(state=extarq.arq.state)
else:
bind_action = False
extarq.unbind(context)
if bind_action:
nova_api = nova_client.NovaAPI()
nova_api.notify_binding(instance_uuid,
device_profile_name, status)
def unbind(self, context):
arq = self.arq

View File

@ -66,3 +66,11 @@ def get_fake_extarq_objs():
arq_list = _get_arqs_as_dict()
obj_extarqs = map(_convert_from_dict_to_obj, arq_list)
return obj_extarqs
def get_fake_db_extarqs():
db_extarqs = []
for db_extarq in _get_arqs_as_dict():
db_extarq.update({'device_profile_id': 0})
db_extarqs.append(db_extarq)
return db_extarqs

View File

@ -1,90 +0,0 @@
# 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.
import mock
from testtools.matchers import HasLength
from cyborg import objects
from cyborg.tests.unit.db import base
from cyborg.tests.unit.db import utils
class TestExtARQObject(base.DbTestCase):
def setUp(self):
super(TestExtARQObject, self).setUp()
self.fake_arq = utils.get_test_arq()
def test_get(self):
uuid = self.fake_arq['uuid']
with mock.patch.object(self.dbapi, 'extarq_get',
autospec=True) as mock_extarq_get:
mock_extarq_get.return_value = self.fake_arq
extarq = objects.ExtARQ.get(self.context, uuid)
mock_extarq_get.assert_called_once_with(self.context, uuid)
self.assertEqual(self.context, extarq._context)
def test_list(self):
with mock.patch.object(self.dbapi, 'extarq_list',
autospec=True) as mock_get_list:
mock_get_list.return_value = [self.fake_arq]
extarqs = objects.ExtARQ.list(self.context, 1, None, None, None)
self.assertEqual(1, mock_get_list.call_count)
self.assertThat(extarqs, HasLength(1))
self.assertIsInstance(extarqs[0], objects.ExtARQ)
self.assertEqual(self.context, extarqs[0]._context)
def test_create(self):
with mock.patch.object(self.dbapi, 'extarq_create',
autospec=True) as mock_extarq_create:
mock_extarq_create.return_value = self.fake_arq
extarq = objects.ExtARQ(self.context, **self.fake_arq)
extarq.arq = objects.ARQ(self.context, **self.fake_arq)
extarq.create(self.context)
mock_extarq_create.assert_called_once_with(self.context,
self.fake_arq)
self.assertEqual(self.context, extarq._context)
def test_destroy(self):
uuid = self.fake_arq['uuid']
with mock.patch.object(self.dbapi, 'extarq_get',
autospec=True) as mock_extarq_get:
mock_extarq_get.return_value = self.fake_arq
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)
self.assertEqual(self.context, extarq._context)
def test_save(self):
uuid = self.fake_arq['uuid']
with mock.patch.object(self.dbapi, 'extarq_get',
autospec=True) as mock_extarq_get:
mock_extarq_get.return_value = self.fake_arq
with mock.patch.object(self.dbapi, 'extarq_update',
autospec=True) as mock_extarq_update:
extarq = objects.ExtARQ.get(self.context, uuid)
arq = extarq.arq
arq.hostname = 'newtestnode1'
fake_arq_updated = self.fake_arq
fake_arq_updated['hostname'] = arq.hostname
mock_extarq_update.return_value = fake_arq_updated
extarq.save(self.context)
mock_extarq_get.assert_called_once_with(self.context, uuid)
mock_extarq_update.assert_called_once_with(
self.context, uuid,
{'hostname': 'newtestnode1'})
self.assertEqual(self.context, extarq._context)

View File

@ -0,0 +1,130 @@
# 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.
import mock
from testtools.matchers import HasLength
from cyborg import objects
from cyborg.tests.unit.db import base
from cyborg.tests.unit.db import utils
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()
@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(self, mock_get, mock_bind, mock_notify_bind,
mock_conn):
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}
]
}
objects.ExtARQ.apply_patch(self.context, patch_list, valid_fields)
status = 'completed'
mock_notify_bind.assert_called_once_with(
instance_uuid, obj_extarq.arq.device_profile_name, status)
@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.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()