create, destroy, list and show commands for plugin have been implemented
Change-Id: I5a65c940edb676150a83adf62a57cb689fc0591e
This commit is contained in:
parent
ac70869ee9
commit
7f717e995b
@ -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 {
|
||||
|
@ -31,6 +31,7 @@ app = {
|
||||
'/',
|
||||
'/v1',
|
||||
'/v1/nodes/[a-z0-9\-]',
|
||||
'/v1/plugins/[a-z0-9\-]',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -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):
|
||||
|
168
iotronic/api/controllers/v1/plugin.py
Normal file
168
iotronic/api/controllers/v1/plugin.py
Normal 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)
|
@ -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)
|
||||
|
@ -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.")
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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
187
iotronic/objects/plugin.py
Normal 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]
|
@ -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
60
utils/iotronic_curl_client
Executable 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
|
||||
|
Loading…
Reference in New Issue
Block a user