create, destroy, list and show commands for plugin have been implemented

Change-Id: I5a65c940edb676150a83adf62a57cb689fc0591e
This commit is contained in:
Fabio Verboso 2017-02-21 14:02:24 +01:00
parent ac70869ee9
commit 7f717e995b
15 changed files with 827 additions and 62 deletions

View File

@ -3,6 +3,7 @@
function build_install {
python setup.py build
python setup.py install
cp utils/iotronic_curl_client /usr/bin/iotronic
}
function restart_apache {

View File

@ -31,6 +31,7 @@ app = {
'/',
'/v1',
'/v1/nodes/[a-z0-9\-]',
'/v1/plugins/[a-z0-9\-]',
],
}

View File

@ -19,6 +19,7 @@ Version 1 of the Iotronic API
from iotronic.api.controllers import base
from iotronic.api.controllers import link
from iotronic.api.controllers.v1 import node
from iotronic.api.controllers.v1 import plugin
from iotronic.api import expose
from iotronic.common.i18n import _
import pecan
@ -52,6 +53,8 @@ class V1(base.APIBase):
nodes = [link.Link]
"""Links to the nodes resource"""
plugins = [link.Link]
@staticmethod
def convert():
v1 = V1()
@ -65,6 +68,14 @@ class V1(base.APIBase):
bookmark=True)
]
v1.plugins = [link.Link.make_link('self', pecan.request.host_url,
'plugins', ''),
link.Link.make_link('bookmark',
pecan.request.host_url,
'plugins', '',
bookmark=True)
]
'''
v1.links = [link.Link.make_link('self', pecan.request.host_url,
'v1', '', bookmark=True),
@ -82,6 +93,7 @@ class Controller(rest.RestController):
"""Version 1 API controller root."""
nodes = node.NodesController()
plugins = plugin.PluginsController()
@expose.expose(V1)
def get(self):

View File

@ -0,0 +1,168 @@
# 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 iotronic.api.controllers import base
from iotronic.api.controllers.v1 import collection
from iotronic.api.controllers.v1 import types
from iotronic.api.controllers.v1 import utils as api_utils
from iotronic.api import expose
from iotronic.common import exception
from iotronic import objects
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
class Plugin(base.APIBase):
"""API representation of a plugin.
"""
uuid = types.uuid
name = wsme.wsattr(wtypes.text)
config = wsme.wsattr(wtypes.text)
extra = types.jsontype
@staticmethod
def _convert(plugin, url, expand=True, show_password=True):
if not expand:
except_list = ['name', 'code', 'status', 'uuid', 'session', 'type']
plugin.unset_fields_except(except_list)
return plugin
return plugin
@classmethod
def convert(cls, rpc_plugin, expand=True):
plugin = Plugin(**rpc_plugin.as_dict())
# plugin.id = rpc_plugin.id
return cls._convert(plugin, pecan.request.host_url,
expand,
pecan.request.context.show_password)
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.Plugin.fields)
for k in fields:
# Skip fields we do not expose.
if not hasattr(self, k):
continue
self.fields.append(k)
setattr(self, k, kwargs.get(k, wtypes.Unset))
class PluginCollection(collection.Collection):
"""API representation of a collection of plugins."""
plugins = [Plugin]
"""A list containing plugins objects"""
def __init__(self, **kwargs):
self._type = 'plugins'
@staticmethod
def convert(plugins, limit, url=None, expand=False, **kwargs):
collection = PluginCollection()
collection.plugins = [
Plugin.convert(
n, expand) for n in plugins]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class PluginsController(rest.RestController):
invalid_sort_key_list = []
def _get_plugins_collection(self, marker, limit, sort_key, sort_dir,
expand=False, resource_url=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.Plugin.get_by_uuid(pecan.request.context,
marker)
if sort_key in self.invalid_sort_key_list:
raise exception.InvalidParameterValue(
("The sort_key value %(key)s is an invalid field for "
"sorting") % {'key': sort_key})
filters = {}
plugins = objects.Plugin.list(pecan.request.context, limit, marker_obj,
sort_key=sort_key, sort_dir=sort_dir,
filters=filters)
parameters = {'sort_key': sort_key, 'sort_dir': sort_dir}
return PluginCollection.convert(plugins, limit,
url=resource_url,
expand=expand,
**parameters)
@expose.expose(PluginCollection, types.uuid, int, wtypes.text, wtypes.text)
def get_all(self, marker=None, limit=None, sort_key='id',
sort_dir='asc'):
"""Retrieve a list of plugins.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
"""
return self._get_plugins_collection(marker,
limit, sort_key, sort_dir)
@expose.expose(Plugin, types.uuid_or_name)
def get(self, plugin_ident):
"""Retrieve information about the given plugin.
:param plugin_ident: UUID or logical name of a plugin.
"""
rpc_plugin = api_utils.get_rpc_plugin(plugin_ident)
plugin = Plugin(**rpc_plugin.as_dict())
plugin.id = rpc_plugin.id
return Plugin.convert(plugin)
@expose.expose(Plugin, body=Plugin, status_code=201)
def post(self, Plugin):
"""Create a new Plugin.
:param Plugin: a Plugin within the request body.
"""
if not Plugin.name:
raise exception.MissingParameterValue(
("Name is not specified."))
if Plugin.name:
if not api_utils.is_valid_name(Plugin.name):
msg = ("Cannot create plugin with invalid name %(name)s")
raise wsme.exc.ClientSideError(msg % {'name': Plugin.name},
status_code=400)
new_Plugin = objects.Plugin(pecan.request.context,
**Plugin.as_dict())
new_Plugin = pecan.request.rpcapi.create_plugin(pecan.request.context,
new_Plugin)
return Plugin.convert(new_Plugin)
@expose.expose(None, types.uuid_or_name, status_code=204)
def delete(self, plugin_ident):
"""Delete a plugin.
:param plugin_ident: UUID or logical name of a plugin.
"""
rpc_plugin = api_utils.get_rpc_plugin(plugin_ident)
pecan.request.rpcapi.destroy_plugin(pecan.request.context,
rpc_plugin.uuid)

View File

@ -92,10 +92,34 @@ def get_rpc_node(node_ident):
raise exception.InvalidUuidOrName(name=node_ident)
# Ensure we raise the same exception as we did for the Juno release
raise exception.NodeNotFound(node=node_ident)
def get_rpc_plugin(plugin_ident):
"""Get the RPC plugin from the plugin uuid or logical name.
:param plugin_ident: the UUID or logical name of a plugin.
:returns: The RPC Plugin.
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
:raises: PluginNotFound if the plugin is not found.
"""
# Check to see if the plugin_ident is a valid UUID. If it is, treat it
# as a UUID.
if uuidutils.is_uuid_like(plugin_ident):
return objects.Plugin.get_by_uuid(pecan.request.context, plugin_ident)
# We can refer to plugins by their name, if the client supports it
# if allow_plugin_logical_names():
# if utils.is_hostname_safe(plugin_ident):
else:
return objects.Plugin.get_by_name(pecan.request.context, plugin_ident)
raise exception.InvalidUuidOrName(name=plugin_ident)
raise exception.PluginNotFound(plugin=plugin_ident)
def is_valid_node_name(name):
"""Determine if the provided name is a valid node name.
@ -105,3 +129,14 @@ def is_valid_node_name(name):
:returns: True if the name is valid, False otherwise.
"""
return utils.is_hostname_safe(name) and (not uuidutils.is_uuid_like(name))
def is_valid_name(name):
"""Determine if the provided name is a valid name.
Check to see that the provided node name isn't a UUID.
:param: name: the node name to check.
:returns: True if the name is valid, False otherwise.
"""
return not uuidutils.is_uuid_like(name)

View File

@ -578,3 +578,7 @@ class PathNotFound(IotronicException):
class DirectoryNotWritable(IotronicException):
message = _("Directory %(dir)s is not writable.")
class PluginNotFound(NotFound):
message = _("Plugin %(plugin)s could not be found.")

View File

@ -120,7 +120,11 @@ class ConductorEndpoint(object):
prov.conf_clean()
p = prov.get_config()
LOG.debug('sending this conf %s', p)
self.execute_on_node(ctx, node_id, 'destroyNode', (p,))
try:
self.execute_on_node(ctx, node_id, 'destroyNode', (p,))
except Exception:
LOG.error('cannot execute remote destroynode on %s. '
'Maybe it is OFFLINE', node_id)
node.destroy()
@ -161,3 +165,23 @@ class ConductorEndpoint(object):
return self.wamp_agent_client.call(ctx, full_topic,
wamp_rpc_call=full_wamp_call,
data=wamp_rpc_args)
def destroy_plugin(self, ctx, plugin_id):
LOG.info('Destroying plugin with id %s',
plugin_id)
plugin = objects.Plugin.get_by_uuid(ctx, plugin_id)
plugin.destroy()
return
def update_plugin(self, ctx, plugin_obj):
plugin = serializer.deserialize_entity(ctx, plugin_obj)
LOG.debug('Updating plugin %s', plugin.name)
plugin.save()
return serializer.serialize_entity(ctx, plugin)
def create_plugin(self, ctx, plugin_obj):
new_plugin = serializer.deserialize_entity(ctx, plugin_obj)
LOG.debug('Creating plugin %s',
new_plugin.name)
new_plugin.create()
return serializer.serialize_entity(ctx, new_plugin)

View File

@ -93,10 +93,6 @@ class ConductorAPI(object):
"""Synchronously, have a conductor update the node's information.
Update the node's information in the database and return a node object.
The conductor will lock the node while it validates the supplied
information. If driver_info is passed, it will be validated by
the core drivers. If instance_uuid is passed, it will be set or unset
only if the node is properly configured.
Note that power_state should not be passed via this method.
Use change_node_power_state for initiating driver actions.
@ -130,3 +126,45 @@ class ConductorAPI(object):
return cctxt.call(context, 'execute_on_node', node_uuid=node_uuid,
wamp_rpc_call=wamp_rpc_call,
wamp_rpc_args=wamp_rpc_args)
def create_plugin(self, context, plugin_obj, topic=None):
"""Add a plugin on the cloud
:param context: request context.
:param plugin_obj: a changed (but not saved) plugin object.
:param topic: RPC topic. Defaults to self.topic.
:returns: created plugin object
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'create_plugin',
plugin_obj=plugin_obj)
def update_plugin(self, context, plugin_obj, topic=None):
"""Synchronously, have a conductor update the plugin's information.
Update the plugin's information in the database and
return a plugin object.
:param context: request context.
:param plugin_obj: a changed (but not saved) plugin object.
:param topic: RPC topic. Defaults to self.topic.
:returns: updated plugin object, including all fields.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'update_plugin', plugin_obj=plugin_obj)
def destroy_plugin(self, context, plugin_id, topic=None):
"""Delete a plugin.
:param context: request context.
:param plugin_id: plugin id or uuid.
:raises: PluginLocked if plugin is locked by another conductor.
:raises: PluginAssociated if the plugin contains an instance
associated with it.
:raises: InvalidState if the plugin is in the wrong provision
state to perform deletion.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'destroy_plugin', plugin_id=plugin_id)

View File

@ -204,6 +204,20 @@ class Connection(object):
:returns: A session.
"""
@abc.abstractmethod
def get_session_by_node_uuid(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a Wamp session of a Node
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of wampagents to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
"""
@abc.abstractmethod
def create_location(self, values):
"""Create a new location.
@ -290,15 +304,52 @@ class Connection(object):
"""
@abc.abstractmethod
def get_session_by_node_uuid(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a Wamp session of a Node
def get_plugin_by_id(self, plugin_id):
"""Return a plugin.
:param filters: Filters to apply. Defaults to None.
:param limit: Maximum number of wampagents to return.
:param marker: the last item of the previous page; we return the next
result set.
:param sort_key: Attribute by which results should be sorted.
:param sort_dir: direction in which results should be sorted.
(asc, desc)
:param plugin_id: The id of a plugin.
:returns: A plugin.
"""
@abc.abstractmethod
def get_plugin_by_uuid(self, plugin_uuid):
"""Return a plugin.
:param plugin_uuid: The uuid of a plugin.
:returns: A plugin.
"""
@abc.abstractmethod
def get_plugin_by_name(self, plugin_name):
"""Return a plugin.
:param plugin_name: The logical name of a plugin.
:returns: A plugin.
"""
@abc.abstractmethod
def create_plugin(self, values):
"""Create a new plugin.
:param values: A dict containing several items used to identify
and track the plugin
:returns: A plugin.
"""
@abc.abstractmethod
def destroy_plugin(self, plugin_id):
"""Destroy a plugin and all associated interfaces.
:param plugin_id: The id or uuid of a plugin.
"""
@abc.abstractmethod
def update_plugin(self, plugin_id, values):
"""Update properties of a plugin.
:param plugin_id: The id or uuid of a plugin.
:param values: Dict of values to update.
:returns: A plugin.
:raises: PluginAssociated
:raises: PluginNotFound
"""

View File

@ -108,21 +108,20 @@ def _paginate_query(model, limit=None, marker=None, sort_key=None,
return query.all()
def add_location_filter_by_node(query, value):
if strutils.is_int_like(value):
return query.filter_by(node_id=value)
else:
query = query.join(models.Node,
models.Location.node_id == models.Node.id)
return query.filter(models.Node.uuid == value)
class Connection(api.Connection):
"""SqlAlchemy connection."""
def __init__(self):
pass
def _add_location_filter_by_node(self, query, value):
if strutils.is_int_like(value):
return query.filter_by(node_id=value)
else:
query = query.join(models.Node,
models.Location.node_id == models.Node.id)
return query.filter(models.Node.uuid == value)
def _add_nodes_filters(self, query, filters):
if filters is None:
filters = []
@ -135,6 +134,13 @@ class Connection(api.Connection):
return query
def _add_plugins_filters(self, query, filters):
if filters is None:
filters = []
# TBD
return query
def _add_wampagents_filters(self, query, filters):
if filters is None:
filters = []
@ -145,8 +151,34 @@ class Connection(api.Connection):
else:
query = query.filter(models.WampAgent.online == 0)
if 'no_ragent' in filters:
if filters['no_ragent']:
query = query.filter(models.WampAgent.ragent == 0)
else:
query = query.filter(models.WampAgent.ragent == 1)
return query
def _do_update_node(self, node_id, values):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
# Prevent instance_uuid overwriting
if values.get("instance_uuid") and ref.instance_uuid:
raise exception.NodeAssociated(
node=node_id, instance=ref.instance_uuid)
ref.update(values)
return ref
# NODE api
def get_nodeinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
# list-ify columns default values because it is bad form
@ -182,7 +214,7 @@ class Connection(api.Connection):
except db_exc.DBDuplicateEntry as exc:
if 'code' in exc.columns:
raise exception.DuplicateCode(code=values['code'])
raise exception.BoardAlreadyExists(uuid=values['uuid'])
raise exception.NodeAlreadyExists(uuid=values['uuid'])
return node
def get_node_by_id(self, node_id):
@ -230,7 +262,7 @@ class Connection(api.Connection):
node_id = node_ref['id']
location_query = model_query(models.Location, session=session)
location_query = add_location_filter_by_node(
location_query = self._add_location_filter_by_node(
location_query, node_id)
location_query.delete()
@ -256,23 +288,7 @@ class Connection(api.Connection):
else:
raise e
def _do_update_node(self, node_id, values):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
# Prevent instance_uuid overwriting
if values.get("instance_uuid") and ref.instance_uuid:
raise exception.NodeAssociated(
node=node_id, instance=ref.instance_uuid)
ref.update(values)
return ref
# CONDUCTOR api
def register_conductor(self, values, update_existing=False):
session = get_session()
@ -323,24 +339,7 @@ class Connection(api.Connection):
if count == 0:
raise exception.ConductorNotFound(conductor=hostname)
def create_session(self, values):
session = models.SessionWP()
session.update(values)
session.save()
return session
def update_session(self, ses_id, values):
# NOTE(dtantsur): this can lead to very strange errors
session = get_session()
try:
with session.begin():
query = model_query(models.SessionWP, session=session)
query = add_identity_filter(query, ses_id)
ref = query.one()
ref.update(values)
except NoResultFound:
raise exception.SessionWPNotFound(ses=ses_id)
return ref
# LOCATION api
def create_location(self, values):
location = models.Location()
@ -377,6 +376,27 @@ class Connection(api.Connection):
return _paginate_query(models.Location, limit, marker,
sort_key, sort_dir, query)
# SESSION api
def create_session(self, values):
session = models.SessionWP()
session.update(values)
session.save()
return session
def update_session(self, ses_id, values):
# NOTE(dtantsur): this can lead to very strange errors
session = get_session()
try:
with session.begin():
query = model_query(models.SessionWP, session=session)
query = add_identity_filter(query, ses_id)
ref = query.one()
ref.update(values)
except NoResultFound:
raise exception.SessionWPNotFound(ses=ses_id)
return ref
def get_session_by_node_uuid(self, node_uuid, valid):
query = model_query(
models.SessionWP).filter_by(
@ -394,6 +414,8 @@ class Connection(api.Connection):
except NoResultFound:
return None
# WAMPAGENT api
def register_wampagent(self, values, update_existing=False):
session = get_session()
with session.begin():
@ -457,3 +479,83 @@ class Connection(api.Connection):
query = self._add_wampagents_filters(query, filters)
return _paginate_query(models.WampAgent, limit, marker,
sort_key, sort_dir, query)
# PLUGIN api
def get_plugin_by_id(self, plugin_id):
query = model_query(models.Plugin).filter_by(id=plugin_id)
try:
return query.one()
except NoResultFound:
raise exception.PluginNotFound(plugin=plugin_id)
def get_plugin_by_uuid(self, plugin_uuid):
query = model_query(models.Plugin).filter_by(uuid=plugin_uuid)
try:
return query.one()
except NoResultFound:
raise exception.PluginNotFound(plugin=plugin_uuid)
def get_plugin_by_name(self, plugin_name):
query = model_query(models.Plugin).filter_by(name=plugin_name)
try:
return query.one()
except NoResultFound:
raise exception.PluginNotFound(plugin=plugin_name)
def destroy_plugin(self, plugin_id):
session = get_session()
with session.begin():
query = model_query(models.Plugin, session=session)
query = add_identity_filter(query, plugin_id)
try:
plugin_ref = query.one()
except NoResultFound:
raise exception.PluginNotFound(plugin=plugin_id)
# Get plugin ID, if an UUID was supplied. The ID is
# required for deleting all ports, attached to the plugin.
if uuidutils.is_uuid_like(plugin_id):
plugin_id = plugin_ref['id']
query.delete()
def update_plugin(self, plugin_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Plugin.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_plugin(plugin_id, values)
except db_exc.DBDuplicateEntry as e:
if 'name' in e.columns:
raise exception.DuplicateName(name=values['name'])
elif 'uuid' in e.columns:
raise exception.PluginAlreadyExists(uuid=values['uuid'])
elif 'instance_uuid' in e.columns:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
plugin=plugin_id)
else:
raise e
def create_plugin(self, values):
# ensure defaults are present for new plugins
if 'uuid' not in values:
values['uuid'] = uuidutils.generate_uuid()
plugin = models.Plugin()
plugin.update(values)
try:
plugin.save()
except db_exc.DBDuplicateEntry:
raise exception.PluginAlreadyExists(uuid=values['uuid'])
return plugin
def get_plugin_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Plugin)
query = self._add_plugins_filters(query, filters)
return _paginate_query(models.Plugin, limit, marker,
sort_key, sort_dir, query)

View File

@ -190,3 +190,31 @@ class SessionWP(Base):
session_id = Column(String(15))
node_uuid = Column(String(36))
node_id = Column(Integer, ForeignKey('nodes.id'))
class Plugin(Base):
"""Represents a plugin."""
__tablename__ = 'plugins'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_plugins0uuid'),
table_args())
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
name = Column(String(36))
config = Column(TEXT)
extra = Column(JSONEncodedDict)
class Injected_Plugin(Base):
"""Represents an plugin injection on board."""
__tablename__ = 'injected_plugins'
__table_args__ = (
table_args())
id = Column(Integer, primary_key=True)
node_uuid = Column(String(36))
node_id = Column(Integer, ForeignKey('nodes.id'))
plugin_uuid = Column(String(36))
plugin_id = Column(Integer, ForeignKey('plugins.id'))
status = Column(String(15))

View File

@ -15,12 +15,14 @@
from iotronic.objects import conductor
from iotronic.objects import location
from iotronic.objects import node
from iotronic.objects import plugin
from iotronic.objects import sessionwp
from iotronic.objects import wampagent
Conductor = conductor.Conductor
Node = node.Node
Location = location.Location
Plugin = plugin.Plugin
SessionWP = sessionwp.SessionWP
WampAgent = wampagent.WampAgent
@ -30,4 +32,5 @@ __all__ = (
Location,
SessionWP,
WampAgent,
Plugin,
)

187
iotronic/objects/plugin.py Normal file
View File

@ -0,0 +1,187 @@
# coding=utf-8
#
#
# 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_utils import strutils
from oslo_utils import uuidutils
from iotronic.common import exception
from iotronic.db import api as db_api
from iotronic.objects import base
from iotronic.objects import utils as obj_utils
class Plugin(base.IotronicObject):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {
'id': int,
'uuid': obj_utils.str_or_none,
'name': obj_utils.str_or_none,
'config': obj_utils.str_or_none,
'extra': obj_utils.dict_or_none,
}
@staticmethod
def _from_db_object(plugin, db_plugin):
"""Converts a database entity to a formal object."""
for field in plugin.fields:
plugin[field] = db_plugin[field]
plugin.obj_reset_changes()
return plugin
@base.remotable_classmethod
def get(cls, context, plugin_id):
"""Find a plugin based on its id or uuid and return a Node object.
:param plugin_id: the id *or* uuid of a plugin.
:returns: a :class:`Node` object.
"""
if strutils.is_int_like(plugin_id):
return cls.get_by_id(context, plugin_id)
elif uuidutils.is_uuid_like(plugin_id):
return cls.get_by_uuid(context, plugin_id)
else:
raise exception.InvalidIdentity(identity=plugin_id)
@base.remotable_classmethod
def get_by_id(cls, context, plugin_id):
"""Find a plugin based on its integer id and return a Node object.
:param plugin_id: the id of a plugin.
:returns: a :class:`Node` object.
"""
db_plugin = cls.dbapi.get_plugin_by_id(plugin_id)
plugin = Plugin._from_db_object(cls(context), db_plugin)
return plugin
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a plugin based on uuid and return a Node object.
:param uuid: the uuid of a plugin.
:returns: a :class:`Node` object.
"""
db_plugin = cls.dbapi.get_plugin_by_uuid(uuid)
plugin = Plugin._from_db_object(cls(context), db_plugin)
return plugin
@base.remotable_classmethod
def get_by_name(cls, context, name):
"""Find a plugin based on name and return a Node object.
:param name: the logical name of a plugin.
:returns: a :class:`Node` object.
"""
db_plugin = cls.dbapi.get_plugin_by_name(name)
plugin = Plugin._from_db_object(cls(context), db_plugin)
return plugin
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None, sort_key=None,
sort_dir=None, filters=None):
"""Return a list of Plugin objects.
:param context: Security context.
:param limit: maximum number of resources to return in a single result.
:param marker: pagination marker for large data sets.
:param sort_key: column to sort results by.
:param sort_dir: direction to sort. "asc" or "desc".
:param filters: Filters to apply.
:returns: a list of :class:`Plugin` object.
"""
db_plugins = cls.dbapi.get_plugin_list(filters=filters,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return [Plugin._from_db_object(cls(context), obj)
for obj in db_plugins]
@base.remotable
def create(self, context=None):
"""Create a Plugin record in the DB.
Column-wise updates will be made based on the result of
self.what_changed(). If target_power_state is provided,
it will be checked against the in-database copy of the
plugin before updates are made.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Plugin(context)
"""
values = self.obj_get_changes()
db_plugin = self.dbapi.create_plugin(values)
self._from_db_object(self, db_plugin)
@base.remotable
def destroy(self, context=None):
"""Delete the Plugin from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Plugin(context)
"""
self.dbapi.destroy_plugin(self.uuid)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this Plugin.
Column-wise updates will be made based on the result of
self.what_changed(). If target_power_state is provided,
it will be checked against the in-database copy of the
plugin before updates are made.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Plugin(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_plugin(self.uuid, updates)
self.obj_reset_changes()
@base.remotable
def refresh(self, context=None):
"""Refresh the object by re-fetching from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
Unfortunately, RPC requires context as the first
argument, even though we don't use it.
A context should be set when instantiating the
object, e.g.: Plugin(context)
"""
current = self.__class__.get_by_uuid(self._context, self.uuid)
for field in self.fields:
if (hasattr(
self, base.get_attrname(field)) and
self[field] != current[field]):
self[field] = current[field]

View File

@ -132,6 +132,57 @@ ENGINE = InnoDB
AUTO_INCREMENT = 10
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
-- Table `iotronic`.`plugins`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `iotronic`.`plugins` ;
CREATE TABLE IF NOT EXISTS `iotronic`.`plugins` (
`created_at` DATETIME NULL DEFAULT NULL,
`updated_at` DATETIME NULL DEFAULT NULL,
`id` INT(11) NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(36) NOT NULL,
`name` VARCHAR(255) NULL DEFAULT NULL,
`config` TEXT NULL DEFAULT NULL,
`extra` TEXT NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `uuid` (`uuid` ASC))
ENGINE = InnoDB
AUTO_INCREMENT = 132
DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
-- Table `iotronic`.`injected_plugins`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `iotronic`.`injected_plugins` ;
CREATE TABLE IF NOT EXISTS `iotronic`.`injected_plugins` (
`created_at` DATETIME NULL DEFAULT NULL,
`updated_at` DATETIME NULL DEFAULT NULL,
`id` INT(11) NOT NULL AUTO_INCREMENT,
`node_uuid` VARCHAR(36) NOT NULL,
`node_id` INT(11) NOT NULL,
`plugin_uuid` VARCHAR(36) NOT NULL,
`plugin_id` INT(11) NOT NULL,
`status` VARCHAR(15) NOT NULL DEFAULT 'injected',
PRIMARY KEY (`id`),
INDEX `node_id` (`node_id` ASC),
CONSTRAINT `node_id`
FOREIGN KEY (`node_id`)
REFERENCES `iotronic`.`nodes` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
INDEX `plugin_id` (`plugin_id` ASC),
CONSTRAINT `plugin_id`
FOREIGN KEY (`plugin_id`)
REFERENCES `iotronic`.`plugins` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
AUTO_INCREMENT = 132
DEFAULT CHARACTER SET = utf8;
SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

60
utils/iotronic_curl_client Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
HOST='localhost'
PORT='1288'
VERSION='v1'
BASE=http://$HOST:$PORT/$VERSION
function node_manager() {
case "$1" in
list) curl -sS $BASE/nodes/ | python -m json.tool
echo "";
;;
create) curl -sS -H "Content-Type: application/json" -X POST $BASE/nodes/ \
-d '{"type":"'"$7"'","code":"'"$2"'","name":"'"$3"'","location":[{"latitude":"'"$4"'","longitude":"'"$5"'","altitude":"'"$6"'"}]}' | python -m json.tool
echo "";
;;
delete) curl -sS -X DELETE $BASE/nodes/$2 | python -m json.tool
echo "";
;;
show) curl -sS $BASE/nodes/$2 | python -m json.tool
echo "";
;;
*) echo "node list|create|delete|show"
esac
}
function plugin_manager() {
case "$1" in
list) curl -sS $BASE/plugins/ | python -m json.tool
echo "";
;;
create) echo "TBI"
echo "";
;;
delete) echo "TBI"
echo "";
;;
show) curl -sS $BASE/plugins/$2 | python -m json.tool
echo "";
;;
*) echo "plugin list|create|delete|show"
esac
}
if [ $# -lt 1 ]
then
echo "USAGE: iotronic node|plugin [OPTIONS]"
exit
fi
case "$1" in
node) node_manager "${@:2}";
echo "";
;;
plugin) plugin_manager "${@:2}"
echo "";
;;
*) echo "USAGE: iotronic node|plugin [OPTIONS]"
esac