Add Instances API

Change-Id: Idccee80dce3ed60b491bd3a0fa56429770c618d0
This commit is contained in:
Zhenguo Niu
2016-08-23 19:15:47 +08:00
parent 10e8d712ca
commit 684969b293
10 changed files with 316 additions and 8 deletions

View File

@@ -16,13 +16,122 @@
import pecan
from pecan import rest
from six.moves import http_client
import wsme
from wsme import types as wtypes
from nimble.api.controllers import base
from nimble.api.controllers import link
from nimble.api.controllers.v1 import types
from nimble.api import expose
from nimble import objects
class Instance(base.APIBase):
"""API representation of a instance.
This class enforces type checking and value constraints, and converts
between the internal object model and the API representation of
a instance.
"""
uuid = types.uuid
"""The UUID of the instance"""
name = wtypes.text
"""The name of the instance"""
description = wtypes.text
"""The description of the instance"""
status = wtypes.text
"""The status of the instance"""
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"""
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link"""
def __init__(self, **kwargs):
self.fields = []
for field in objects.Instance.fields:
# Skip fields we do not expose.
if not hasattr(self, field):
continue
self.fields.append(field)
setattr(self, field, kwargs.get(field, wtypes.Unset))
@classmethod
def convert_with_links(cls, rpc_instance):
instance = Instance(**rpc_instance.as_dict())
url = pecan.request.public_url
instance.links = [link.Link.make_link('self',
url,
'instance', instance.uuid),
link.Link.make_link('bookmark',
url,
'instance', instance.uuid,
bookmark=True)
]
return instance
class InstanceCollection(base.APIBase):
"""API representation of a collection of instance."""
instance = [Instance]
"""A list containing instance objects"""
@staticmethod
def convert_with_links(instance, url=None, **kwargs):
collection = InstanceCollection()
collection.instance = [Instance.convert_with_links(inst)
for inst in instance]
return collection
class InstanceController(rest.RestController):
"""REST controller for Chassis."""
"""REST controller for Instance."""
@expose.expose(InstanceCollection)
def get_all(self):
"""Retrieve a list of instance."""
instance = objects.Instance.list(pecan.request.context)
return InstanceCollection.convert_with_links(instance)
@expose.expose(Instance, types.uuid)
def get_one(self, instance_uuid):
"""Retrieve information about the given instance.
:param instance_uuid: UUID of a instance.
"""
rpc_instance = objects.Instance.get(pecan.request.context,
instance_uuid)
return Instance.convert_with_links(rpc_instance)
@expose.expose(Instance, body=Instance, status_code=http_client.CREATED)
def post(self, instance):
"""Create a new instance.
:param instance: a instance within the request body.
"""
instance_obj = objects.Instance(pecan.request.context,
**instance.as_dict())
instance_obj.create()
# Set the HTTP Location Header
pecan.response.location = link.build_url('instance', instance_obj.uuid)
new_instance = pecan.request.rpcapi.create_instance(
pecan.request.context, instance_obj)
return Instance.convert_with_links(new_instance)
@expose.expose(None, types.uuid, status_code=http_client.NO_CONTENT)
def delete(self, instance_uuid):
@@ -30,4 +139,6 @@ class InstanceController(rest.RestController):
:param instance_uuid: UUID of a instance.
"""
pecan.request.rpcapi.do_node_deploy(pecan.request.context)
rpc_instance = objects.Instance.get(pecan.request.context,
instance_uuid)
rpc_instance.destroy()

View File

@@ -106,6 +106,12 @@ class Invalid(NimbleException):
code = http_client.BAD_REQUEST
# Cannot be templated as the error syntax varies.
# msg needs to be constructed when raised.
class InvalidParameterValue(Invalid):
_msg_fmt = _("%(err)s")
class Conflict(NimbleException):
_msg_fmt = _('Conflict.')
code = http_client.CONFLICT
@@ -141,7 +147,19 @@ class FlavorNotFound(NotFound):
msg_fmt = _("Flavor %(flavor)s could not be found.")
class InstanceAlreadyExists(NimbleException):
_msg_fmt = _("Instance with name %(name)s already exists.")
class InstanceNotFound(NotFound):
msg_fmt = _("Instance %(instance)s could not be found.")
class NoFreeEngineWorker(TemporaryFailure):
_msg_fmt = _('Requested action cannot be performed due to lack of free '
'engine workers.')
code = http_client.SERVICE_UNAVAILABLE
class DuplicateName(Conflict):
_msg_fmt = _("A instance with name %(name)s already exists.")

View File

@@ -41,6 +41,7 @@ class Connection(object):
def __init__(self):
"""Constructor."""
# Flavors
@abc.abstractmethod
def flavor_create(self, values):
"""Create a new instance type."""
@@ -55,3 +56,19 @@ class Connection(object):
@abc.abstractmethod
def flavor_destroy(name):
"""Delete an instance type."""
# Instances
@abc.abstractmethod
def instance_create(self, values):
"""Create a new instance."""
@abc.abstractmethod
def instance_get(uuid):
"""Get instance by name."""
def instance_get_all():
"""Get all instances."""
@abc.abstractmethod
def instance_destroy(name):
"""Delete an instance."""

View File

@@ -67,7 +67,7 @@ def upgrade():
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_id', sa.Integer(), nullable=False),
sa.Column('instance_type_id', sa.Integer(), nullable=True),
sa.Column('launched_at', sa.DateTime(), nullable=True),
sa.Column('terminated_at', sa.DateTime(), nullable=True),
sa.Column('availability_zone', sa.String(length=255), nullable=True),
@@ -75,6 +75,7 @@ def upgrade():
sa.Column('extra', sa.Text(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('uuid', name='uniq_instances0uuid'),
sa.UniqueConstraint('name', name='uniq_instances0name'),
mysql_ENGINE='InnoDB',
mysql_DEFAULT_CHARSET='UTF8'
)

View File

@@ -115,3 +115,60 @@ class Connection(api.Connection):
count = query.delete()
if count != 1:
raise exception.FlavorNotFound(flavor=flavor_id)
def instance_create(self, values):
if not values.get('uuid'):
values['uuid'] = uuidutils.generate_uuid()
instance = models.Instance()
instance.update(values)
with _session_for_write() as session:
try:
session.add(instance)
session.flush()
except db_exc.DBDuplicateEntry:
raise exception.InstanceAlreadyExists(name=values['name'])
return instance
def instance_get(self, instance_id):
query = model_query(models.Instance).filter_by(uuid=instance_id)
try:
return query.one()
except NoResultFound:
raise exception.InstanceNotFound(instance=instance_id)
def instance_get_all(self):
return model_query(models.Instance)
def instance_destroy(self, instance_id):
with _session_for_write():
query = model_query(models.Instance)
query = add_identity_filter(query, instance_id)
count = query.delete()
if count != 1:
raise exception.InstanceNotFound(instance=instance_id)
def update_instance(self, instance_id, values):
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Instance.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_instance(instance_id, values)
except db_exc.DBDuplicateEntry as e:
if 'name' in e.columns:
raise exception.DuplicateName(name=values['name'])
def _do_update_instance(self, instance_id, values):
with _session_for_write():
query = model_query(models.Instance)
query = add_identity_filter(query, instance_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.InstanceNotFound(instance=instance_id)
ref.update(values)
return ref

View File

@@ -21,7 +21,7 @@ from oslo_db import options as db_options
from oslo_db.sqlalchemy import models
import six.moves.urllib.parse as urlparse
from sqlalchemy import Boolean, Column
from sqlalchemy import schema, String, Integer
from sqlalchemy import schema, String, Integer, Text
from sqlalchemy.ext.declarative import declarative_base
from nimble.common import paths
@@ -85,3 +85,24 @@ class InstanceTypeProjects(Base):
id = Column(Integer, primary_key=True)
instance_type_id = Column(Integer, nullable=True)
project_id = Column(String(36), nullable=True)
class Instance(Base):
"""Represents possible types for instances."""
__tablename__ = 'instances'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_instances0uuid'),
table_args()
)
id = Column(Integer, primary_key=True)
uuid = Column(String(36), nullable=True)
name = Column(String(255), nullable=False)
description = Column(String(255), nullable=True)
status = Column(String(255), nullable=True)
power_state = Column(String(255), nullable=True)
task_state = Column(String(255), nullable=True)
instance_type_id = Column(Integer, nullable=True)
availability_zone = Column(String(255), nullable=True)
node_uuid = Column(String(36), nullable=True)
extra = Column(Text, nullable=True)

View File

@@ -38,6 +38,9 @@ class EngineManager(base_manager.BaseEngineManager):
def _sync_node_resources(self, context):
LOG.info(_LI("During sync_node_resources."))
def do_node_deploy(self, context):
def create_instance(self, context, instance):
"""Signal to engine service to perform a deployment."""
LOG.debug("During do node deploy.")
LOG.debug("During create instance.")
instance.task_state = 'deploying'
instance.save()
return instance

View File

@@ -49,7 +49,7 @@ class EngineAPI(object):
version_cap=self.RPC_API_VERSION,
serializer=serializer)
def do_node_deploy(self, context):
def create_instance(self, context, instance):
"""Signal to engine service to perform a deployment."""
cctxt = self.client.prepare(topic=self.topic, server=CONF.host)
return cctxt.cast(context, 'do_node_deploy')
return cctxt.call(context, 'create_instance', instance=instance)

View File

@@ -26,3 +26,4 @@ def register_all():
# function in order for it to be registered by services that may
# need to receive it via RPC.
__import__('nimble.objects.flavor')
__import__('nimble.objects.instance')

View File

@@ -0,0 +1,79 @@
# Copyright 2016 Huawei Technologies Co.,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 oslo_versionedobjects import base as object_base
from nimble.db import api as dbapi
from nimble.objects import base
from nimble.objects import fields as object_fields
@base.NimbleObjectRegistry.register
class Instance(base.NimbleObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = dbapi.get_instance()
fields = {
'id': object_fields.IntegerField(),
'uuid': object_fields.UUIDField(nullable=True),
'name': object_fields.StringField(nullable=True),
'description': object_fields.StringField(nullable=True),
'status': object_fields.StringField(nullable=True),
'power_state': object_fields.StringField(nullable=True),
'task_state': object_fields.StringField(nullable=True),
'instance_type_id': object_fields.IntegerField(nullable=True),
'availability_zone': object_fields.StringField(nullable=True),
'node_uuid': object_fields.UUIDField(nullable=True),
'extra': object_fields.FlexibleDictField(nullable=True),
}
@staticmethod
def _from_db_object_list(db_objects, cls, context):
"""Converts a list of database entities to a list of formal objects."""
return [Instance._from_db_object(cls(context), obj)
for obj in db_objects]
@classmethod
def list(cls, context):
"""Return a list of Instance objects."""
db_instances = cls.dbapi.instance_get_all()
return Instance._from_db_object_list(db_instances, cls, context)
@classmethod
def get(cls, context, instance_id):
"""Find a instance and return a Instance object."""
db_instance = cls.dbapi.instance_get(instance_id)
instance = Instance._from_db_object(cls(context), db_instance)
return instance
def create(self, context=None):
"""Create a Instance record in the DB."""
values = self.obj_get_changes()
db_instance = self.dbapi.instance_create(values)
self._from_db_object(self, db_instance)
def destroy(self, context=None):
"""Delete the Instance from the DB."""
self.dbapi.instance_destroy(self.uuid)
self.obj_reset_changes()
def save(self, context=None):
"""Save updates to this Instance."""
updates = self.obj_get_changes()
self.dbapi.update_instance(self.uuid, updates)
self.obj_reset_changes()