Add instance project_id for arq patch

In order to control the operation of arq with different roles.
Use microversion 2.1 for adding project_id for arq patching.
Add and modify unittest.

Change-Id: Ie331a3b34e7397531ec9737595b8996e38044801
This commit is contained in:
songwenping 2020-06-29 11:53:18 +00:00
parent 9179ed3053
commit de0823d198
10 changed files with 102 additions and 11 deletions

View File

@ -23,6 +23,8 @@ from oslo_log import log
from cyborg.api.controllers import base
from cyborg.api.controllers import link
from cyborg.api.controllers import types
from cyborg.api.controllers.v2 import utils
from cyborg.api.controllers.v2 import versions
from cyborg.api import expose
from cyborg.common import constants
from cyborg.common import exception
@ -54,6 +56,8 @@ class ARQ(base.APIBase):
instance_uuid = wtypes.text
"""The UUID of the instance associated with this ARQ, if any"""
project_id = wtypes.text
"""The UUID of the instance project_id associated with this ARQ, if any"""
attach_handle_type = wtypes.text
attach_handle_info = {wtypes.text: wtypes.text}
@ -257,6 +261,8 @@ class ARQsController(base.CyborgController):
valid_fields = {'hostname': None,
'device_rp_uuid': None,
'instance_uuid': None}
if utils.allow_project_id():
valid_fields['project_id'] = None
if ((not all(p['op'] == 'add' for p in patch)) and
(not all(p['op'] == 'remove' for p in patch))):
raise exception.PatchError(
@ -264,6 +270,12 @@ class ARQsController(base.CyborgController):
for p in patch:
path = p['path'].lstrip('/')
if path == 'project_id' and not utils.allow_project_id():
raise exception.NotAcceptable(_(
"Request not acceptable. The minimal required API "
"version should be %(base)s.%(opr)s") %
{'base': versions.BASE_VERSION,
'opr': versions.MINOR_1_PROJECT_ID})
if path not in valid_fields.keys():
reason = 'Invalid path in patch {}'.format(p['path'])
raise exception.PatchError(reason=reason)
@ -306,6 +318,7 @@ class ARQsController(base.CyborgController):
{"path": "/hostname", "op": ADD/RM, "value": "..."},
{"path": "/device_rp_uuid", "op": ADD/RM, "value": "..."},
{"path": "/instance_uuid", "op": ADD/RM, "value": "..."},
{"path": "/project_id", "op": ADD/RM, "value": "..."},
],
"$arq_uuid": [...]
}

View File

@ -0,0 +1,22 @@
# Copyright 2020 Inspur, Inc.
# 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 cyborg import api
from cyborg.api.controllers.v2 import versions
def allow_project_id():
# v2.1 added project_id for arq patch
return api.request.version.minor >= versions.MINOR_1_PROJECT_ID

View File

@ -23,7 +23,9 @@ BASE_VERSION = 2
# explanation of what each version contains.
#
# v2.0: Initial minor version.
# v2.1: Add project_id for arq patch
MINOR_0_INITIAL_VERSION = 0
MINOR_1_PROJECT_ID = 1
# When adding another version, update:
@ -32,7 +34,7 @@ MINOR_0_INITIAL_VERSION = 0
# explanation of what changed in the new version
MINOR_MAX_VERSION = MINOR_0_INITIAL_VERSION
MINOR_MAX_VERSION = MINOR_1_PROJECT_ID
# String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_0_INITIAL_VERSION)

View File

@ -12,6 +12,11 @@ user documentation.
This is the initial version of the v2 API which supports
microversions.
2.1
---
Add ``project_id`` for arq.
A user can specify a header in the API request::
OpenStack-API-Version: accelerator <microversion>

View File

@ -411,3 +411,8 @@ class InvalidType(Invalid):
class ResourceNotFound(NotFound):
_msg_fmt = _("%(resource)s not found %(msg)s")
class NotAcceptable(CyborgException):
_msg_fmt = _("Request not acceptable.")
code = http_client.NOT_ACCEPTABLE

View File

@ -44,6 +44,7 @@ class ARQ(base.CyborgObject, object_base.VersionedObjectDictCompat):
'hostname': object_fields.StringField(nullable=True),
'device_rp_uuid': object_fields.StringField(nullable=True),
'instance_uuid': object_fields.StringField(nullable=True),
'project_id': object_fields.StringField(nullable=True),
# Fields populated by Cyborg after binding
'attach_handle_type': object_fields.StringField(nullable=True),
@ -66,6 +67,6 @@ class ARQ(base.CyborgObject, object_base.VersionedObjectDictCompat):
db_extarq['attach_handle_info'] = {}
for field in arq.fields:
arq[field] = db_extarq[field]
arq[field] = db_extarq.get(field)
arq.obj_reset_changes()
return arq

View File

@ -278,7 +278,7 @@ class ExtARQ(base.CyborgObject, object_base.VersionedObjectDictCompat,
for field in extarq.fields:
if field != 'arq':
extarq[field] = db_extarq[field]
extarq[field] = db_extarq.get(field)
extarq.arq = objects.ARQ()
extarq.arq._from_db_object(extarq.arq, db_extarq)
extarq.obj_reset_changes()

View File

@ -65,12 +65,17 @@ class ExtARQJobMixin(object):
hostname = valid_fields[self.arq.uuid]['hostname']
devrp_uuid = valid_fields[self.arq.uuid]['device_rp_uuid']
instance_uuid = valid_fields[self.arq.uuid]['instance_uuid']
LOG.info('[arqs:objs] bind. hostname: %s, devrp_uuid: %s'
'instance: %s', hostname, devrp_uuid, instance_uuid)
project_id = valid_fields[self.arq.uuid].get('project_id')
LOG.info('[arqs:objs] bind. hostname: %(hostname)s,'
' devrp_uuid: %(devrp_uuid)s, instance: %(instance)s, '
'project_id: %(project_id)s',
{'hostname': hostname, 'devrp_uuid': devrp_uuid,
'instance_uuid': instance_uuid, 'project_id': project_id})
self.arq.hostname = hostname
self.arq.device_rp_uuid = devrp_uuid
self.arq.instance_uuid = instance_uuid
self.arq.project_id = project_id
# If prog fails, we'll change this ARQ state changes get committed here
self.update_check_state(context, constants.ARQ_BIND_STARTED)

View File

@ -19,6 +19,7 @@ from unittest import mock
from oslo_serialization import jsonutils
from cyborg.api.controllers import base
from cyborg.api.controllers.v2 import arqs
from cyborg.common import exception
from cyborg.tests.unit.api.controllers.v2 import base as v2_test
@ -255,13 +256,14 @@ class TestARQsController(v2_test.APITestV2):
@mock.patch('cyborg.objects.ExtARQ.apply_patch')
def test_apply_patch(self, mock_apply_patch, mock_check_if_bound):
"""Test the happy path."""
patch_list = fake_extarq.get_patch_list()
patch_list, device_rp_uuid = fake_extarq.get_patch_list()
arq_uuids = list(patch_list.keys())
obj_extarq = self.fake_extarqs[0]
valid_fields = {
arq_uuid: {
'hostname': 'myhost',
'device_rp_uuid': 'fb16c293-5739-4c84-8590-926f9ab16669',
'instance_uuid': '5922a70f-1e06-4cfd-88dd-a332120d7144'}
'hostname': obj_extarq.arq.hostname,
'device_rp_uuid': device_rp_uuid,
'instance_uuid': obj_extarq.arq.instance_uuid}
for arq_uuid in arq_uuids}
self.patch_json(self.ARQ_URL, params=patch_list,
@ -271,6 +273,41 @@ class TestARQsController(v2_test.APITestV2):
valid_fields)
mock_check_if_bound.assert_called_once_with(mock.ANY, valid_fields)
@mock.patch.object(arqs.ARQsController, '_check_if_already_bound')
@mock.patch('cyborg.objects.ExtARQ.apply_patch')
def test_apply_patch_allow_project_id(
self, mock_apply_patch, mock_check_if_bound):
patch_list, _ = fake_extarq.get_patch_list()
for arq_uuid, patch in patch_list.items():
patch.append({'path': '/project_id', 'op': 'add',
'value': 'b1c76756ac2e482789a8e1c5f4bf065e'})
arq_uuids = list(patch_list.keys())
valid_fields = {
arq_uuid: {
'hostname': 'myhost',
'device_rp_uuid': 'fb16c293-5739-4c84-8590-926f9ab16669',
'instance_uuid': '5922a70f-1e06-4cfd-88dd-a332120d7144',
'project_id': 'b1c76756ac2e482789a8e1c5f4bf065e'}
for arq_uuid in arq_uuids}
self.patch_json(self.ARQ_URL, params=patch_list,
headers={base.Version.current_api_version:
'accelerator 2.1'})
mock_apply_patch.assert_called_once_with(mock.ANY, patch_list,
valid_fields)
mock_check_if_bound.assert_called_once_with(mock.ANY, valid_fields)
def test_apply_patch_not_allow_project_id(self):
patch_list, _ = fake_extarq.get_patch_list()
for arq_uuid, patch in patch_list.items():
patch.append({'path': '/project_id', 'op': 'add',
'value': 'b1c76756ac2e482789a8e1c5f4bf065e'})
response = self.patch_json(self.ARQ_URL, params=patch_list,
headers=self.headers,
expect_errors=True)
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_code)
self.assertTrue(response.json['error_message'])
# TODO(all): Add exception test cases for apply_patch.
@mock.patch('cyborg.objects.ExtARQ.list')

View File

@ -288,7 +288,8 @@ def get_patch_list(same_device=True):
must be for the same device.
"""
arqs = _get_arqs_as_dict()
host_binding = {'path': '/hostname', 'op': 'add', 'value': 'myhost'}
host_binding = {'path': '/hostname', 'op': 'add',
'value': arqs[0]['hostname']}
inst_binding = {'path': '/instance_uuid', 'op': 'add',
'value': arqs[0]['instance_uuid']}
device_rp_uuid = 'fb16c293-5739-4c84-8590-926f9ab16669'
@ -298,4 +299,4 @@ def get_patch_list(same_device=True):
dev_binding = {'path': '/device_rp_uuid', 'op': 'add',
'value': dev_uuid}
patch_list[newarq['uuid']] = [host_binding, inst_binding, dev_binding]
return patch_list
return patch_list, device_rp_uuid