Merge "Add instance list and detail api"
This commit is contained in:
commit
9639edd5cd
@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
import jsonschema
|
||||
from oslo_log import log
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from six.moves import http_client
|
||||
@ -26,9 +27,17 @@ from nimble.api.controllers.v1 import types
|
||||
from nimble.api import expose
|
||||
from nimble.common import exception
|
||||
from nimble.common.i18n import _
|
||||
from nimble.common.i18n import _LW
|
||||
from nimble.engine.baremetal.ironic import get_node_by_instance
|
||||
from nimble.engine.baremetal.ironic import get_node_list
|
||||
from nimble.engine.baremetal import ironic_states as ir_states
|
||||
from nimble import objects
|
||||
|
||||
_DEFAULT_INSTANCE_RETURN_FIELDS = ('uuid', 'name', 'description',
|
||||
'status')
|
||||
_NODE_FIELDS = ['power_state', 'instance_uuid']
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
_CREATE_INSTANCE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/schema#",
|
||||
@ -162,9 +171,6 @@ class Instance(base.APIBase):
|
||||
power_state = wtypes.text
|
||||
"""The power state of the instance"""
|
||||
|
||||
task_state = wtypes.text
|
||||
"""The task state of the instance"""
|
||||
|
||||
availability_zone = wtypes.text
|
||||
"""The availability zone of the instance"""
|
||||
|
||||
@ -181,6 +187,7 @@ class Instance(base.APIBase):
|
||||
"""A list containing a self link"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Instance, self).__init__(**kwargs)
|
||||
self.fields = []
|
||||
for field in objects.Instance.fields:
|
||||
# Skip fields we do not expose.
|
||||
@ -190,18 +197,20 @@ class Instance(base.APIBase):
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_instance):
|
||||
instance = Instance(**rpc_instance.as_dict())
|
||||
def convert_with_links(cls, instance_data, fields=None):
|
||||
instance = Instance(**instance_data)
|
||||
instance_uuid = instance.uuid
|
||||
if fields is not None:
|
||||
instance.unset_fields_except(fields)
|
||||
url = pecan.request.public_url
|
||||
instance.links = [link.Link.make_link('self',
|
||||
url,
|
||||
'instances', instance.uuid),
|
||||
'instances', instance_uuid),
|
||||
link.Link.make_link('bookmark',
|
||||
url,
|
||||
'instances', instance.uuid,
|
||||
'instances', instance_uuid,
|
||||
bookmark=True)
|
||||
]
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
@ -212,10 +221,10 @@ class InstanceCollection(base.APIBase):
|
||||
"""A list containing instance objects"""
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(instances, url=None, **kwargs):
|
||||
def convert_with_links(instances_data, fields=None):
|
||||
collection = InstanceCollection()
|
||||
collection.instances = [Instance.convert_with_links(inst)
|
||||
for inst in instances]
|
||||
collection.instances = [Instance.convert_with_links(inst, fields)
|
||||
for inst in instances_data]
|
||||
return collection
|
||||
|
||||
|
||||
@ -224,22 +233,84 @@ class InstanceController(rest.RestController):
|
||||
|
||||
states = InstanceStatesController()
|
||||
|
||||
@expose.expose(InstanceCollection)
|
||||
def get_all(self):
|
||||
"""Retrieve a list of instance."""
|
||||
_custom_actions = {
|
||||
'detail': ['GET']
|
||||
}
|
||||
|
||||
def _get_instance_collection(self, fields=None):
|
||||
instances = objects.Instance.list(pecan.request.context)
|
||||
return InstanceCollection.convert_with_links(instances)
|
||||
instances_data = [instance.as_dict() for instance in instances]
|
||||
|
||||
@expose.expose(Instance, types.uuid)
|
||||
def get_one(self, instance_uuid):
|
||||
if fields is None or 'power_state' in fields:
|
||||
try:
|
||||
node_list = get_node_list(
|
||||
associated=True, limit=0,
|
||||
fields=_NODE_FIELDS)
|
||||
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
_LW("Failed to retrieve node list from"
|
||||
"ironic api: %(msg)s") % {"msg": e})
|
||||
node_list = []
|
||||
|
||||
if node_list:
|
||||
node_dict = {node.instance_uuid: node.to_dict()
|
||||
for node in node_list}
|
||||
# Merge nimble instance info with ironic node power state
|
||||
for instance_data in instances_data:
|
||||
uuid = instance_data['uuid']
|
||||
if uuid in node_dict:
|
||||
instance_data['power_state'] = \
|
||||
node_dict[uuid]['power_state']
|
||||
|
||||
return InstanceCollection.convert_with_links(instances_data,
|
||||
fields=fields)
|
||||
|
||||
@expose.expose(InstanceCollection, types.listtype)
|
||||
def get_all(self, fields=None):
|
||||
"""Retrieve a list of instance.
|
||||
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
of the resource to be returned.
|
||||
"""
|
||||
if fields is None:
|
||||
fields = _DEFAULT_INSTANCE_RETURN_FIELDS
|
||||
return self._get_instance_collection(fields=fields)
|
||||
|
||||
@expose.expose(Instance, types.uuid, types.listtype)
|
||||
def get_one(self, instance_uuid, fields=None):
|
||||
"""Retrieve information about the given instance.
|
||||
|
||||
:param instance_uuid: UUID of a instance.
|
||||
:param fields: Optional, a list with a specified set of fields
|
||||
of the resource to be returned.
|
||||
"""
|
||||
rpc_instance = objects.Instance.get(pecan.request.context,
|
||||
instance_uuid)
|
||||
return Instance.convert_with_links(rpc_instance)
|
||||
instance_data = rpc_instance.as_dict()
|
||||
if fields is None or 'power_state' in fields:
|
||||
# Only fetch node info if fields parameter is not specified
|
||||
# or node fields is not requested.
|
||||
try:
|
||||
node = get_node_by_instance(instance_uuid, _NODE_FIELDS)
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
_LW("Failed to retrieve node by instance_uuid"
|
||||
" %(instance_uuid)s from ironic api: %(msg)s") % {
|
||||
"instance_uuid": instance_uuid,
|
||||
"msg": e})
|
||||
|
||||
instance_data['power_state'] = node.power_state
|
||||
return Instance.convert_with_links(instance_data, fields=fields)
|
||||
|
||||
@expose.expose(InstanceCollection)
|
||||
def detail(self):
|
||||
"""Retrieve detail of a list of instances."""
|
||||
# /detail should only work against collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "instances":
|
||||
raise exception.NotFound()
|
||||
return self._get_instance_collection()
|
||||
|
||||
@expose.expose(Instance, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
|
@ -16,10 +16,9 @@
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import six
|
||||
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
import six
|
||||
from wsme import types as wtypes
|
||||
|
||||
from nimble.common import exception
|
||||
@ -91,7 +90,33 @@ class JsonType(wtypes.UserType):
|
||||
return JsonType.validate(value)
|
||||
|
||||
|
||||
class ListType(wtypes.UserType):
|
||||
"""A simple list type."""
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'list'
|
||||
|
||||
@staticmethod
|
||||
def validate(value):
|
||||
"""Validate and convert the input to a ListType.
|
||||
|
||||
:param value: A comma separated string of values
|
||||
:returns: A list of unique values, whose order is not guaranteed.
|
||||
"""
|
||||
items = [v.strip().lower() for v in six.text_type(value).split(',')]
|
||||
# filter() to remove empty items
|
||||
# set() to remove duplicated items
|
||||
return list(set(filter(None, items)))
|
||||
|
||||
@staticmethod
|
||||
def frombasetype(value):
|
||||
if value is None:
|
||||
return None
|
||||
return ListType.validate(value)
|
||||
|
||||
|
||||
boolean = BooleanType()
|
||||
uuid = UuidType()
|
||||
# Can't call it 'json' because that's the name of the stdlib module
|
||||
jsontype = JsonType()
|
||||
listtype = ListType()
|
||||
|
37
nimble/api/controllers/v1/utils.py
Normal file
37
nimble/api/controllers/v1/utils.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Copyright 2016 Intel, 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 nimble.common.i18n import _
|
||||
from oslo_config import cfg
|
||||
import wsme
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def validate_limit(limit):
|
||||
if limit is None:
|
||||
return CONF.api.max_limit
|
||||
|
||||
if limit <= 0:
|
||||
raise wsme.exc.ClientSideError(_("Limit must be positive"))
|
||||
|
||||
return min(CONF.api.max_limit, limit)
|
||||
|
||||
|
||||
def validate_sort_dir(sort_dir):
|
||||
if sort_dir not in ['asc', 'desc']:
|
||||
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
|
||||
"Acceptable values are "
|
||||
"'asc' or 'desc'") % sort_dir)
|
||||
return sort_dir
|
@ -78,8 +78,6 @@ def upgrade():
|
||||
sa.Column('name', sa.String(length=255), nullable=True),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('status', sa.String(length=255), nullable=True),
|
||||
sa.Column('power_state', sa.String(length=255), nullable=True),
|
||||
sa.Column('task_state', sa.String(length=255), nullable=True),
|
||||
sa.Column('instance_type_uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('image_uuid', sa.String(length=36), nullable=True),
|
||||
sa.Column('network_info', sa.Text(), nullable=True),
|
||||
|
@ -127,8 +127,6 @@ class Instance(Base):
|
||||
project_id = Column(String(36), nullable=True)
|
||||
user_id = Column(String(36), nullable=True)
|
||||
status = Column(String(255), nullable=True)
|
||||
power_state = Column(String(255), nullable=True)
|
||||
task_state = Column(String(255), nullable=True)
|
||||
instance_type_uuid = Column(String(36), nullable=True)
|
||||
availability_zone = Column(String(255), nullable=True)
|
||||
image_uuid = Column(String(36), nullable=True)
|
||||
|
@ -85,10 +85,12 @@ def do_node_deploy(node_uuid):
|
||||
ironic_states.ACTIVE)
|
||||
|
||||
|
||||
def get_node_by_instance(instance_uuid):
|
||||
def get_node_by_instance(instance_uuid, fields=None):
|
||||
if fields is None:
|
||||
fields = _NODE_FIELDS
|
||||
ironicclient = ironic.IronicClientWrapper()
|
||||
return ironicclient.call('node.get_by_instance_uuid',
|
||||
instance_uuid, fields=_NODE_FIELDS)
|
||||
instance_uuid, fields=fields)
|
||||
|
||||
|
||||
def destroy_node(node_uuid):
|
||||
|
@ -36,8 +36,6 @@ class Instance(base.NimbleObject, object_base.VersionedObjectDictCompat):
|
||||
'project_id': object_fields.UUIDField(nullable=True),
|
||||
'user_id': object_fields.UUIDField(nullable=True),
|
||||
'status': object_fields.StringField(nullable=True),
|
||||
'power_state': object_fields.StringField(nullable=True),
|
||||
'task_state': object_fields.StringField(nullable=True),
|
||||
'instance_type_uuid': object_fields.UUIDField(nullable=True),
|
||||
'availability_zone': object_fields.StringField(nullable=True),
|
||||
'image_uuid': object_fields.UUIDField(nullable=True),
|
||||
@ -59,9 +57,9 @@ class Instance(base.NimbleObject, object_base.VersionedObjectDictCompat):
|
||||
return Instance._from_db_object_list(db_instances, cls, context)
|
||||
|
||||
@classmethod
|
||||
def get(cls, context, instance_id):
|
||||
def get(cls, context, uuid):
|
||||
"""Find a instance and return a Instance object."""
|
||||
db_instance = cls.dbapi.instance_get(instance_id)
|
||||
db_instance = cls.dbapi.instance_get(uuid)
|
||||
instance = Instance._from_db_object(cls(context), db_instance)
|
||||
return instance
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user