Imply new cyborg deployable api
1. Imply deployable api as our discussion on 19th, Jul. https://etherpad.openstack.org/p/cyborg-rocky-development 2. Add filter to deployable. 3. Improve test_accelerator. 4. Add command "rm" to tox whitelist_externals. Change-Id: I78154ef14a0ab3831a17ba93f44dfedef0d56df1
This commit is contained in:
parent
c8ba137be6
commit
458e0b1162
|
@ -22,7 +22,6 @@ from wsme import types as wtypes
|
|||
from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers.v1 import accelerators
|
||||
from cyborg.api.controllers.v1 import deployables
|
||||
from cyborg.api import expose
|
||||
|
||||
|
||||
|
@ -52,7 +51,6 @@ class Controller(rest.RestController):
|
|||
"""Version 1 API controller root"""
|
||||
|
||||
accelerators = accelerators.AcceleratorsController()
|
||||
deployables = deployables.DeployablesController()
|
||||
|
||||
@expose.expose(V1)
|
||||
def get(self):
|
||||
|
|
|
@ -14,16 +14,15 @@
|
|||
# under the License.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from six.moves import http_client
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers.v1 import deployables
|
||||
from cyborg.api.controllers.v1 import types
|
||||
from cyborg.api.controllers.v1 import utils as api_utils
|
||||
from cyborg.api.controllers.v1 import deployables
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import exception
|
||||
from cyborg.common import policy
|
||||
|
@ -77,6 +76,10 @@ class Accelerator(base.APIBase):
|
|||
def __init__(self, **kwargs):
|
||||
super(Accelerator, self).__init__(**kwargs)
|
||||
self.fields = []
|
||||
# NOTE(wangzhh): It not worked here. Because the response contain a
|
||||
# white_list named _wsme_attributes. See wsme.types.list_attributes.
|
||||
# Attribute which is not in the list will be ignored.
|
||||
# We have no disscussion about it, so just left it here now.
|
||||
for field in objects.Accelerator.fields:
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
|
|
@ -13,12 +13,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from six.moves import http_client
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import json
|
||||
|
||||
from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
|
@ -186,23 +185,48 @@ class DeployablesController(base.CyborgController):
|
|||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "get_all")
|
||||
@expose.expose(DeployableCollection, int, types.uuid, wtypes.text,
|
||||
wtypes.text, types.boolean)
|
||||
def get_all(self):
|
||||
wtypes.text, wtypes.ArrayType(types.FilterType))
|
||||
# TODO(wangzhh): Remove limit, marker, sort_key, sort_dir in next release.
|
||||
# They are used to compatible with R release client.
|
||||
def get_all(self, limit=None, marker=None, sort_key='id', sort_dir='asc',
|
||||
filters=None):
|
||||
"""Retrieve a list of deployables."""
|
||||
obj_deps = objects.Deployable.list(pecan.request.context)
|
||||
filters_dict = {}
|
||||
self._generate_filters(limit, sort_key, sort_dir, filters_dict)
|
||||
if filters:
|
||||
for filter in filters:
|
||||
filters_dict.update(filter.as_dict())
|
||||
context = pecan.request.context
|
||||
if marker:
|
||||
marker_obj = objects.Deployable.get(context, marker)
|
||||
filters_dict["marker_obj"] = marker_obj
|
||||
obj_deps = objects.Deployable.list(context, filters=filters_dict)
|
||||
return DeployableCollection.convert_with_links(obj_deps)
|
||||
|
||||
def _generate_filters(self, limit, sort_key, sort_dir, filters_dict):
|
||||
"""This method are used to compatible with R release client."""
|
||||
if limit:
|
||||
filters_dict["limit"] = limit
|
||||
if sort_key:
|
||||
filters_dict["sort_key"] = sort_key
|
||||
if sort_dir:
|
||||
filters_dict["sort_dir"] = sort_dir
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "update")
|
||||
@expose.expose(Deployable, types.uuid, body=[DeployablePatchType])
|
||||
def patch(self, uuid, patch):
|
||||
"""Update a deployable.
|
||||
|
||||
Usage: curl -X PATCH {ip}:{port}/v1/accelerators/deployables/
|
||||
{deployable_uuid} -d '[{"path":"/instance_uuid","value":
|
||||
{instance_uuid}, "op":"replace"}]' -H "Content-type:
|
||||
application/json"
|
||||
|
||||
:param uuid: UUID of a deployable.
|
||||
:param patch: a json PATCH document to apply to this deployable.
|
||||
"""
|
||||
context = pecan.request.context
|
||||
obj_dep = objects.Deployable.get(context, uuid)
|
||||
|
||||
try:
|
||||
api_dep = Deployable(
|
||||
**api_utils.apply_jsonpatch(obj_dep.as_dict(), patch))
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import inspect
|
||||
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
|
@ -25,6 +25,44 @@ from cyborg.common import exception
|
|||
from cyborg.common.i18n import _
|
||||
|
||||
|
||||
class FilterType(wtypes.UserType):
|
||||
"""Query filter."""
|
||||
name = 'filtertype'
|
||||
basetype = wtypes.text
|
||||
|
||||
_supported_fields = wtypes.Enum(wtypes.text, 'parent_uuid', 'root_uuid',
|
||||
'vender', 'host', 'board', 'availability',
|
||||
'assignable', 'interface_type',
|
||||
'instance_uuid', 'limit', 'marker',
|
||||
'sort_key', 'sort_dir')
|
||||
|
||||
field = wsme.wsattr(_supported_fields, mandatory=True)
|
||||
value = wsme.wsattr(wtypes.text, mandatory=True)
|
||||
|
||||
def __repr__(self):
|
||||
# for logging calls
|
||||
return '<Query %s %s>' % (self.field,
|
||||
self.value)
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
return cls(field='interface_type',
|
||||
value='pci')
|
||||
|
||||
def as_dict(self):
|
||||
d = dict()
|
||||
d[getattr(self, 'field')] = getattr(self, 'value')
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
def validate(filters):
|
||||
for filter in filters:
|
||||
if filter.field not in FilterType._supported_fields:
|
||||
msg = _("'%s' is an unsupported field for querying.")
|
||||
raise wsme.exc.ClientSideError(msg % filter.field)
|
||||
return filters
|
||||
|
||||
|
||||
class UUIDType(wtypes.UserType):
|
||||
"""A simple UUID type."""
|
||||
|
||||
|
|
|
@ -109,10 +109,12 @@ class Deployable(base.CyborgObject, object_base.VersionedObjectDictCompat):
|
|||
return obj_dpl_list
|
||||
|
||||
@classmethod
|
||||
def list(cls, context):
|
||||
def list(cls, context, filters={}):
|
||||
"""Return a list of Deployable objects."""
|
||||
db_deps = cls.dbapi.deployable_list(context)
|
||||
|
||||
if filters:
|
||||
db_deps = cls.dbapi.deployable_get_by_filters(context, filters)
|
||||
else:
|
||||
db_deps = cls.dbapi.deployable_list(context)
|
||||
obj_dpl_list = cls._from_db_object_list(db_deps, context)
|
||||
for obj_dpl in obj_dpl_list:
|
||||
query = {"deployable_id": obj_dpl.id}
|
||||
|
|
|
@ -18,6 +18,7 @@ import mock
|
|||
from oslo_utils import timeutils
|
||||
from six.moves import http_client
|
||||
|
||||
from cyborg.api.controllers.v1.accelerators import Accelerator
|
||||
from cyborg.conductor import rpcapi
|
||||
from cyborg.tests.unit.api.controllers.v1 import base as v1_test
|
||||
from cyborg.tests.unit.db import utils as db_utils
|
||||
|
@ -80,19 +81,8 @@ class TestList(v1_test.APITestV1):
|
|||
data = self.get_json('/accelerators/%s' % self.acc.uuid,
|
||||
headers=self.headers)
|
||||
self.assertEqual(self.acc.uuid, data['uuid'])
|
||||
self.assertIn('acc_capability', data)
|
||||
self.assertIn('acc_type', data)
|
||||
self.assertIn('created_at', data)
|
||||
self.assertIn('description', data)
|
||||
self.assertIn('device_type', data)
|
||||
self.assertIn('links', data)
|
||||
self.assertIn('name', data)
|
||||
self.assertIn('product_id', data)
|
||||
self.assertIn('project_id', data)
|
||||
self.assertIn('remotable', data)
|
||||
self.assertIn('updated_at', data)
|
||||
self.assertIn('user_id', data)
|
||||
self.assertIn('vendor_id', data)
|
||||
for attr in Accelerator._wsme_attributes:
|
||||
self.assertIn(attr.name, data)
|
||||
|
||||
def test_get_all(self):
|
||||
data = self.get_json('/accelerators', headers=self.headers)
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# Copyright 2018 Lenovo, 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.
|
||||
|
||||
import mock
|
||||
from six.moves import http_client
|
||||
|
||||
from cyborg.api.controllers.v1.deployables import Deployable
|
||||
from cyborg.tests.unit.api.controllers.v1 import base as v1_test
|
||||
from cyborg.tests.unit import fake_deployable
|
||||
|
||||
|
||||
class TestDeployableController(v1_test.APITestV1):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDeployableController, self).setUp()
|
||||
self.headers = self.gen_headers(self.context)
|
||||
self.deployable_uuids = ['10efe63d-dfea-4a37-ad94-4116fba50981']
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.get')
|
||||
def test_get_one(self, mock_get_dep):
|
||||
dep_uuid = self.deployable_uuids[0]
|
||||
mock_get_dep.return_value = fake_deployable.\
|
||||
fake_deployable_obj(self.context, uuid=dep_uuid)
|
||||
data = self.get_json('/accelerators/deployables/%s' % dep_uuid,
|
||||
headers=self.headers)
|
||||
self.assertEqual(dep_uuid, data['uuid'])
|
||||
for attr in Deployable._wsme_attributes:
|
||||
self.assertIn(attr.name, data)
|
||||
mock_get_dep.assert_called_once_with(mock.ANY, dep_uuid)
|
||||
|
||||
@mock.patch('cyborg.objects.Deployable.list')
|
||||
def test_get_all(self, mock_list_dep):
|
||||
fake_deps = []
|
||||
for uuid in self.deployable_uuids:
|
||||
fake_dep = fake_deployable.fake_deployable_obj(self.context,
|
||||
uuid=uuid)
|
||||
fake_deps.append(fake_dep)
|
||||
mock_list_dep.return_value = fake_deps
|
||||
data = self.get_json('/accelerators/deployables',
|
||||
headers=self.headers)
|
||||
self.assertEqual(len(self.deployable_uuids), len(data['deployables']))
|
||||
mock_list_dep.assert_called_once()
|
||||
|
||||
@mock.patch('cyborg.conductor.rpcapi.ConductorAPI.deployable_update')
|
||||
@mock.patch('cyborg.objects.Deployable.get')
|
||||
def test_patch(self, mock_get_dep, mock_deployable_update):
|
||||
self.headers['X-Roles'] = 'admin'
|
||||
self.headers['Content-Type'] = 'application/json'
|
||||
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
|
||||
instance_uuid = '10efe63d-dfea-4a37-ad94-4116fba50981'
|
||||
fake_dep.instance_uuid = instance_uuid
|
||||
mock_deployable_update.return_value = fake_dep
|
||||
response = self.patch_json('/accelerators/deployables/%s' % dep_uuid,
|
||||
[{'path': '/instance_uuid',
|
||||
'value': instance_uuid,
|
||||
'op': 'replace'}],
|
||||
headers=self.headers)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
data = response.json_body
|
||||
self.assertEqual(instance_uuid, data['instance_uuid'])
|
||||
mock_deployable_update.assert_called_once()
|
Loading…
Reference in New Issue