A LOT OF STUFF :)

Change-Id: I7c4c386559def70142b76da6112dff50c55183cc
This commit is contained in:
Fabio Verboso 2017-03-22 17:16:14 +01:00
parent 0960dc4527
commit b9e45b1eee
32 changed files with 1535 additions and 1355 deletions

View File

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

View File

@ -32,7 +32,7 @@ from iotronic.api.controllers.v1 import plugin
# from iotronic.api.controllers.v1 import ramdisk
# from iotronic.api.controllers.v1 import utils
from iotronic.api.controllers.v1 import node
from iotronic.api.controllers.v1 import board
from iotronic.api.controllers.v1 import versions
from iotronic.api import expose
@ -57,8 +57,8 @@ class V1(base.APIBase):
# links = [link.Link]
"""Links that point to a specific URL for this version and documentation"""
nodes = [link.Link]
"""Links to the nodes resource"""
boards = [link.Link]
"""Links to the boards resource"""
@staticmethod
def convert():
@ -81,13 +81,13 @@ class V1(base.APIBase):
bookmark=True)
]
v1.nodes = [link.Link.make_link('self', pecan.request.public_url,
'nodes', ''),
link.Link.make_link('bookmark',
pecan.request.public_url,
'nodes', '',
bookmark=True)
]
v1.boards = [link.Link.make_link('self', pecan.request.public_url,
'boards', ''),
link.Link.make_link('bookmark',
pecan.request.public_url,
'boards', '',
bookmark=True)
]
return v1
@ -95,7 +95,7 @@ class V1(base.APIBase):
class Controller(rest.RestController):
"""Version 1 API controller root."""
nodes = node.NodesController()
boards = board.BoardsController()
plugins = plugin.PluginsController()
@expose.expose(V1)
@ -113,19 +113,20 @@ class Controller(rest.RestController):
raise exc.HTTPNotAcceptable(_(
"Mutually exclusive versions requested. Version %(ver)s "
"requested but not supported by this service. The supported "
"version range is: [%(min)s, %(max)s].") %
{'ver': version, 'min': versions.MIN_VERSION_STRING,
'max': versions.MAX_VERSION_STRING},
headers=headers)
"version range is: [%(min)s, %(max)s].") % {
'ver': version, 'min': versions.MIN_VERSION_STRING,
'max': versions.MAX_VERSION_STRING
}, headers=headers)
# ensure the minor version is within the supported range
if version < MIN_VER or version > MAX_VER:
raise exc.HTTPNotAcceptable(_(
"Version %(ver)s was requested but the minor version is not "
"supported by this service. The supported version range is: "
"[%(min)s, %(max)s].") %
{'ver': version, 'min': versions.MIN_VERSION_STRING,
'max': versions.MAX_VERSION_STRING},
headers=headers)
"[%(min)s, %(max)s].") % {
'ver': version,
'min': versions.MIN_VERSION_STRING,
'max': versions.MAX_VERSION_STRING
}, headers=headers)
@pecan.expose()
def _route(self, args):

View File

@ -0,0 +1,481 @@
# 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 import link
from iotronic.api.controllers.v1 import collection
from iotronic.api.controllers.v1 import location as loc
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.common import policy
from iotronic import objects
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
_DEFAULT_RETURN_FIELDS = ('name', 'code', 'status', 'uuid', 'session', 'type')
class Board(base.APIBase):
"""API representation of a board.
"""
uuid = types.uuid
code = wsme.wsattr(wtypes.text)
status = wsme.wsattr(wtypes.text)
name = wsme.wsattr(wtypes.text)
type = wsme.wsattr(wtypes.text)
owner = types.uuid
session = wsme.wsattr(wtypes.text)
project = types.uuid
mobile = types.boolean
links = wsme.wsattr([link.Link], readonly=True)
location = wsme.wsattr([loc.Location])
extra = types.jsontype
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.Board.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))
@staticmethod
def _convert_with_links(board, url, fields=None):
board_uuid = board.uuid
if fields is not None:
board.unset_fields_except(fields)
# rel_name, url, resource, resource_args,
# bookmark=False, type=wtypes.Unset
board.links = [link.Link.make_link('self', url, 'boards',
board_uuid),
link.Link.make_link('bookmark', url, 'boards',
board_uuid, bookmark=True)
]
return board
@classmethod
def convert_with_links(cls, rpc_board, fields=None):
board = Board(**rpc_board.as_dict())
try:
session = objects.SessionWP.get_session_by_board_uuid(
pecan.request.context, board.uuid)
board.session = session.session_id
except Exception:
board.session = None
try:
list_loc = objects.Location.list_by_board_uuid(
pecan.request.context, board.uuid)
board.location = loc.Location.convert_with_list(list_loc)
except Exception:
board.location = []
# to enable as soon as a better session and location management
# is implemented
# if fields is not None:
# api_utils.check_for_invalid_fields(fields, board_dict)
return cls._convert_with_links(board,
pecan.request.public_url,
fields=fields)
class BoardCollection(collection.Collection):
"""API representation of a collection of boards."""
boards = [Board]
"""A list containing boards objects"""
def __init__(self, **kwargs):
self._type = 'boards'
@staticmethod
def convert_with_links(boards, limit, url=None, fields=None, **kwargs):
collection = BoardCollection()
collection.boards = [Board.convert_with_links(n, fields=fields)
for n in boards]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class InjectionPlugin(base.APIBase):
plugin = types.uuid_or_name
board_uuid = types.uuid_or_name
status = wtypes.text
onboot = types.boolean
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.InjectionPlugin.fields)
fields.remove('board_uuid')
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))
setattr(self, 'plugin', kwargs.get('plugin_uuid', wtypes.Unset))
class InjectionCollection(collection.Collection):
"""API representation of a collection of injection."""
injections = [InjectionPlugin]
def __init__(self, **kwargs):
self._type = 'injections'
@staticmethod
def get_list(injections, fields=None):
collection = InjectionCollection()
collection.injections = [InjectionPlugin(**n.as_dict())
for n in injections]
return collection
class PluginAction(base.APIBase):
action = wsme.wsattr(wtypes.text)
parameters = types.jsontype
class BoardPluginsController(rest.RestController):
def __init__(self, board_ident):
self.board_ident = board_ident
def _get_plugins_on_board_collection(self, board_uuid, fields=None):
injections = objects.InjectionPlugin.list(pecan.request.context,
board_uuid)
return InjectionCollection.get_list(injections,
fields=fields)
@expose.expose(InjectionCollection,
status_code=200)
def get_all(self):
"""Retrieve a list of plugins of a board.
"""
# cdict = pecan.request.context.to_policy_values()
# policy.authorize('iot:plugins_on_board:get', cdict, cdict)
rpc_board = api_utils.get_rpc_board(self.board_ident)
return self._get_plugins_on_board_collection(rpc_board.uuid)
@expose.expose(InjectionPlugin, types.uuid_or_name)
def get_one(self, plugin_ident):
"""Retrieve information about the given board.
:param plugin_ident: UUID or logical name of a board.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
# cdict = pecan.request.context.to_policy_values()
# policy.authorize('iot:plugins_on_board:get', cdict, cdict)
rpc_board = api_utils.get_rpc_board(self.board_ident)
rpc_plugin = api_utils.get_rpc_plugin(plugin_ident)
inj_plug = objects.InjectionPlugin.get(pecan.request.context,
rpc_board.uuid,
rpc_plugin.uuid)
return InjectionPlugin(**inj_plug.as_dict())
@expose.expose(wtypes.text, types.uuid_or_name, body=PluginAction,
status_code=200)
def post(self, plugin_ident, PluginAction):
# cdict = pecan.request.context.to_policy_values()
# policy.authorize('iot:plugin_action:post', cdict, cdict)
rpc_plugin = api_utils.get_rpc_plugin(plugin_ident)
rpc_board = api_utils.get_rpc_board(self.board_ident)
result = pecan.request.rpcapi.action_plugin(pecan.request.context,
rpc_plugin.uuid,
rpc_board.uuid,
PluginAction.action,
PluginAction.parameters)
return result
@expose.expose(wtypes.text, body=InjectionPlugin,
status_code=200)
def put(self, Injection):
"""inject a plugin into a board.
:param plugin_ident: UUID or logical name of a plugin.
:param board_ident: UUID or logical name of a board.
"""
# cdict = context.to_policy_values()
# policy.authorize('iot:plugin:inject', cdict, cdict)
rpc_plugin = api_utils.get_rpc_plugin(Injection.plugin)
rpc_board = api_utils.get_rpc_board(self.board_ident)
result = pecan.request.rpcapi.inject_plugin(pecan.request.context,
rpc_plugin.uuid,
rpc_board.uuid,
Injection.onboot)
return result
@expose.expose(wtypes.text, types.uuid_or_name,
status_code=204)
def delete(self, plugin_uuid):
"""inject a plugin into a board.
:param plugin_ident: UUID or logical name of a plugin.
:param board_ident: UUID or logical name of a board.
"""
# cdict = context.to_policy_values()
# policy.authorize('iot:plugin:remove', cdict, cdict)
rpc_plugin = api_utils.get_rpc_plugin(plugin_uuid)
rpc_board = api_utils.get_rpc_board(self.board_ident)
return pecan.request.rpcapi.remove_plugin(pecan.request.context,
rpc_plugin.uuid,
rpc_board.uuid)
class BoardsController(rest.RestController):
"""REST controller for Boards."""
_subcontroller_map = {
'plugins': BoardPluginsController,
}
invalid_sort_key_list = ['extra', 'location']
_custom_actions = {
'detail': ['GET'],
}
@pecan.expose()
def _lookup(self, ident, *remainder):
try:
ident = types.uuid_or_name.validate(ident)
except exception.InvalidUuidOrName as e:
pecan.abort('400', e.args[0])
if not remainder:
return
subcontroller = self._subcontroller_map.get(remainder[0])
if subcontroller:
return subcontroller(board_ident=ident), remainder[1:]
def _get_boards_collection(self, marker, limit,
sort_key, sort_dir,
project=None,
resource_url=None, fields=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.Board.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 = {}
# bounding the request to a project
if project:
if pecan.request.context.is_admin:
filters['project_id'] = project
else:
msg = ("Project parameter can be used only "
"by the administrator.")
raise wsme.exc.ClientSideError(msg,
status_code=400)
else:
filters['project_id'] = pecan.request.context.project_id
boards = objects.Board.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 BoardCollection.convert_with_links(boards, limit,
url=resource_url,
fields=fields,
**parameters)
@expose.expose(Board, types.uuid_or_name, types.listtype)
def get_one(self, board_ident, fields=None):
"""Retrieve information about the given board.
:param board_ident: UUID or logical name of a board.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:board:get', cdict, cdict)
rpc_board = api_utils.get_rpc_board(board_ident)
return Board.convert_with_links(rpc_board, fields=fields)
@expose.expose(BoardCollection, types.uuid, int, wtypes.text,
wtypes.text, types.listtype, wtypes.text)
def get_all(self, marker=None,
limit=None, sort_key='id', sort_dir='asc',
fields=None):
"""Retrieve a list of boards.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
This value cannot be larger than the value of max_limit
in the [api] section of the ironic configuration, or only
max_limit resources will be returned.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:board:get', cdict, cdict)
if fields is None:
fields = _DEFAULT_RETURN_FIELDS
return self._get_boards_collection(marker,
limit, sort_key, sort_dir,
fields=fields)
@expose.expose(Board, body=Board, status_code=201)
def post(self, Board):
"""Create a new Board.
:param Board: a Board within the request body.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:board:create', cdict, cdict)
if not Board.name:
raise exception.MissingParameterValue(
("Name is not specified."))
if not Board.code:
raise exception.MissingParameterValue(
("Code is not specified."))
if not Board.location:
raise exception.MissingParameterValue(
("Location is not specified."))
if Board.name:
if not api_utils.is_valid_board_name(Board.name):
msg = ("Cannot create board with invalid name %(name)s")
raise wsme.exc.ClientSideError(msg % {'name': Board.name},
status_code=400)
new_Board = objects.Board(pecan.request.context,
**Board.as_dict())
new_Board.owner = pecan.request.context.user_id
new_Board.project = pecan.request.context.project_id
new_Location = objects.Location(pecan.request.context,
**Board.location[0].as_dict())
new_Board = pecan.request.rpcapi.create_board(pecan.request.context,
new_Board, new_Location)
return Board.convert_with_links(new_Board)
@expose.expose(None, types.uuid_or_name, status_code=204)
def delete(self, board_ident):
"""Delete a board.
:param board_ident: UUID or logical name of a board.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:board:delete', cdict, cdict)
rpc_board = api_utils.get_rpc_board(board_ident)
pecan.request.rpcapi.destroy_board(pecan.request.context,
rpc_board.uuid)
@expose.expose(Board, types.uuid_or_name, body=Board, status_code=200)
def patch(self, board_ident, val_Board):
"""Update a board.
:param board_ident: UUID or logical name of a board.
:param Board: values to be changed
:return updated_board: updated_board
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:board:update', cdict, cdict)
board = api_utils.get_rpc_board(board_ident)
val_Board = val_Board.as_dict()
for key in val_Board:
try:
board[key] = val_Board[key]
except Exception:
pass
updated_board = pecan.request.rpcapi.update_board(
pecan.request.context,
board)
return Board.convert_with_links(updated_board)
@expose.expose(BoardCollection, types.uuid, int, wtypes.text,
wtypes.text, types.listtype, wtypes.text)
def detail(self, marker=None,
limit=None, sort_key='id', sort_dir='asc',
fields=None, project=None):
"""Retrieve a list of boards.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
This value cannot be larger than the value of max_limit
in the [api] section of the ironic configuration, or only
max_limit resources will be returned.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param project: Optional string value to get only boards
of the project.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:board:get', cdict, cdict)
# /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "boards":
raise exception.HTTPNotFound()
return self._get_boards_collection(marker,
limit, sort_key, sort_dir,
project=project,
fields=fields)

View File

@ -1,323 +0,0 @@
# 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 import link
from iotronic.api.controllers.v1 import collection
from iotronic.api.controllers.v1 import location as loc
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.common import policy
from iotronic import objects
import pecan
from pecan import rest
import wsme
from wsme import types as wtypes
_DEFAULT_RETURN_FIELDS = ('name', 'code', 'status', 'uuid', 'session', 'type')
class Node(base.APIBase):
"""API representation of a node.
"""
uuid = types.uuid
code = wsme.wsattr(wtypes.text)
status = wsme.wsattr(wtypes.text)
name = wsme.wsattr(wtypes.text)
type = wsme.wsattr(wtypes.text)
owner = types.uuid
session = wsme.wsattr(wtypes.text)
project = types.uuid
mobile = types.boolean
links = wsme.wsattr([link.Link], readonly=True)
location = wsme.wsattr([loc.Location])
extra = types.jsontype
def __init__(self, **kwargs):
self.fields = []
fields = list(objects.Node.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))
@staticmethod
def _convert_with_links(node, url, fields=None):
node_uuid = node.uuid
if fields is not None:
node.unset_fields_except(fields)
# rel_name, url, resource, resource_args,
# bookmark=False, type=wtypes.Unset
node.links = [link.Link.make_link('self', url, 'nodes',
node_uuid),
link.Link.make_link('bookmark', url, 'nodes',
node_uuid, bookmark=True)
]
return node
@classmethod
def convert_with_links(cls, rpc_node, fields=None):
node = Node(**rpc_node.as_dict())
try:
session = objects.SessionWP.get_session_by_node_uuid(
pecan.request.context, node.uuid)
node.session = session.session_id
except Exception:
node.session = None
try:
list_loc = objects.Location.list_by_node_uuid(
pecan.request.context, node.uuid)
node.location = loc.Location.convert_with_list(list_loc)
except Exception:
node.location = []
# to enable as soon as a better session and location management
# is implemented
# if fields is not None:
# api_utils.check_for_invalid_fields(fields, node_dict)
return cls._convert_with_links(node,
pecan.request.public_url,
fields=fields)
class NodeCollection(collection.Collection):
"""API representation of a collection of nodes."""
nodes = [Node]
"""A list containing nodes objects"""
def __init__(self, **kwargs):
self._type = 'nodes'
@staticmethod
def convert_with_links(nodes, limit, url=None, fields=None, **kwargs):
collection = NodeCollection()
collection.nodes = [Node.convert_with_links(n, fields=fields)
for n in nodes]
collection.next = collection.get_next(limit, url=url, **kwargs)
return collection
class NodesController(rest.RestController):
"""REST controller for Nodes."""
invalid_sort_key_list = ['extra', 'location']
_custom_actions = {
'detail': ['GET'],
}
def _get_nodes_collection(self, marker, limit,
sort_key, sort_dir,
project=None,
resource_url=None, fields=None):
limit = api_utils.validate_limit(limit)
sort_dir = api_utils.validate_sort_dir(sort_dir)
marker_obj = None
if marker:
marker_obj = objects.Node.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 = {}
# bounding the request to a project
if project:
if pecan.request.context.is_admin:
filters['project_id'] = project
else:
msg = ("Project parameter can be used only "
"by the administrator.")
raise wsme.exc.ClientSideError(msg,
status_code=400)
else:
filters['project_id'] = pecan.request.context.project_id
nodes = objects.Node.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 NodeCollection.convert_with_links(nodes, limit,
url=resource_url,
fields=fields,
**parameters)
@expose.expose(Node, types.uuid_or_name, types.listtype)
def get_one(self, node_ident, fields=None):
"""Retrieve information about the given node.
:param node_ident: UUID or logical name of a node.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:node:get', cdict, cdict)
rpc_node = api_utils.get_rpc_node(node_ident)
return Node.convert_with_links(rpc_node, fields=fields)
@expose.expose(NodeCollection, types.uuid, int, wtypes.text,
wtypes.text, types.listtype, wtypes.text)
def get_all(self, marker=None,
limit=None, sort_key='id', sort_dir='asc',
fields=None):
"""Retrieve a list of nodes.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
This value cannot be larger than the value of max_limit
in the [api] section of the ironic configuration, or only
max_limit resources will be returned.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:node:get', cdict, cdict)
if fields is None:
fields = _DEFAULT_RETURN_FIELDS
return self._get_nodes_collection(marker,
limit, sort_key, sort_dir,
fields=fields)
@expose.expose(Node, body=Node, status_code=201)
def post(self, Node):
"""Create a new Node.
:param Node: a Node within the request body.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:node:create', cdict, cdict)
if not Node.name:
raise exception.MissingParameterValue(
("Name is not specified."))
if not Node.code:
raise exception.MissingParameterValue(
("Code is not specified."))
if not Node.location:
raise exception.MissingParameterValue(
("Location is not specified."))
if Node.name:
if not api_utils.is_valid_node_name(Node.name):
msg = ("Cannot create node with invalid name %(name)s")
raise wsme.exc.ClientSideError(msg % {'name': Node.name},
status_code=400)
new_Node = objects.Node(pecan.request.context,
**Node.as_dict())
new_Node.owner = pecan.request.context.user_id
new_Node.project = pecan.request.context.project_id
new_Location = objects.Location(pecan.request.context,
**Node.location[0].as_dict())
new_Node = pecan.request.rpcapi.create_node(pecan.request.context,
new_Node, new_Location)
return Node.convert_with_links(new_Node)
@expose.expose(None, types.uuid_or_name, status_code=204)
def delete(self, node_ident):
"""Delete a node.
:param node_ident: UUID or logical name of a node.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:node:delete', cdict, cdict)
rpc_node = api_utils.get_rpc_node(node_ident)
pecan.request.rpcapi.destroy_node(pecan.request.context,
rpc_node.uuid)
@expose.expose(Node, types.uuid_or_name, body=Node, status_code=200)
def patch(self, node_ident, val_Node):
"""Update a node.
:param node_ident: UUID or logical name of a node.
:param Node: values to be changed
:return updated_node: updated_node
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:node:update', cdict, cdict)
node = api_utils.get_rpc_node(node_ident)
val_Node = val_Node.as_dict()
for key in val_Node:
try:
node[key] = val_Node[key]
except Exception:
pass
updated_node = pecan.request.rpcapi.update_node(pecan.request.context,
node)
return Node.convert_with_links(updated_node)
@expose.expose(NodeCollection, types.uuid, int, wtypes.text,
wtypes.text, types.listtype, wtypes.text)
def detail(self, marker=None,
limit=None, sort_key='id', sort_dir='asc',
fields=None, project=None):
"""Retrieve a list of nodes.
:param marker: pagination marker for large data sets.
:param limit: maximum number of resources to return in a single result.
This value cannot be larger than the value of max_limit
in the [api] section of the ironic configuration, or only
max_limit resources will be returned.
:param sort_key: column to sort results by. Default: id.
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
:param project: Optional string value to get only nodes of the project.
:param fields: Optional, a list with a specified set of fields
of the resource to be returned.
"""
cdict = pecan.request.context.to_policy_values()
policy.authorize('iot:node:get', cdict, cdict)
# /detail should only work against collections
parent = pecan.request.path.split('/')[:-1][-1]
if parent != "nodes":
raise exception.HTTPNotFound()
return self._get_nodes_collection(marker,
limit, sort_key, sort_dir,
project=project,
fields=fields)

View File

@ -26,7 +26,7 @@ from pecan import rest
import wsme
from wsme import types as wtypes
_DEFAULT_RETURN_FIELDS = ('name', 'uuid', 'owner', 'public')
_DEFAULT_RETURN_FIELDS = ('name', 'uuid', 'owner', 'public', 'callable')
class Plugin(base.APIBase):
@ -35,9 +35,10 @@ class Plugin(base.APIBase):
"""
uuid = types.uuid
name = wsme.wsattr(wtypes.text)
config = wsme.wsattr(wtypes.text)
code = wsme.wsattr(wtypes.text)
public = types.boolean
owner = types.uuid
callable = types.boolean
links = wsme.wsattr([link.Link], readonly=True)
extra = types.jsontype
@ -254,24 +255,6 @@ class PluginsController(rest.RestController):
pecan.request.context, plugin)
return Plugin.convert_with_links(updated_plugin)
@expose.expose(None, types.uuid_or_name, types.uuid_or_name,
status_code=200)
def put(self, plugin_ident, node_ident):
"""inject a plugin into a node.
:param plugin_ident: UUID or logical name of a plugin.
:param node_ident: UUID or logical name of a node.
"""
context = pecan.request.context
cdict = context.to_policy_values()
policy.authorize('iot:plugin:inject', cdict, cdict)
rpc_plugin = api_utils.get_rpc_plugin(plugin_ident)
rpc_node = api_utils.get_rpc_node(node_ident)
pecan.request.rpcapi.inject_plugin(pecan.request.context,
rpc_plugin.uuid, rpc_node.uuid)
@expose.expose(PluginCollection, types.uuid, int, wtypes.text,
wtypes.text, types.listtype, types.boolean, types.boolean)
def detail(self, marker=None,

View File

@ -65,34 +65,34 @@ def get_patch_value(patch, path):
return p['value']
def allow_node_logical_names():
def allow_board_logical_names():
# v1.5 added logical name aliases
return pecan.request.version.minor >= 5
def get_rpc_node(node_ident):
"""Get the RPC node from the node uuid or logical name.
def get_rpc_board(board_ident):
"""Get the RPC board from the board uuid or logical name.
:param node_ident: the UUID or logical name of a node.
:param board_ident: the UUID or logical name of a board.
:returns: The RPC Node.
:returns: The RPC Board.
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
:raises: NodeNotFound if the node is not found.
:raises: BoardNotFound if the board is not found.
"""
# Check to see if the node_ident is a valid UUID. If it is, treat it
# Check to see if the board_ident is a valid UUID. If it is, treat it
# as a UUID.
if uuidutils.is_uuid_like(node_ident):
return objects.Node.get_by_uuid(pecan.request.context, node_ident)
if uuidutils.is_uuid_like(board_ident):
return objects.Board.get_by_uuid(pecan.request.context, board_ident)
# We can refer to nodes by their name, if the client supports it
# if allow_node_logical_names():
# if utils.is_hostname_safe(node_ident):
# We can refer to boards by their name, if the client supports it
# if allow_board_logical_names():
# if utils.is_hostname_safe(board_ident):
else:
return objects.Node.get_by_name(pecan.request.context, node_ident)
return objects.Board.get_by_name(pecan.request.context, board_ident)
raise exception.InvalidUuidOrName(name=node_ident)
raise exception.InvalidUuidOrName(name=board_ident)
raise exception.NodeNotFound(node=node_ident)
raise exception.BoardNotFound(board=board_ident)
def get_rpc_plugin(plugin_ident):
@ -120,12 +120,12 @@ def get_rpc_plugin(plugin_ident):
raise exception.PluginNotFound(plugin=plugin_ident)
def is_valid_node_name(name):
"""Determine if the provided name is a valid node name.
def is_valid_board_name(name):
"""Determine if the provided name is a valid board name.
Check to see that the provided node name is valid, and isn't a UUID.
Check to see that the provided board name is valid, and isn't a UUID.
:param: name: the node name to check.
:param: name: the board name to check.
:returns: True if the name is valid, False otherwise.
"""
return utils.is_hostname_safe(name) and (not uuidutils.is_uuid_like(name))
@ -134,9 +134,9 @@ def is_valid_node_name(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.
Check to see that the provided board name isn't a UUID.
:param: name: the node name to check.
:param: name: the board name to check.
:returns: True if the name is valid, False otherwise.
"""
return not uuidutils.is_uuid_like(name)

View File

@ -138,8 +138,8 @@ class InvalidState(Conflict):
message = _("Invalid resource state.")
class NodeAlreadyExists(Conflict):
message = _("A node with UUID %(uuid)s already exists.")
class BoardAlreadyExists(Conflict):
message = _("A board with UUID %(uuid)s already exists.")
class MACAlreadyExists(Conflict):
@ -150,17 +150,12 @@ class PortAlreadyExists(Conflict):
message = _("A port with UUID %(uuid)s already exists.")
class InstanceAssociated(Conflict):
message = _("Instance %(instance_uuid)s is already associated with a node,"
" it cannot be associated with this other node %(node)s")
class DuplicateName(Conflict):
message = _("A node with name %(name)s already exists.")
message = _("A board with name %(name)s already exists.")
class DuplicateCode(Conflict):
message = _("A node with code %(code)s already exists.")
message = _("A board with code %(code)s already exists.")
class InvalidUUID(Invalid):
@ -185,7 +180,7 @@ class InvalidMAC(Invalid):
class InvalidStateRequested(Invalid):
message = _('The requested action "%(action)s" can not be performed '
'on node "%(node)s" while it is in state "%(state)s".')
'on board "%(board)s" while it is in state "%(state)s".')
class PatchError(Invalid):
@ -244,16 +239,16 @@ class InstanceNotFound(NotFound):
message = _("Instance %(instance)s could not be found.")
class NodeNotFound(NotFound):
message = _("Node %(node)s could not be found.")
class BoardNotFound(NotFound):
message = _("Board %(board)s could not be found.")
class NodeNotConnected(Invalid):
message = _("Node %(node)s is not connected.")
class BoardNotConnected(Invalid):
message = _("Board %(board)s is not connected.")
class NodeAssociated(InvalidState):
message = _("Node %(node)s is associated with instance %(instance)s.")
class BoardAssociated(InvalidState):
message = _("Board %(board)s is associated with instance %(instance)s.")
class PortNotFound(NotFound):
@ -302,7 +297,7 @@ class WampAgentAlreadyRegistered(IotronicException):
class PowerStateFailure(InvalidState):
message = _("Failed to set node power state to %(pstate)s.")
message = _("Failed to set board power state to %(pstate)s.")
class ExclusiveLockRequired(NotAuthorized):
@ -310,18 +305,18 @@ class ExclusiveLockRequired(NotAuthorized):
"but the current context has a shared lock.")
class NodeMaintenanceFailure(Invalid):
class BoardMaintenanceFailure(Invalid):
message = _("Failed to toggle maintenance-mode flag "
"for node %(node)s: %(reason)s")
"for board %(board)s: %(reason)s")
class NodeConsoleNotEnabled(Invalid):
message = _("Console access is not enabled on node %(node)s")
class BoardConsoleNotEnabled(Invalid):
message = _("Console access is not enabled on board %(board)s")
class NodeInMaintenance(Invalid):
message = _("The %(op)s operation can't be performed on node "
"%(node)s because it's in maintenance mode.")
class BoardInMaintenance(Invalid):
message = _("The %(op)s operation can't be performed on board "
"%(board)s because it's in maintenance mode.")
class IPMIFailure(IotronicException):
@ -436,13 +431,13 @@ class ConfigNotFound(IotronicException):
message = _("Could not find config at %(path)s")
class NodeLocked(Conflict):
message = _("Node %(node)s is locked by host %(host)s, please retry "
class BoardLocked(Conflict):
message = _("Board %(board)s is locked by host %(host)s, please retry "
"after the current operation is completed.")
class NodeNotLocked(Invalid):
message = _("Node %(node)s found not to be locked on release")
class BoardNotLocked(Invalid):
message = _("Board %(board)s found not to be locked on release")
class NoFreeConductorWorker(TemporaryFailure):
@ -523,12 +518,12 @@ class DracInvalidFilterDialect(IotronicException):
class FailedToGetSensorData(IotronicException):
message = _("Failed to get sensor data for node %(node)s. "
message = _("Failed to get sensor data for board %(board)s. "
"Error: %(error)s")
class FailedToParseSensorData(IotronicException):
message = _("Failed to parse sensor data for node %(node)s. "
message = _("Failed to parse sensor data for board %(board)s. "
"Error: %(error)s")
@ -568,8 +563,8 @@ class HardwareInspectionFailure(IotronicException):
message = _("Failed to inspect hardware. Reason: %(error)s")
class NodeCleaningFailure(IotronicException):
message = _("Failed to clean node %(node)s: %(reason)s")
class BoardCleaningFailure(IotronicException):
message = _("Failed to clean board %(board)s: %(reason)s")
class PathNotFound(IotronicException):
@ -582,3 +577,15 @@ class DirectoryNotWritable(IotronicException):
class PluginNotFound(NotFound):
message = _("Plugin %(plugin)s could not be found.")
class InjectionPluginNotFound(NotFound):
message = _("InjectionPlugin could not be found.")
class InvalidPluginAction(Invalid):
message = _("Invalid Action %(action)s for the plugin.")
class NeedParams(Invalid):
message = _("Action %(action)s needs parameters.")

View File

@ -70,21 +70,21 @@ default_policies = [
# All of these may be overridden by configuration, but we can
# depend on their existence throughout the code.
node_policies = [
policy.RuleDefault('iot:node:get',
board_policies = [
policy.RuleDefault('iot:board:get',
'rule:is_admin or rule:is_iot_member',
description='Retrieve Node records'),
policy.RuleDefault('iot:node:create',
description='Retrieve Board records'),
policy.RuleDefault('iot:board:create',
'rule:is_admin_iot_project',
description='Create Node records'),
policy.RuleDefault('iot:node:delete',
description='Create Board records'),
policy.RuleDefault('iot:board:delete',
'rule:is_admin or rule:is_admin_iot_project '
'or rule:is_manager_iot_project',
description='Delete Node records'),
policy.RuleDefault('iot:node:update',
description='Delete Board records'),
policy.RuleDefault('iot:board:update',
'rule:is_admin or rule:is_admin_iot_project '
'or rule:is_manager_iot_project',
description='Update Node records'),
description='Update Board records'),
]
@ -111,7 +111,7 @@ plugin_policies = [
def list_policies():
policies = (default_policies
+ node_policies
+ board_policies
+ plugin_policies
)
return policies

View File

@ -37,9 +37,9 @@ service_opts = [
help='Seconds between running periodic tasks.'),
cfg.StrOpt('host',
default=socket.getfqdn(),
help='Name of this node. This can be an opaque identifier. '
help='Name of this board. This can be an opaque identifier. '
'It is not necessarily a hostname, FQDN, or IP address. '
'However, the node name must be valid within '
'However, the board name must be valid within '
'an AMQP key, and if using ZeroMQ, a valid '
'hostname, FQDN, or IP address.'),
]

View File

@ -1,5 +1,4 @@
# Copyright (c) 2012 NTT DOCOMO, INC.
# Copyright 2010 OpenStack Foundation
# Copyright 2017 MDSLAB - University of Messina
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -14,288 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
Mapping of bare metal node states.
Setting the node `power_state` is handled by the conductor's power
synchronization thread. Based on the power state retrieved from the driver
for the node, the state is set to POWER_ON or POWER_OFF, accordingly.
Should this fail, the `power_state` value is left unchanged, and the node
is placed into maintenance mode.
# from oslo_log import log as logging
The `power_state` can also be set manually via the API. A failure to change
the state leaves the current state unchanged. The node is NOT placed into
maintenance mode in this case.
"""
# LOG = logging.getLogger(__name__)
from oslo_log import log as logging
from iotronic.common import fsm
LOG = logging.getLogger(__name__)
#####################
# Provisioning states
#####################
# TODO(deva): add add'l state mappings here
VERBS = {
'active': 'deploy',
'deleted': 'delete',
'manage': 'manage',
'provide': 'provide',
'inspect': 'inspect',
}
""" Mapping of state-changing events that are PUT to the REST API
This is a mapping of target states which are PUT to the API, eg,
PUT /v1/node/states/provision {'target': 'active'}
The dict format is:
{target string used by the API: internal verb}
This provides a reference set of supported actions, and in the future
may be used to support renaming these actions.
"""
NOSTATE = None
""" No state information.
This state is used with power_state to represent a lack of knowledge of
power state, and in target_*_state fields when there is no target.
"""
MANAGEABLE = 'manageable'
""" Node is in a manageable state.
This state indicates that Iotronic has verified, at least once, that it had
sufficient information to manage the hardware. While in this state, the node
is not available for provisioning (it must be in the AVAILABLE state for that).
"""
AVAILABLE = 'available'
""" Node is available for use and scheduling.
This state is replacing the NOSTATE state used prior to Kilo.
"""
ACTIVE = 'active'
""" Node is successfully deployed and associated with an instance. """
DEPLOYWAIT = 'wait call-back'
""" Node is waiting to be deployed.
This will be the node `provision_state` while the node is waiting for
the driver to finish deployment.
"""
DEPLOYING = 'deploying'
""" Node is ready to receive a deploy request, or is currently being deployed.
A node will have its `provision_state` set to DEPLOYING briefly before it
receives its initial deploy request. It will also move to this state from
DEPLOYWAIT after the callback is triggered and deployment is continued
(disk partitioning and image copying).
"""
DEPLOYFAIL = 'deploy failed'
""" Node deployment failed. """
DEPLOYDONE = 'deploy complete'
""" Node was successfully deployed.
This is mainly a target provision state used during deployment. A successfully
deployed node should go to ACTIVE status.
"""
DELETING = 'deleting'
""" Node is actively being torn down. """
DELETED = 'deleted'
""" Node tear down was successful.
In Juno, target_provision_state was set to this value during node tear down.
In Kilo, this will be a transitory value of provision_state, and never
represented in target_provision_state.
"""
CLEANING = 'cleaning'
""" Node is being automatically cleaned to prepare it for provisioning. """
CLEANFAIL = 'clean failed'
""" Node failed cleaning. This requires operator intervention to resolve. """
ERROR = 'error'
""" An error occurred during node processing.
The `last_error` attribute of the node details should contain an error message.
"""
REBUILD = 'rebuild'
""" Node is to be rebuilt.
This is not used as a state, but rather as a "verb" when changing the node's
provision_state via the REST API.
"""
INSPECTING = 'inspecting'
""" Node is under inspection.
This is the provision state used when inspection is started. A successfully
inspected node shall transition to MANAGEABLE status.
"""
INSPECTFAIL = 'inspect failed'
""" Node inspection failed. """
UPDATE_ALLOWED_STATES = (DEPLOYFAIL, INSPECTING, INSPECTFAIL, CLEANFAIL)
"""Transitional states in which we allow updating a node."""
# NEW
OPERATIVE = 'operative'
MAINTENANCE = 'maintenance'
OFFLINE = 'offline'
REGISTERED = 'registered'
##############
# Power states
##############
POWER_ON = 'power on'
""" Node is powered on. """
POWER_OFF = 'power off'
""" Node is powered off. """
REBOOT = 'rebooting'
""" Node is rebooting. """
#####################
# State machine model
#####################
def on_exit(old_state, event):
"""Used to log when a state is exited."""
LOG.debug("Exiting old state '%s' in response to event '%s'",
old_state, event)
def on_enter(new_state, event):
"""Used to log when entering a state."""
LOG.debug("Entering new state '%s' in response to event '%s'",
new_state, event)
watchers = {}
watchers['on_exit'] = on_exit
watchers['on_enter'] = on_enter
machine = fsm.FSM()
# Add stable states
machine.add_state(MANAGEABLE, stable=True, **watchers)
machine.add_state(AVAILABLE, stable=True, **watchers)
machine.add_state(ACTIVE, stable=True, **watchers)
machine.add_state(ERROR, stable=True, **watchers)
# Add deploy* states
# NOTE(deva): Juno shows a target_provision_state of DEPLOYDONE
# this is changed in Kilo to ACTIVE
machine.add_state(DEPLOYING, target=ACTIVE, **watchers)
machine.add_state(DEPLOYWAIT, target=ACTIVE, **watchers)
machine.add_state(DEPLOYFAIL, target=ACTIVE, **watchers)
# Add clean* states
machine.add_state(CLEANING, target=AVAILABLE, **watchers)
machine.add_state(CLEANFAIL, target=AVAILABLE, **watchers)
# Add delete* states
machine.add_state(DELETING, target=AVAILABLE, **watchers)
# From AVAILABLE, a deployment may be started
machine.add_transition(AVAILABLE, DEPLOYING, 'deploy')
# Add inspect* states.
machine.add_state(INSPECTING, target=MANAGEABLE, **watchers)
machine.add_state(INSPECTFAIL, target=MANAGEABLE, **watchers)
# A deployment may fail
machine.add_transition(DEPLOYING, DEPLOYFAIL, 'fail')
# A failed deployment may be retried
# iotronic/conductor/manager.py:do_node_deploy()
machine.add_transition(DEPLOYFAIL, DEPLOYING, 'rebuild')
# NOTE(deva): Juno allows a client to send "active" to initiate a rebuild
machine.add_transition(DEPLOYFAIL, DEPLOYING, 'deploy')
# A deployment may also wait on external callbacks
machine.add_transition(DEPLOYING, DEPLOYWAIT, 'wait')
machine.add_transition(DEPLOYWAIT, DEPLOYING, 'resume')
# A deployment waiting on callback may time out
machine.add_transition(DEPLOYWAIT, DEPLOYFAIL, 'fail')
# A deployment may complete
machine.add_transition(DEPLOYING, ACTIVE, 'done')
# An active instance may be re-deployed
# iotronic/conductor/manager.py:do_node_deploy()
machine.add_transition(ACTIVE, DEPLOYING, 'rebuild')
# An active instance may be deleted
# iotronic/conductor/manager.py:do_node_tear_down()
machine.add_transition(ACTIVE, DELETING, 'delete')
# While a deployment is waiting, it may be deleted
# iotronic/conductor/manager.py:do_node_tear_down()
machine.add_transition(DEPLOYWAIT, DELETING, 'delete')
# A failed deployment may also be deleted
# iotronic/conductor/manager.py:do_node_tear_down()
machine.add_transition(DEPLOYFAIL, DELETING, 'delete')
# This state can also transition to error
machine.add_transition(DELETING, ERROR, 'error')
# When finished deleting, a node will begin cleaning
machine.add_transition(DELETING, CLEANING, 'clean')
# If cleaning succeeds, it becomes available for scheduling
machine.add_transition(CLEANING, AVAILABLE, 'done')
# If cleaning fails, wait for operator intervention
machine.add_transition(CLEANING, CLEANFAIL, 'fail')
# An operator may want to move a CLEANFAIL node to MANAGEABLE, to perform
# other actions like zapping
machine.add_transition(CLEANFAIL, MANAGEABLE, 'manage')
# From MANAGEABLE, a node may move to available after going through cleaning
machine.add_transition(MANAGEABLE, CLEANING, 'provide')
# From AVAILABLE, a node may be made unavailable by managing it
machine.add_transition(AVAILABLE, MANAGEABLE, 'manage')
# An errored instance can be rebuilt
# iotronic/conductor/manager.py:do_node_deploy()
machine.add_transition(ERROR, DEPLOYING, 'rebuild')
# or deleted
# iotronic/conductor/manager.py:do_node_tear_down()
machine.add_transition(ERROR, DELETING, 'delete')
# Added transitions for inspection.
# Initiate inspection.
machine.add_transition(MANAGEABLE, INSPECTING, 'inspect')
# iotronic/conductor/manager.py:inspect_hardware().
machine.add_transition(INSPECTING, MANAGEABLE, 'done')
# Inspection may fail.
machine.add_transition(INSPECTING, INSPECTFAIL, 'fail')
# Move the node to manageable state for any other
# action.
machine.add_transition(INSPECTFAIL, MANAGEABLE, 'manage')
# Reinitiate the inspect after inspectfail.
machine.add_transition(INSPECTFAIL, INSPECTING, 'inspect')
ONLINE = 'online'

View File

@ -596,7 +596,7 @@ def is_valid_logical_name(hostname):
def is_hostname_safe(hostname):
"""Old check for valid logical node names.
"""Old check for valid logical board names.
Retained for compatibility with REST API < 1.10.

View File

@ -22,7 +22,6 @@ from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
import random
LOG = logging.getLogger(__name__)
@ -55,131 +54,138 @@ class ConductorEndpoint(object):
LOG.debug('Received registration from %s with session %s',
uuid, session_num)
try:
node = objects.Node.get_by_uuid(ctx, uuid)
board = objects.Board.get_by_uuid(ctx, uuid)
except Exception as exc:
msg = exc.message % {'node': uuid}
msg = exc.message % {'board': uuid}
LOG.error(msg)
wmessage = wm.WampError(msg).serialize()
return wmessage
return wm.WampError(msg).serialize()
try:
old_ses = objects.SessionWP(ctx)
old_ses = old_ses.get_session_by_node_uuid(ctx, node.uuid,
valid=True)
old_ses = old_ses.get_session_by_board_uuid(ctx, board.uuid,
valid=True)
old_ses.valid = False
old_ses.save()
except Exception:
LOG.debug('valid session for %s not found', node.uuid)
LOG.debug('valid session for %s not found', board.uuid)
node.status = states.REGISTERED
node.save()
session = objects.SessionWP(ctx)
session.node_id = node.id
session.node_uuid = node.uuid
session.session_id = session_num
session_data = {'board_id': board.id,
'board_uuid': board.uuid,
'session_id': session_num}
session = objects.SessionWP(ctx, **session_data)
session.create()
session.save()
board.status = states.ONLINE
board.save()
LOG.debug('Board %s is now %s', board.uuid, states.ONLINE)
return wm.WampSuccess('').serialize()
def registration(self, ctx, code, session_num):
LOG.debug('Received registration from %s with session %s',
code, session_num)
try:
node = objects.Node.get_by_code(ctx, code)
board = objects.Board.get_by_code(ctx, code)
except Exception as exc:
msg = exc.message % {'node': code}
msg = exc.message % {'board': code}
LOG.error(msg)
wmessage = wm.WampError(msg).serialize()
return wmessage
return wm.WampError(msg).serialize()
try:
old_ses = objects.SessionWP(ctx)
old_ses = old_ses.get_session_by_node_uuid(ctx, node.uuid,
valid=True)
old_ses = old_ses.get_session_by_board_uuid(ctx, board.uuid,
valid=True)
old_ses.valid = False
old_ses.save()
except Exception:
LOG.debug('valid session for %s not found', node.uuid)
LOG.debug('valid session for %s not found', board.uuid)
session = objects.SessionWP(ctx)
session.node_id = node.id
session.node_uuid = node.uuid
session.session_id = session_num
session_data = {'board_id': board.id,
'board_uuid': board.uuid,
'session_id': session_num}
session = objects.SessionWP(ctx, **session_data)
session.create()
session.save()
node.agent = get_best_agent(ctx)
agent = objects.WampAgent.get_by_hostname(ctx, node.agent)
board.agent = get_best_agent(ctx)
agent = objects.WampAgent.get_by_hostname(ctx, board.agent)
prov = Provisioner(node)
prov = Provisioner(board)
prov.conf_registration_agent(self.ragent.wsurl)
prov.conf_main_agent(agent.wsurl)
node.config = prov.get_config()
node.save()
board.config = prov.get_config()
board.status = states.OFFLINE
board.save()
LOG.debug('sending this conf %s', node.config)
LOG.debug('sending this conf %s', board.config)
wmessage = wm.WampSuccess(node.config)
wmessage = wm.WampSuccess(board.config)
return wmessage.serialize()
def destroy_node(self, ctx, node_id):
LOG.info('Destroying node with id %s',
node_id)
node = objects.Node.get_by_uuid(ctx, node_id)
def destroy_board(self, ctx, board_id):
LOG.info('Destroying board with id %s',
board_id)
board = objects.Board.get_by_uuid(ctx, board_id)
prov = Provisioner()
prov.conf_clean()
p = prov.get_config()
LOG.debug('sending this conf %s', p)
try:
self.execute_on_node(ctx, node_id, 'destroyNode', (p,))
result = self.execute_on_board(ctx, board_id, 'destroyBoard', (p,))
except Exception:
LOG.error('cannot execute remote destroynode on %s. '
'Maybe it is OFFLINE', node_id)
node.destroy()
LOG.error('cannot execute remote destroyboard on %s. '
'Maybe it is OFFLINE', board_id)
board.destroy()
if result:
LOG.debug(result)
return result
return
def update_node(self, ctx, node_obj):
node = serializer.deserialize_entity(ctx, node_obj)
LOG.debug('Updating node %s', node.name)
node.save()
return serializer.serialize_entity(ctx, node)
def update_board(self, ctx, board_obj):
board = serializer.deserialize_entity(ctx, board_obj)
LOG.debug('Updating board %s', board.name)
board.save()
return serializer.serialize_entity(ctx, board)
def create_node(self, ctx, node_obj, location_obj):
new_node = serializer.deserialize_entity(ctx, node_obj)
LOG.debug('Creating node %s',
new_node.name)
def create_board(self, ctx, board_obj, location_obj):
new_board = serializer.deserialize_entity(ctx, board_obj)
LOG.debug('Creating board %s',
new_board.name)
new_location = serializer.deserialize_entity(ctx, location_obj)
new_node.create()
new_location.node_id = new_node.id
new_board.create()
new_location.board_id = new_board.id
new_location.create()
return serializer.serialize_entity(ctx, new_node)
return serializer.serialize_entity(ctx, new_board)
def execute_on_node(self, ctx, node_uuid, wamp_rpc_call, wamp_rpc_args):
LOG.debug('Executing \"%s\" on the node: %s', wamp_rpc_call, node_uuid)
def execute_on_board(self, ctx, board_uuid, wamp_rpc_call, wamp_rpc_args):
LOG.debug('Executing \"%s\" on the board: %s',
wamp_rpc_call, board_uuid)
node = objects.Node.get_by_uuid(ctx, node_uuid)
board = objects.Board.get_by_uuid(ctx, board_uuid)
# check the session; it rise an excpetion if session miss
# session = objects.SessionWP.get_session_by_node_uuid(node_uuid)
objects.SessionWP.get_session_by_board_uuid(ctx, board_uuid)
s4t_topic = 's4t_invoke_wamp'
full_topic = node.agent + '.' + s4t_topic
full_topic = board.agent + '.' + s4t_topic
self.target.topic = full_topic
full_wamp_call = 'iotronic.' + node.uuid + "." + wamp_rpc_call
full_wamp_call = 'iotronic.' + board.uuid + "." + wamp_rpc_call
return self.wamp_agent_client.call(ctx, full_topic,
wamp_rpc_call=full_wamp_call,
data=wamp_rpc_args)
res = self.wamp_agent_client.call(ctx, full_topic,
wamp_rpc_call=full_wamp_call,
data=wamp_rpc_args)
res = wm.deserialize(res)
if res.result == wm.SUCCESS:
return res.message
elif res.result == wm.ERROR:
raise Exception
def destroy_plugin(self, ctx, plugin_id):
LOG.info('Destroying plugin with id %s',
@ -198,19 +204,80 @@ class ConductorEndpoint(object):
new_plugin = serializer.deserialize_entity(ctx, plugin_obj)
LOG.debug('Creating plugin %s',
new_plugin.name)
new_plugin.config = cpickle.dumps(new_plugin.config, 0)
new_plugin.code = cpickle.dumps(new_plugin.code, 0)
new_plugin.create()
return serializer.serialize_entity(ctx, new_plugin)
def inject_plugin(self, ctx, plugin_uuid, node_uuid):
LOG.info('Injecting plugin with id %s into the node %s',
plugin_uuid, node_uuid)
def inject_plugin(self, ctx, plugin_uuid, board_uuid, onboot):
LOG.info('Injecting plugin with id %s into the board %s',
plugin_uuid, board_uuid)
plugin = objects.Plugin.get(ctx, plugin_uuid)
result = self.execute_on_board(ctx,
board_uuid,
'PluginInject',
(plugin, onboot))
injection = None
try:
injection = objects.InjectionPlugin.get(ctx,
board_uuid,
plugin_uuid)
except Exception:
pass
if injection:
injection.status = 'updated'
injection.save()
else:
inj_data = {
'board_uuid': board_uuid,
'plugin_uuid': plugin_uuid,
'onboot': onboot,
'status': 'injected'
}
injection = objects.InjectionPlugin(ctx, **inj_data)
injection.create()
LOG.debug(result)
return result
def remove_plugin(self, ctx, plugin_uuid, board_uuid):
LOG.info('Removing plugin with id %s into the board %s',
plugin_uuid, board_uuid)
plugin = objects.Plugin.get_by_uuid(ctx, plugin_uuid)
injection = objects.InjectionPlugin.get(ctx, board_uuid, plugin_uuid)
try:
self.execute_on_node(ctx, node_uuid, 'PluginInject',
(plugin.name, plugin.config))
result = self.execute_on_board(ctx, board_uuid, 'PluginRemove',
(plugin.uuid,))
except Exception:
LOG.error('cannot execute remote injection on %s. '
'Maybe it is OFFLINE', node_uuid)
return
LOG.error('cannot execute a plugin remove on %s. ', Exception)
return Exception
LOG.debug(result)
injection.destroy()
return result
def action_plugin(self, ctx, plugin_uuid, board_uuid, action, params):
LOG.info('Calling plugin with id %s into the board %s',
plugin_uuid, board_uuid)
plugin = objects.Plugin.get(ctx, plugin_uuid)
objects.plugin.is_valid_action(action)
try:
if objects.plugin.want_params(action):
result = self.execute_on_board(ctx, board_uuid, action,
(plugin.uuid, params))
else:
result = self.execute_on_board(ctx, board_uuid, action,
(plugin.uuid,))
except Exception:
LOG.error('cannot execute a plugin remove on %s. ', Exception)
return Exception
LOG.debug(result)
return result

View File

@ -18,23 +18,23 @@ serializer = objects_base.IotronicObjectSerializer()
class Provisioner(object):
def __init__(self, node=None):
if not node:
def __init__(self, board=None):
if not board:
self.config = {"iotronic": {"extra": {}}}
else:
self.config = node.config
self.config = board.config
if 'iotronic' not in self.config:
self.config = {"iotronic": {"extra": {}}}
if 'node' not in self.config['iotronic']:
self.config['iotronic']['node'] = {}
self.config['iotronic']['node'] = node.as_dict()
self.config['iotronic']['node']['created_at'] = \
node._attr_to_primitive('created_at')
self.config['iotronic']['node']['updated_at'] = \
node._attr_to_primitive('updated_at')
if 'board' not in self.config['iotronic']:
self.config['iotronic']['board'] = {}
self.config['iotronic']['board'] = board.as_dict()
self.config['iotronic']['board']['created_at'] = \
board._attr_to_primitive('created_at')
self.config['iotronic']['board']['updated_at'] = \
board._attr_to_primitive('updated_at')
try:
del self.config['iotronic']['node']['config']
del self.config['iotronic']['board']['config']
except Exception:
pass
@ -72,6 +72,6 @@ class Provisioner(object):
def conf_clean(self):
self.conf_registration_agent()
if 'node' not in self.config['iotronic']:
self.config['iotronic']['node'] = {}
self.config['iotronic']['node']['token'] = "<REGISTRATION-TOKEN>"
if 'board' not in self.config['iotronic']:
self.config['iotronic']['board'] = {}
self.config['iotronic']['board']['token'] = "<REGISTRATION-TOKEN>"

View File

@ -46,14 +46,14 @@ class ConductorAPI(object):
"""Test
:param context: request context.
:param data: node id or uuid.
:param data: board id or uuid.
:param topic: RPC topic. Defaults to self.topic.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'echo', data=data)
def registration(self, context, code, session_num, topic=None):
"""Registration of a node.
"""Registration of a board.
:param context: request context.
:param code: token used for the first registration
@ -65,10 +65,10 @@ class ConductorAPI(object):
code=code, session_num=session_num)
def connection(self, context, uuid, session_num, topic=None):
"""Connection of a node.
"""Connection of a board.
:param context: request context.
:param uuid: uuid node
:param uuid: uuid board
:param session_num: wamp session number
:param topic: RPC topic. Defaults to self.topic.
"""
@ -76,54 +76,55 @@ class ConductorAPI(object):
return cctxt.call(context, 'connection',
uuid=uuid, session_num=session_num)
def create_node(self, context, node_obj, location_obj, topic=None):
"""Add a node on the cloud
def create_board(self, context, board_obj, location_obj, topic=None):
"""Add a board on the cloud
:param context: request context.
:param node_obj: a changed (but not saved) node object.
:param board_obj: a changed (but not saved) board object.
:param topic: RPC topic. Defaults to self.topic.
:returns: created node object
:returns: created board object
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'create_node',
node_obj=node_obj, location_obj=location_obj)
return cctxt.call(context, 'create_board',
board_obj=board_obj, location_obj=location_obj)
def update_node(self, context, node_obj, topic=None):
"""Synchronously, have a conductor update the node's information.
def update_board(self, context, board_obj, topic=None):
"""Synchronously, have a conductor update the board's information.
Update the node's information in the database and return a node object.
Update the board's information in the database and return
a board object.
Note that power_state should not be passed via this method.
Use change_node_power_state for initiating driver actions.
Use change_board_power_state for initiating driver actions.
:param context: request context.
:param node_obj: a changed (but not saved) node object.
:param board_obj: a changed (but not saved) board object.
:param topic: RPC topic. Defaults to self.topic.
:returns: updated node object, including all fields.
:returns: updated board object, including all fields.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'update_node', node_obj=node_obj)
return cctxt.call(context, 'update_board', board_obj=board_obj)
def destroy_node(self, context, node_id, topic=None):
"""Delete a node.
def destroy_board(self, context, board_id, topic=None):
"""Delete a board.
:param context: request context.
:param node_id: node id or uuid.
:raises: NodeLocked if node is locked by another conductor.
:raises: NodeAssociated if the node contains an instance
:param board_id: board id or uuid.
:raises: BoardLocked if board is locked by another conductor.
:raises: BoardAssociated if the board contains an instance
associated with it.
:raises: InvalidState if the node is in the wrong provision
:raises: InvalidState if the board 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_node', node_id=node_id)
return cctxt.call(context, 'destroy_board', board_id=board_id)
def execute_on_node(self, context, node_uuid, wamp_rpc_call,
wamp_rpc_args=None, topic=None):
def execute_on_board(self, context, board_uuid, wamp_rpc_call,
wamp_rpc_args=None, topic=None):
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'execute_on_node', node_uuid=node_uuid,
return cctxt.call(context, 'execute_on_board', board_uuid=board_uuid,
wamp_rpc_call=wamp_rpc_call,
wamp_rpc_args=wamp_rpc_args)
@ -169,14 +170,40 @@ class ConductorAPI(object):
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'destroy_plugin', plugin_id=plugin_id)
def inject_plugin(self, context, plugin_uuid, node_uuid, topic=None):
"""inject a plugin into a node.
def inject_plugin(self, context, plugin_uuid,
board_uuid, onboot=False, topic=None):
"""inject a plugin into a board.
:param context: request context.
:param plugin_uuid: plugin id or uuid.
:param ndoe_uuid: node id or uuid.
:param board_uuid: board id or uuid.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'inject_plugin', plugin_uuid=plugin_uuid,
node_uuid=node_uuid)
board_uuid=board_uuid, onboot=onboot)
def remove_plugin(self, context, plugin_uuid, board_uuid, topic=None):
"""inject a plugin into a board.
:param context: request context.
:param plugin_uuid: plugin id or uuid.
:param board_uuid: board id or uuid.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'remove_plugin', plugin_uuid=plugin_uuid,
board_uuid=board_uuid)
def action_plugin(self, context, plugin_uuid,
board_uuid, action, params=None, topic=None):
"""Action on a plugin into a board.
:param context: request context.
:param plugin_uuid: plugin id or uuid.
:param board_uuid: board id or uuid.
"""
cctxt = self.client.prepare(topic=topic or self.topic, version='1.0')
return cctxt.call(context, 'action_plugin', plugin_uuid=plugin_uuid,
board_uuid=board_uuid, action=action, params=params)

View File

@ -43,11 +43,11 @@ class Connection(object):
"""Constructor."""
@abc.abstractmethod
def get_nodeinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching nodes.
def get_boardinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
"""Get specific columns for matching boards.
Return a list of the specified columns for all nodes that match the
Return a list of the specified columns for all boards that match the
specified filters.
:param columns: List of column names to return.
@ -57,11 +57,11 @@ class Connection(object):
:associated: True | False
:reserved: True | False
:maintenance: True | False
:provision_state: provision state of node
:provision_state: provision state of board
:provisioned_before:
nodes with provision_updated_at field before this
boards with provision_updated_at field before this
interval in seconds
:param limit: Maximum number of nodes to return.
:param limit: Maximum number of boards 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.
@ -71,20 +71,20 @@ class Connection(object):
"""
@abc.abstractmethod
def get_node_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of nodes.
def get_board_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of boards.
:param filters: Filters to apply. Defaults to None.
:associated: True | False
:reserved: True | False
:maintenance: True | False
:provision_state: provision state of node
:provision_state: provision state of board
:provisioned_before:
nodes with provision_updated_at field before this
boards with provision_updated_at field before this
interval in seconds
:param limit: Maximum number of nodes to return.
:param limit: Maximum number of boards 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.
@ -93,12 +93,12 @@ class Connection(object):
"""
@abc.abstractmethod
def create_node(self, values):
"""Create a new node.
def create_board(self, values):
"""Create a new board.
:param values: A dict containing several items used to identify
and track the node, and several dicts which are passed
into the Drivers when managing this node. For example:
and track the board, and several dicts which are passed
into the Drivers when managing this board. For example:
::
@ -110,65 +110,65 @@ class Connection(object):
'properties': { ... },
'extra': { ... },
}
:returns: A node.
:returns: A board.
"""
@abc.abstractmethod
def get_node_by_id(self, node_id):
"""Return a node.
def get_board_by_id(self, board_id):
"""Return a board.
:param node_id: The id of a node.
:returns: A node.
:param board_id: The id of a board.
:returns: A board.
"""
@abc.abstractmethod
def get_node_by_uuid(self, node_uuid):
"""Return a node.
def get_board_by_uuid(self, board_uuid):
"""Return a board.
:param node_uuid: The uuid of a node.
:returns: A node.
:param board_uuid: The uuid of a board.
:returns: A board.
"""
@abc.abstractmethod
def get_node_id_by_uuid(self, node_uuid):
"""Return a node id.
def get_board_id_by_uuid(self, board_uuid):
"""Return a board id.
:param node_uuid: The uuid of a node.
# :returns: A node.id.
:param board_uuid: The uuid of a board.
# :returns: A board.id.
"""
@abc.abstractmethod
def get_node_by_name(self, node_name):
"""Return a node.
def get_board_by_name(self, board_name):
"""Return a board.
:param node_name: The logical name of a node.
:returns: A node.
:param board_name: The logical name of a board.
:returns: A board.
"""
@abc.abstractmethod
def get_node_by_code(self, instance):
"""Return a node.
def get_board_by_code(self, instance):
"""Return a board.
:param instance: The instance code or uuid to search for.
:returns: A node.
:returns: A board.
"""
@abc.abstractmethod
def destroy_node(self, node_id):
"""Destroy a node and all associated interfaces.
def destroy_board(self, board_id):
"""Destroy a board and all associated interfaces.
:param node_id: The id or uuid of a node.
:param board_id: The id or uuid of a board.
"""
@abc.abstractmethod
def update_node(self, node_id, values):
"""Update properties of a node.
def update_board(self, board_id, values):
"""Update properties of a board.
:param node_id: The id or uuid of a node.
:param board_id: The id or uuid of a board.
:param values: Dict of values to update.
:returns: A node.
:raises: NodeAssociated
:raises: NodeNotFound
:returns: A board.
:raises: BoardAssociated
:raises: BoardNotFound
"""
@abc.abstractmethod
@ -213,10 +213,10 @@ class Connection(object):
"""
@abc.abstractmethod
def get_session_by_node_uuid(self, node_uuid, valid):
"""Return a Wamp session of a Node
def get_session_by_board_uuid(self, board_uuid, valid):
"""Return a Wamp session of a Board
:param node_uuid: Filters to apply. Defaults to None.
:param board_uuid: Filters to apply. Defaults to None.
:param valid: is valid
:returns: A session.
"""
@ -253,11 +253,11 @@ class Connection(object):
"""
@abc.abstractmethod
def get_locations_by_node_id(self, node_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""List all the locations for a given node.
def get_locations_by_board_id(self, board_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""List all the locations for a given board.
:param node_id: The integer node ID.
:param board_id: The integer board ID.
:param limit: Maximum number of locations to return.
:param marker: the last item of the previous page; we return the next
result set.
@ -364,3 +364,58 @@ class Connection(object):
:raises: PluginAssociated
:raises: PluginNotFound
"""
@abc.abstractmethod
def get_injection_plugin_by_board_uuid(self, board_uuid):
"""get an injection of a plugin using a board_uuid
:param board_uuid: The id or uuid of a board.
:returns: An injection_plugin.
"""
@abc.abstractmethod
def get_injection_plugin_by_uuids(self, board_uuid, plugin_uuid):
"""get an injection of a plugin using a board_uuid and plugin_uuid
:param board_uuid: The id or uuid of a board.
:param plugin_uuid: The id or uuid of a plugin.
:returns: An injection_plugin.
"""
@abc.abstractmethod
def create_injection_plugin(self, values):
"""Create a new injection_plugin.
:param values: A dict containing several items used to identify
and track the plugin
:returns: An injection plugin.
"""
@abc.abstractmethod
def destroy_injection_plugin(self, injection_plugin_id):
"""Destroy an injection plugin and all associated interfaces.
:param injection_plugin_id: The id or uuid of a plugin.
"""
@abc.abstractmethod
def update_injection_plugin(self, plugin_injection_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
"""
@abc.abstractmethod
def get_injection_plugin_list(self, board_uuid):
"""Return a list of injection_plugins.
:param board_uuid: The id or uuid of a plugin.
:returns: A list of InjectionPlugins on the board.
"""

View File

@ -26,14 +26,12 @@ from oslo_utils import uuidutils
from sqlalchemy import or_
from sqlalchemy.orm.exc import NoResultFound
from iotronic.common import exception
from iotronic.common.i18n import _
from iotronic.common import states
from iotronic.db import api
from iotronic.db.sqlalchemy import models
CONF = cfg.CONF
CONF.import_opt('heartbeat_timeout',
'iotronic.conductor.manager',
@ -117,20 +115,20 @@ class Connection(api.Connection):
def __init__(self):
pass
def _add_location_filter_by_node(self, query, value):
def _add_location_filter_by_board(self, query, value):
if strutils.is_int_like(value):
return query.filter_by(node_id=value)
return query.filter_by(board_id=value)
else:
query = query.join(models.Node,
models.Location.node_id == models.Node.id)
return query.filter(models.Node.uuid == value)
query = query.join(models.Board,
models.Location.board_id == models.Board.id)
return query.filter(models.Board.uuid == value)
def _add_nodes_filters(self, query, filters):
def _add_boards_filters(self, query, filters):
if filters is None:
filters = []
if 'project_id' in filters:
query = query.filter(models.Node.project == filters['project_id'])
query = query.filter(models.Board.project == filters['project_id'])
return query
@ -168,139 +166,157 @@ class Connection(api.Connection):
return query
def _do_update_node(self, node_id, values):
def _do_update_board(self, board_id, values):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
query = model_query(models.Board, session=session)
query = add_identity_filter(query, board_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)
raise exception.BoardNotFound(board=board_id)
ref.update(values)
return ref
# NODE api
def _do_update_plugin(self, plugin_id, values):
session = get_session()
with session.begin():
query = model_query(models.Plugin, session=session)
query = add_identity_filter(query, plugin_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.PluginNotFound(plugin=plugin_id)
def get_nodeinfo_list(self, columns=None, filters=None, limit=None,
marker=None, sort_key=None, sort_dir=None):
ref.update(values)
return ref
def _do_update_injection_plugin(self, injection_plugin_id, values):
session = get_session()
with session.begin():
query = model_query(models.InjectionPlugin, session=session)
query = add_identity_filter(query, injection_plugin_id)
try:
ref = query.with_lockmode('update').one()
except NoResultFound:
raise exception.InjectionPluginNotFound(
injection_plugin=injection_plugin_id)
ref.update(values)
return ref
# BOARD api
def get_boardinfo_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
# to include a mutable list in function definitions.
if columns is None:
columns = [models.Node.id]
columns = [models.Board.id]
else:
columns = [getattr(models.Node, c) for c in columns]
columns = [getattr(models.Board, c) for c in columns]
query = model_query(*columns, base_model=models.Node)
query = self._add_nodes_filters(query, filters)
return _paginate_query(models.Node, limit, marker,
query = model_query(*columns, base_model=models.Board)
query = self._add_boards_filters(query, filters)
return _paginate_query(models.Board, limit, marker,
sort_key, sort_dir, query)
def get_node_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Node)
query = self._add_nodes_filters(query, filters)
return _paginate_query(models.Node, limit, marker,
def get_board_list(self, filters=None, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Board)
query = self._add_boards_filters(query, filters)
return _paginate_query(models.Board, limit, marker,
sort_key, sort_dir, query)
def create_node(self, values):
# ensure defaults are present for new nodes
def create_board(self, values):
# ensure defaults are present for new boards
if 'uuid' not in values:
values['uuid'] = uuidutils.generate_uuid()
if 'status' not in values:
values['status'] = states.REGISTERED
node = models.Node()
node.update(values)
board = models.Board()
board.update(values)
try:
node.save()
board.save()
except db_exc.DBDuplicateEntry as exc:
if 'code' in exc.columns:
raise exception.DuplicateCode(code=values['code'])
raise exception.NodeAlreadyExists(uuid=values['uuid'])
return node
raise exception.BoardAlreadyExists(uuid=values['uuid'])
return board
def get_node_by_id(self, node_id):
query = model_query(models.Node).filter_by(id=node_id)
def get_board_by_id(self, board_id):
query = model_query(models.Board).filter_by(id=board_id)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
raise exception.BoardNotFound(board=board_id)
def get_node_id_by_uuid(self, node_uuid):
query = model_query(models.Node.id).filter_by(uuid=node_uuid)
def get_board_id_by_uuid(self, board_uuid):
query = model_query(models.Board.id).filter_by(uuid=board_uuid)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_uuid)
raise exception.BoardNotFound(board=board_uuid)
def get_node_by_uuid(self, node_uuid):
query = model_query(models.Node).filter_by(uuid=node_uuid)
def get_board_by_uuid(self, board_uuid):
query = model_query(models.Board).filter_by(uuid=board_uuid)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_uuid)
raise exception.BoardNotFound(board=board_uuid)
def get_node_by_name(self, node_name):
query = model_query(models.Node).filter_by(name=node_name)
def get_board_by_name(self, board_name):
query = model_query(models.Board).filter_by(name=board_name)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_name)
raise exception.BoardNotFound(board=board_name)
def get_node_by_code(self, node_code):
query = model_query(models.Node).filter_by(code=node_code)
def get_board_by_code(self, board_code):
query = model_query(models.Board).filter_by(code=board_code)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_code)
raise exception.BoardNotFound(board=board_code)
def destroy_node(self, node_id):
def destroy_board(self, board_id):
session = get_session()
with session.begin():
query = model_query(models.Node, session=session)
query = add_identity_filter(query, node_id)
query = model_query(models.Board, session=session)
query = add_identity_filter(query, board_id)
try:
node_ref = query.one()
board_ref = query.one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
raise exception.BoardNotFound(board=board_id)
# Get node ID, if an UUID was supplied. The ID is
# required for deleting all ports, attached to the node.
if uuidutils.is_uuid_like(node_id):
node_id = node_ref['id']
# Get board ID, if an UUID was supplied. The ID is
# required for deleting all ports, attached to the board.
if uuidutils.is_uuid_like(board_id):
board_id = board_ref['id']
location_query = model_query(models.Location, session=session)
location_query = self._add_location_filter_by_node(
location_query, node_id)
location_query = self._add_location_filter_by_board(
location_query, board_id)
location_query.delete()
query.delete()
def update_node(self, node_id, values):
def update_board(self, board_id, values):
# NOTE(dtantsur): this can lead to very strange errors
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Node.")
msg = _("Cannot overwrite UUID for an existing Board.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_node(node_id, values)
return self._do_update_board(board_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.NodeAlreadyExists(uuid=values['uuid'])
elif 'instance_uuid' in e.columns:
raise exception.InstanceAssociated(
instance_uuid=values['instance_uuid'],
node=node_id)
raise exception.BoardAlreadyExists(uuid=values['uuid'])
else:
raise e
@ -385,10 +401,10 @@ class Connection(api.Connection):
if count == 0:
raise exception.LocationNotFound(location=location_id)
def get_locations_by_node_id(self, node_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
def get_locations_by_board_id(self, board_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
query = model_query(models.Location)
query = query.filter_by(node_id=node_id)
query = query.filter_by(board_id=board_id)
return _paginate_query(models.Location, limit, marker,
sort_key, sort_dir, query)
@ -413,15 +429,15 @@ class Connection(api.Connection):
raise exception.SessionWPNotFound(ses=ses_id)
return ref
def get_session_by_node_uuid(self, node_uuid, valid):
def get_session_by_board_uuid(self, board_uuid, valid):
query = model_query(
models.SessionWP).filter_by(
node_uuid=node_uuid).filter_by(
board_uuid=board_uuid).filter_by(
valid=valid)
try:
return query.one()
except NoResultFound:
raise exception.NodeNotConnected(node=node_uuid)
raise exception.BoardNotConnected(board=board_uuid)
def get_session_by_id(self, session_id):
query = model_query(models.SessionWP).filter_by(session_id=session_id)
@ -550,10 +566,6 @@ class Connection(api.Connection):
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
@ -575,3 +587,71 @@ class Connection(api.Connection):
query = self._add_plugins_filters(query, filters)
return _paginate_query(models.Plugin, limit, marker,
sort_key, sort_dir, query)
# INJECTION PLUGIN api
def get_injection_plugin_by_board_uuid(self, board_uuid):
query = model_query(
models.InjectionPlugin).filter_by(
board_uuid=board_uuid)
try:
return query.one()
except NoResultFound:
raise exception.InjectionPluginNotFound()
def create_injection_plugin(self, values):
# ensure defaults are present for new plugins
if 'uuid' not in values:
values['uuid'] = uuidutils.generate_uuid()
inj_plug = models.InjectionPlugin()
inj_plug.update(values)
try:
inj_plug.save()
except db_exc.DBDuplicateEntry:
raise exception.PluginAlreadyExists(uuid=values['uuid'])
return inj_plug
def update_injection_plugin(self, plugin_injection_id, values):
if 'uuid' in values:
msg = _("Cannot overwrite UUID for an existing Plugin.")
raise exception.InvalidParameterValue(err=msg)
try:
return self._do_update_injection_plugin(
plugin_injection_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'])
else:
raise e
def get_injection_plugin_by_uuids(self, board_uuid, plugin_uuid):
query = model_query(
models.InjectionPlugin).filter_by(
board_uuid=board_uuid).filter_by(
plugin_uuid=plugin_uuid)
try:
return query.one()
except NoResultFound:
raise exception.InjectionPluginNotFound()
def destroy_injection_plugin(self, injection_plugin_id):
session = get_session()
with session.begin():
query = model_query(models.InjectionPlugin, session=session)
query = add_identity_filter(query, injection_plugin_id)
try:
query.delete()
except NoResultFound:
raise exception.InjectionPluginNotFound()
def get_injection_plugin_list(self, board_uuid):
query = model_query(
models.InjectionPlugin).filter_by(
board_uuid=board_uuid)
return query.all()

View File

@ -138,14 +138,14 @@ class WampAgent(Base):
ragent = Column(Boolean, default=False)
class Node(Base):
"""Represents a Node."""
class Board(Base):
"""Represents a Board."""
__tablename__ = 'nodes'
__tablename__ = 'boards'
__table_args__ = (
schema.UniqueConstraint('uuid', name='uniq_nodes0uuid'),
schema.UniqueConstraint('code', name='uniq_nodes0code'),
schema.UniqueConstraint('uuid', name='uniq_boards0uuid'),
schema.UniqueConstraint('code', name='uniq_boards0code'),
table_args())
id = Column(Integer, primary_key=True)
uuid = Column(String(36))
@ -162,7 +162,7 @@ class Node(Base):
class Location(Base):
"""Represents a location of a node."""
"""Represents a location of a board."""
__tablename__ = 'locations'
__table_args__ = (
@ -171,11 +171,11 @@ class Location(Base):
longitude = Column(String(18), nullable=True)
latitude = Column(String(18), nullable=True)
altitude = Column(String(18), nullable=True)
node_id = Column(Integer, ForeignKey('nodes.id'))
board_id = Column(Integer, ForeignKey('boards.id'))
class SessionWP(Base):
"""Represents a session of a node."""
"""Represents a session of a board."""
__tablename__ = 'sessions'
__table_args__ = (
@ -183,14 +183,14 @@ class SessionWP(Base):
'session_id',
name='uniq_session_id0session_id'),
schema.UniqueConstraint(
'node_uuid',
name='uniq_node_uuid0node_uuid'),
'board_uuid',
name='uniq_board_uuid0board_uuid'),
table_args())
id = Column(Integer, primary_key=True)
valid = Column(Boolean, default=True)
session_id = Column(String(15))
node_uuid = Column(String(36))
node_id = Column(Integer, ForeignKey('nodes.id'))
board_uuid = Column(String(36))
board_id = Column(Integer, ForeignKey('boards.id'))
class Plugin(Base):
@ -205,19 +205,19 @@ class Plugin(Base):
name = Column(String(36))
owner = Column(String(36))
public = Column(Boolean, default=False)
config = Column(TEXT)
code = Column(TEXT)
callable = Column(Boolean)
extra = Column(JSONEncodedDict)
class Injected_Plugin(Base):
class InjectionPlugin(Base):
"""Represents an plugin injection on board."""
__tablename__ = 'injected_plugins'
__tablename__ = 'injection_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'))
board_uuid = Column(String(36), ForeignKey('boards.uuid'))
plugin_uuid = Column(String(36), ForeignKey('plugins.uuid'))
onboot = Column(Boolean, default=False)
status = Column(String(15))

View File

@ -12,25 +12,28 @@
# License for the specific language governing permissions and limitations
# under the License.
from iotronic.objects import board
from iotronic.objects import conductor
from iotronic.objects import injectionplugin
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
Board = board.Board
Location = location.Location
Plugin = plugin.Plugin
InjectionPlugin = injectionplugin.InjectionPlugin
SessionWP = sessionwp.SessionWP
WampAgent = wampagent.WampAgent
__all__ = (
Conductor,
Node,
Board,
Location,
SessionWP,
WampAgent,
Plugin,
InjectionPlugin,
)

View File

@ -22,7 +22,7 @@ from iotronic.objects import base
from iotronic.objects import utils as obj_utils
class Node(base.IotronicObject):
class Board(base.IotronicObject):
# Version 1.0: Initial version
VERSION = '1.0'
@ -44,75 +44,75 @@ class Node(base.IotronicObject):
}
@staticmethod
def _from_db_object(node, db_node):
def _from_db_object(board, db_board):
"""Converts a database entity to a formal object."""
for field in node.fields:
node[field] = db_node[field]
node.obj_reset_changes()
return node
for field in board.fields:
board[field] = db_board[field]
board.obj_reset_changes()
return board
@base.remotable_classmethod
def get(cls, context, node_id):
"""Find a node based on its id or uuid and return a Node object.
def get(cls, context, board_id):
"""Find a board based on its id or uuid and return a Board object.
:param node_id: the id *or* uuid of a node.
:returns: a :class:`Node` object.
:param board_id: the id *or* uuid of a board.
:returns: a :class:`Board` object.
"""
if strutils.is_int_like(node_id):
return cls.get_by_id(context, node_id)
elif uuidutils.is_uuid_like(node_id):
return cls.get_by_uuid(context, node_id)
if strutils.is_int_like(board_id):
return cls.get_by_id(context, board_id)
elif uuidutils.is_uuid_like(board_id):
return cls.get_by_uuid(context, board_id)
else:
raise exception.InvalidIdentity(identity=node_id)
raise exception.InvalidIdentity(identity=board_id)
@base.remotable_classmethod
def get_by_id(cls, context, node_id):
"""Find a node based on its integer id and return a Node object.
def get_by_id(cls, context, board_id):
"""Find a board based on its integer id and return a Board object.
:param node_id: the id of a node.
:returns: a :class:`Node` object.
:param board_id: the id of a board.
:returns: a :class:`Board` object.
"""
db_node = cls.dbapi.get_node_by_id(node_id)
node = Node._from_db_object(cls(context), db_node)
return node
db_board = cls.dbapi.get_board_by_id(board_id)
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a node based on uuid and return a Node object.
"""Find a board based on uuid and return a Board object.
:param uuid: the uuid of a node.
:returns: a :class:`Node` object.
:param uuid: the uuid of a board.
:returns: a :class:`Board` object.
"""
db_node = cls.dbapi.get_node_by_uuid(uuid)
node = Node._from_db_object(cls(context), db_node)
return node
db_board = cls.dbapi.get_board_by_uuid(uuid)
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def get_by_code(cls, context, code):
"""Find a node based on name and return a Node object.
"""Find a board based on name and return a Board object.
:param name: the logical name of a node.
:returns: a :class:`Node` object.
:param name: the logical name of a board.
:returns: a :class:`Board` object.
"""
db_node = cls.dbapi.get_node_by_code(code)
node = Node._from_db_object(cls(context), db_node)
return node
db_board = cls.dbapi.get_board_by_code(code)
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def get_by_name(cls, context, name):
"""Find a node based on name and return a Node object.
"""Find a board based on name and return a Board object.
:param name: the logical name of a node.
:returns: a :class:`Node` object.
:param name: the logical name of a board.
:returns: a :class:`Board` object.
"""
db_node = cls.dbapi.get_node_by_name(name)
node = Node._from_db_object(cls(context), db_node)
return node
db_board = cls.dbapi.get_board_by_name(name)
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def list(cls, context, limit=None, marker=None, sort_key=None,
sort_dir=None, filters=None):
"""Return a list of Node objects.
"""Return a list of Board objects.
:param context: Security context.
:param limit: maximum number of resources to return in a single result.
@ -120,97 +120,97 @@ class Node(base.IotronicObject):
: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:`Node` object.
:returns: a list of :class:`Board` object.
"""
db_nodes = cls.dbapi.get_node_list(filters=filters, limit=limit,
marker=marker, sort_key=sort_key,
sort_dir=sort_dir)
return [Node._from_db_object(cls(context), obj) for obj in db_nodes]
db_boards = cls.dbapi.get_board_list(filters=filters, limit=limit,
marker=marker, sort_key=sort_key,
sort_dir=sort_dir)
return [Board._from_db_object(cls(context), obj) for obj in db_boards]
@base.remotable_classmethod
def reserve(cls, context, tag, node_id):
"""Get and reserve a node.
def reserve(cls, context, tag, board_id):
"""Get and reserve a board.
To prevent other ManagerServices from manipulating the given
Node while a Task is performed, mark it reserved by this host.
Board while a Task is performed, mark it reserved by this host.
:param context: Security context.
:param tag: A string uniquely identifying the reservation holder.
:param node_id: A node id or uuid.
:raises: NodeNotFound if the node is not found.
:returns: a :class:`Node` object.
:param board_id: A board id or uuid.
:raises: BoardNotFound if the board is not found.
:returns: a :class:`Board` object.
"""
db_node = cls.dbapi.reserve_node(tag, node_id)
node = Node._from_db_object(cls(context), db_node)
return node
db_board = cls.dbapi.reserve_board(tag, board_id)
board = Board._from_db_object(cls(context), db_board)
return board
@base.remotable_classmethod
def release(cls, context, tag, node_id):
"""Release the reservation on a node.
def release(cls, context, tag, board_id):
"""Release the reservation on a board.
:param context: Security context.
:param tag: A string uniquely identifying the reservation holder.
:param node_id: A node id or uuid.
:raises: NodeNotFound if the node is not found.
:param board_id: A board id or uuid.
:raises: BoardNotFound if the board is not found.
"""
cls.dbapi.release_node(tag, node_id)
cls.dbapi.release_board(tag, board_id)
@base.remotable
def create(self, context=None):
"""Create a Node record in the DB.
"""Create a Board 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
node before updates are made.
board 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.: Node(context)
object, e.g.: Board(context)
"""
values = self.obj_get_changes()
db_node = self.dbapi.create_node(values)
self._from_db_object(self, db_node)
db_board = self.dbapi.create_board(values)
self._from_db_object(self, db_board)
@base.remotable
def destroy(self, context=None):
"""Delete the Node from the DB.
"""Delete the Board 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.: Node(context)
object, e.g.: Board(context)
"""
self.dbapi.destroy_node(self.uuid)
self.dbapi.destroy_board(self.uuid)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this Node.
"""Save updates to this Board.
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
node before updates are made.
board 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.: Node(context)
object, e.g.: Board(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_node(self.uuid, updates)
self.dbapi.update_board(self.uuid, updates)
self.obj_reset_changes()
@base.remotable
@ -222,7 +222,7 @@ class Node(base.IotronicObject):
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.: Node(context)
object, e.g.: Board(context)
"""
current = self.__class__.get_by_uuid(self._context, self.uuid)
for field in self.fields:

View File

@ -0,0 +1,165 @@
# 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 iotronic.db import api as db_api
from iotronic.objects import base
from iotronic.objects import utils as obj_utils
class InjectionPlugin(base.IotronicObject):
# Version 1.0: Initial version
VERSION = '1.0'
dbapi = db_api.get_instance()
fields = {
'id': int,
'board_uuid': obj_utils.str_or_none,
'plugin_uuid': obj_utils.str_or_none,
'onboot': bool,
'status': obj_utils.str_or_none,
}
@staticmethod
def _from_db_object(injection_plugin, db_injection_plugin):
"""Converts a database entity to a formal object."""
for field in injection_plugin.fields:
injection_plugin[field] = db_injection_plugin[field]
injection_plugin.obj_reset_changes()
return injection_plugin
@base.remotable_classmethod
def get_by_id(cls, context, injection_plugin_id):
"""Find a injection_plugin based on its integer id and return a Board object.
:param injection_plugin_id: the id of a injection_plugin.
:returns: a :class:`injection_plugin` object.
"""
db_inj_plugin = cls.dbapi.get_injection_plugin_by_id(
injection_plugin_id)
inj_plugin = InjectionPlugin._from_db_object(cls(context),
db_inj_plugin)
return inj_plugin
@base.remotable_classmethod
def get_by_board_uuid(cls, context, board_uuid):
"""Find a injection_plugin based on uuid and return a Board object.
:param board_uuid: the uuid of a injection_plugin.
:returns: a :class:`injection_plugin` object.
"""
db_inj_plugin = cls.dbapi.get_injection_plugin_by_board_uuid(
board_uuid)
inj_plugin = InjectionPlugin._from_db_object(cls(context),
db_inj_plugin)
return inj_plugin
@base.remotable_classmethod
def get_by_plugin_uuid(cls, context, plugin_uuid):
"""Find a injection_plugin based on uuid and return a Board object.
:param plugin_uuid: the uuid of a injection_plugin.
:returns: a :class:`injection_plugin` object.
"""
db_inj_plugin = cls.dbapi.get_injection_plugin_by_plugin_uuid(
plugin_uuid)
inj_plugin = InjectionPlugin._from_db_object(cls(context),
db_inj_plugin)
return inj_plugin
@base.remotable_classmethod
def get(cls, context, board_uuid, plugin_uuid):
"""Find a injection_plugin based on uuid and return a Board object.
:param board_uuid: the uuid of a injection_plugin.
:returns: a :class:`injection_plugin` object.
"""
db_inj_plugin = cls.dbapi.get_injection_plugin_by_uuids(board_uuid,
plugin_uuid)
inj_plugin = InjectionPlugin._from_db_object(cls(context),
db_inj_plugin)
return inj_plugin
@base.remotable_classmethod
def list(cls, context, board_uuid):
"""Return a list of InjectionPlugin 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:`InjectionPlugin` object.
"""
db_injs = cls.dbapi.get_injection_plugin_list(board_uuid)
return [InjectionPlugin._from_db_object(cls(context), obj)
for obj in db_injs]
@base.remotable
def create(self, context=None):
"""Create a InjectionPlugin 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
injection_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.: InjectionPlugin(context)
"""
values = self.obj_get_changes()
db_injection_plugin = self.dbapi.create_injection_plugin(values)
self._from_db_object(self, db_injection_plugin)
@base.remotable
def destroy(self, context=None):
"""Delete the InjectionPlugin 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.: InjectionPlugin(context)
"""
self.dbapi.destroy_injection_plugin(self.id)
self.obj_reset_changes()
@base.remotable
def save(self, context=None):
"""Save updates to this InjectionPlugin.
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
injection_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.: InjectionPlugin(context)
"""
updates = self.obj_get_changes()
self.dbapi.update_injection_plugin(self.id, updates)
self.obj_reset_changes()

View File

@ -29,7 +29,7 @@ class Location(base.IotronicObject):
fields = {
'id': int,
'node_id': obj_utils.int_or_none,
'board_id': obj_utils.int_or_none,
'longitude': obj_utils.str_or_none,
'latitude': obj_utils.str_or_none,
'altitude': obj_utils.str_or_none,
@ -109,12 +109,12 @@ class Location(base.IotronicObject):
return Location._from_db_object_list(db_locations, cls, context)
@base.remotable_classmethod
def list_by_node_uuid(cls, context, node_uuid, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Location objects associated with a given node ID.
def list_by_board_uuid(cls, context, board_uuid, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Location objects associated with a given board ID.
:param context: Security context.
:param node_id: the ID of the node.
:param board_id: the ID of the board.
: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.
@ -122,20 +122,21 @@ class Location(base.IotronicObject):
:returns: a list of :class:`Location` object.
"""
node_id = cls.dbapi.get_node_id_by_uuid(node_uuid)[0]
db_locations = cls.dbapi.get_locations_by_node_id(node_id, limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Location._from_db_object_list(db_locations, cls, context)
board_id = cls.dbapi.get_board_id_by_uuid(board_uuid)[0]
db_loc = cls.dbapi.get_locations_by_board_id(board_id,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Location._from_db_object_list(db_loc, cls, context)
@base.remotable_classmethod
def list_by_node_id(cls, context, node_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Location objects associated with a given node ID.
def list_by_board_id(cls, context, board_id, limit=None, marker=None,
sort_key=None, sort_dir=None):
"""Return a list of Location objects associated with a given board ID.
:param context: Security context.
:param node_id: the ID of the node.
:param board_id: the ID of the board.
: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.
@ -143,11 +144,12 @@ class Location(base.IotronicObject):
:returns: a list of :class:`Location` object.
"""
db_locations = cls.dbapi.get_locations_by_node_id(node_id, limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Location._from_db_object_list(db_locations, cls, context)
db_loc = cls.dbapi.get_locations_by_board_id(board_id,
limit=limit,
marker=marker,
sort_key=sort_key,
sort_dir=sort_dir)
return Location._from_db_object_list(db_loc, cls, context)
@base.remotable
def create(self, context=None):

View File

@ -21,6 +21,20 @@ from iotronic.db import api as db_api
from iotronic.objects import base
from iotronic.objects import utils as obj_utils
ACTIONS = ['PluginCall', 'PluginStop', 'PluginStart',
'PluginStatus', 'PluginReboot']
NO_PARAMS = ['PluginStatus', 'PluginReboot']
def is_valid_action(action):
if action not in ACTIONS:
raise exception.InvalidPluginAction(action=action)
return True
def want_params(action):
return False if action in NO_PARAMS else True
class Plugin(base.IotronicObject):
# Version 1.0: Initial version
@ -34,7 +48,8 @@ class Plugin(base.IotronicObject):
'name': obj_utils.str_or_none,
'owner': obj_utils.str_or_none,
'public': bool,
'config': obj_utils.str_or_none,
'code': obj_utils.str_or_none,
'callable': bool,
'extra': obj_utils.dict_or_none,
}
@ -48,10 +63,10 @@ class Plugin(base.IotronicObject):
@base.remotable_classmethod
def get(cls, context, plugin_id):
"""Find a plugin based on its id or uuid and return a Node object.
"""Find a plugin based on its id or uuid and return a Board object.
:param plugin_id: the id *or* uuid of a plugin.
:returns: a :class:`Node` object.
:returns: a :class:`Board` object.
"""
if strutils.is_int_like(plugin_id):
return cls.get_by_id(context, plugin_id)
@ -62,10 +77,10 @@ class Plugin(base.IotronicObject):
@base.remotable_classmethod
def get_by_id(cls, context, plugin_id):
"""Find a plugin based on its integer id and return a Node object.
"""Find a plugin based on its integer id and return a Board object.
:param plugin_id: the id of a plugin.
:returns: a :class:`Node` object.
:returns: a :class:`Board` object.
"""
db_plugin = cls.dbapi.get_plugin_by_id(plugin_id)
plugin = Plugin._from_db_object(cls(context), db_plugin)
@ -73,10 +88,10 @@ class Plugin(base.IotronicObject):
@base.remotable_classmethod
def get_by_uuid(cls, context, uuid):
"""Find a plugin based on uuid and return a Node object.
"""Find a plugin based on uuid and return a Board object.
:param uuid: the uuid of a plugin.
:returns: a :class:`Node` object.
:returns: a :class:`Board` object.
"""
db_plugin = cls.dbapi.get_plugin_by_uuid(uuid)
plugin = Plugin._from_db_object(cls(context), db_plugin)
@ -84,10 +99,10 @@ class Plugin(base.IotronicObject):
@base.remotable_classmethod
def get_by_name(cls, context, name):
"""Find a plugin based on name and return a Node object.
"""Find a plugin based on name and return a Board object.
:param name: the logical name of a plugin.
:returns: a :class:`Node` object.
:returns: a :class:`Board` object.
"""
db_plugin = cls.dbapi.get_plugin_by_name(name)
plugin = Plugin._from_db_object(cls(context), db_plugin)
@ -184,6 +199,6 @@ class Plugin(base.IotronicObject):
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]
self, base.get_attrname(field))
and self[field] != current[field]):
self[field] = current[field]

View File

@ -18,6 +18,7 @@ from iotronic.db import api as dbapi
from iotronic.objects import base
from iotronic.objects import utils as obj_utils
from oslo_utils import strutils
from oslo_utils import uuidutils
class SessionWP(base.IotronicObject):
@ -27,9 +28,9 @@ class SessionWP(base.IotronicObject):
fields = {
'id': int,
'node_uuid': obj_utils.str_or_none,
'board_uuid': obj_utils.str_or_none,
'session_id': obj_utils.str_or_none,
'node_id': obj_utils.int_or_none,
'board_id': obj_utils.int_or_none,
'valid': bool,
}
@ -51,16 +52,18 @@ class SessionWP(base.IotronicObject):
obj) for obj in db_objects]
@base.remotable_classmethod
def get(cls, context, session_id):
def get(cls, context, session_or_board_uuid):
"""Find a session based on its id or uuid and return a SessionWP object.
:param session_id: the id *or* uuid of a session.
:returns: a :class:`SessionWP` object.
"""
if strutils.is_int_like(session_id):
return cls.get_by_id(context, session_id)
if strutils.is_int_like(session_or_board_uuid):
return cls.get_by_id(context, session_or_board_uuid)
elif uuidutils.is_uuid_like(session_or_board_uuid):
return cls.get_by_uuid(context, session_or_board_uuid)
else:
raise exception.InvalidIdentity(identity=session_id)
raise exception.InvalidIdentity(identity=session_or_board_uuid)
@base.remotable_classmethod
def get_by_id(cls, context, ses_id):
@ -74,14 +77,15 @@ class SessionWP(base.IotronicObject):
return session
@base.remotable_classmethod
def get_session_by_node_uuid(cls, context, node_uuid, valid=True):
def get_session_by_board_uuid(cls, context, board_uuid, valid=True):
"""Find a session based on uuid and return a :class:`SessionWP` object.
:param node_uuid: the uuid of a node.
:param board_uuid: the uuid of a board.
:param context: Security context
:returns: a :class:`SessionWP` object.
"""
db_session = cls.dbapi.get_session_by_node_uuid(node_uuid, valid)
db_session = cls.dbapi.get_session_by_board_uuid(board_uuid, valid)
session = SessionWP._from_db_object(cls(context), db_session)
return session
@ -174,4 +178,4 @@ class SessionWP(base.IotronicObject):
if (hasattr(
self, base.get_attrname(field))
and self[field] != current[field]):
self[field] = current[field]
self[field] = current[field]

View File

@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Generic Node base class for all workers that run on hosts."""
"""Generic Board base class for all workers that run on hosts."""
import errno
import os

View File

@ -106,8 +106,9 @@ class WampFrontend(wamp.ApplicationSession):
import iotronic.wamp.functions as fun
self.subscribe(fun.node_on_leave, 'wamp.session.on_leave')
self.subscribe(fun.node_on_join, 'wamp.session.on_join')
self.subscribe(fun.board_on_leave, 'wamp.session.on_leave')
self.subscribe(fun.board_on_join, 'wamp.session.on_join')
# self.subscribe(fun.on_board_connect, 'board.connection')
try:
if CONF.wamp.register_agent:

View File

@ -14,6 +14,7 @@
# under the License.
from iotronic.common import rpc
from iotronic.common import states
from iotronic.conductor import rpcapi
from iotronic import objects
from oslo_config import cfg
@ -43,16 +44,38 @@ def echo(data):
return data
def node_on_leave(session_id):
LOG.debug('A node with %s disconnectd', session_id)
def board_on_leave(session_id):
LOG.debug('A board with %s disconnectd', session_id)
try:
old_session = objects.SessionWP({}).get_by_session_id({}, session_id)
old_session = objects.SessionWP.get(ctxt, session_id)
old_session.valid = False
old_session.save()
LOG.debug('Session %s deleted', session_id)
except Exception:
LOG.debug('session %s not found', session_id)
board = objects.Board.get_by_uuid(ctxt, old_session.board_uuid)
board.status = states.OFFLINE
board.save()
LOG.debug('Board %s is now %s', old_session.uuid, states.OFFLINE)
def on_board_connect(board_uuid, session_id, msg):
if msg == 'connection':
try:
board = objects.Board.get_by_uuid(ctxt, board_uuid)
board.status = states.ONLINE
session_data = {'board_id': board.id,
'board_uuid': board.uuid,
'session_id': session_id}
session = objects.SessionWP(ctxt, **session_data)
session.create()
board.save()
LOG.debug('Board %s is now %s', board_uuid, states.ONLINE)
except Exception:
LOG.debug(Exception.message)
def connection(uuid, session):
return c.connection(ctxt, uuid, session)
@ -62,5 +85,5 @@ def registration(code, session):
return c.registration(ctxt, code, session)
def node_on_join(session_id):
LOG.debug('A node with %s joined', session_id)
def board_on_join(session_id):
LOG.debug('A board with %s joined', session_id['session'])

View File

@ -21,6 +21,11 @@ ERROR = 'ERROR'
WARNING = 'WARNING'
def deserialize(received):
m = json.loads(received)
return WampMessage(**m)
class WampMessage(object):
def __init__(self, message=None, result=None):
self.message = message
@ -29,10 +34,6 @@ class WampMessage(object):
def serialize(self):
return json.dumps(self, default=lambda o: o.__dict__)
def deserialize(self, received):
self.__dict__ = json.loads(received)
return self
class WampSuccess(WampMessage):
def __init__(self, msg=None):

View File

@ -56,11 +56,11 @@ DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
-- Table `iotronic`.`nodes`
-- Table `iotronic`.`boards`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `iotronic`.`nodes` ;
DROP TABLE IF EXISTS `iotronic`.`boards` ;
CREATE TABLE IF NOT EXISTS `iotronic`.`nodes` (
CREATE TABLE IF NOT EXISTS `iotronic`.`boards` (
`created_at` DATETIME NULL DEFAULT NULL,
`updated_at` DATETIME NULL DEFAULT NULL,
`id` INT(11) NOT NULL AUTO_INCREMENT,
@ -95,12 +95,12 @@ CREATE TABLE IF NOT EXISTS `iotronic`.`locations` (
`longitude` VARCHAR(18) NULL DEFAULT NULL,
`latitude` VARCHAR(18) NULL DEFAULT NULL,
`altitude` VARCHAR(18) NULL DEFAULT NULL,
`node_id` INT(11) NOT NULL,
`board_id` INT(11) NOT NULL,
PRIMARY KEY (`id`),
INDEX `node_id` (`node_id` ASC),
INDEX `board_id` (`board_id` ASC),
CONSTRAINT `location_ibfk_1`
FOREIGN KEY (`node_id`)
REFERENCES `iotronic`.`nodes` (`id`)
FOREIGN KEY (`board_id`)
REFERENCES `iotronic`.`boards` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
@ -119,14 +119,14 @@ CREATE TABLE IF NOT EXISTS `iotronic`.`sessions` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`valid` TINYINT(1) NOT NULL DEFAULT '1',
`session_id` VARCHAR(18) NOT NULL,
`node_uuid` VARCHAR(36) NOT NULL,
`node_id` INT(11) NOT NULL,
`board_uuid` VARCHAR(36) NOT NULL,
`board_id` INT(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `session_id` (`session_id` ASC),
INDEX `session_node_id` (`node_id` ASC),
CONSTRAINT `session_node_id`
FOREIGN KEY (`node_id`)
REFERENCES `iotronic`.`nodes` (`id`)
INDEX `session_board_id` (`board_id` ASC),
CONSTRAINT `session_board_id`
FOREIGN KEY (`board_id`)
REFERENCES `iotronic`.`boards` (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
@ -145,7 +145,8 @@ CREATE TABLE IF NOT EXISTS `iotronic`.`plugins` (
`uuid` VARCHAR(36) NOT NULL,
`name` VARCHAR(255) NULL DEFAULT NULL,
`public` TINYINT(1) NOT NULL DEFAULT '0',
`config` TEXT NULL DEFAULT NULL,
`code` TEXT NULL DEFAULT NULL,
`callable` TINYINT(1) NOT NULL,
`extra` TEXT NULL DEFAULT NULL,
`owner` VARCHAR(36) NOT NULL,
PRIMARY KEY (`id`),
@ -157,28 +158,27 @@ DEFAULT CHARACTER SET = utf8;
-- -----------------------------------------------------
-- Table `iotronic`.`injected_plugins`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `iotronic`.`injected_plugins` ;
DROP TABLE IF EXISTS `iotronic`.`injection_plugins` ;
CREATE TABLE IF NOT EXISTS `iotronic`.`injected_plugins` (
CREATE TABLE IF NOT EXISTS `iotronic`.`injection_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,
`board_uuid` VARCHAR(36) NOT NULL,
`plugin_uuid` VARCHAR(36) NOT NULL,
`plugin_id` INT(11) NOT NULL,
`status` VARCHAR(15) NOT NULL DEFAULT 'injected',
`onboot` TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `node_id` (`node_id` ASC),
CONSTRAINT `node_id`
FOREIGN KEY (`node_id`)
REFERENCES `iotronic`.`nodes` (`id`)
INDEX `board_uuid` (`board_uuid` ASC),
CONSTRAINT `board_uuid`
FOREIGN KEY (`board_uuid`)
REFERENCES `iotronic`.`boards` (`uuid`)
ON DELETE CASCADE
ON UPDATE CASCADE,
INDEX `plugin_id` (`plugin_id` ASC),
CONSTRAINT `plugin_id`
FOREIGN KEY (`plugin_id`)
REFERENCES `iotronic`.`plugins` (`id`)
INDEX `plugin_uuid` (`plugin_uuid` ASC),
CONSTRAINT `plugin_uuid`
FOREIGN KEY (`plugin_uuid`)
REFERENCES `iotronic`.`plugins` (`uuid`)
ON DELETE CASCADE
ON UPDATE CASCADE)
ENGINE = InnoDB
@ -191,12 +191,19 @@ SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
-- insert testing nodes
INSERT INTO `nodes` VALUES
('2017-02-20 10:38:26',NULL,132,'f3961f7a-c937-4359-8848-fb64aa8eeaaa','12345','registered','node','server',NULL,'eee383360cc14c44b9bf21e1e003a4f3','4adfe95d49ad41398e00ecda80257d21',0,'{}','{}'),
-- insert testing boards
INSERT INTO `boards` VALUES
('2017-02-20 10:38:26',NULL,132,'f3961f7a-c937-4359-8848-fb64aa8eeaaa','12345','registered','laptop-14','server',NULL,'eee383360cc14c44b9bf21e1e003a4f3','4adfe95d49ad41398e00ecda80257d21',0,'{}','{}'),
('2017-02-20 10:38:45',NULL,133,'ba1efce9-cad9-4ae1-a5d1-d90a8d203d3b','yunyun','registered','yun22','yun',NULL,'eee383360cc14c44b9bf21e1e003a4f3','4adfe95d49ad41398e00ecda80257d21',0,'{}','{}'),
('2017-02-20 10:39:08',NULL,134,'65f9db36-9786-4803-b66f-51dcdb60066e','test','registered','test','server',NULL,'eee383360cc14c44b9bf21e1e003a4f3','4adfe95d49ad41398e00ecda80257d21',0,'{}','{}');
INSERT INTO `locations` VALUES
('2017-02-20 10:38:26',NULL,6,'2','1','3',132),
('2017-02-20 10:38:45',NULL,7,'2','1','3',133),
('2017-02-20 10:39:08',NULL,8,'2','1','3',134)
('2017-02-20 10:39:08',NULL,8,'2','1','3',134);
INSERT INTO `plugins` VALUES
('2017-02-20 10:38:26',NULL,132,'edff22cd-9148-4ad8-b35b-51dcdb60066e','runner','0','V# Copyright 2017 MDSLAB - University of Messina\u000a# All Rights Reserved.\u000a#\u000a# Licensed under the Apache License, Version 2.0 (the "License"); you may\u000a# not use this file except in compliance with the License. You may obtain\u000a# a copy of the License at\u000a#\u000a# http://www.apache.org/licenses/LICENSE-2.0\u000a#\u000a# Unless required by applicable law or agreed to in writing, software\u000a# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\u000a# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\u000a# License for the specific language governing permissions and limitations\u000a# under the License.\u000a\u000afrom iotronic_lightningrod.plugins import Plugin\u000a\u000afrom oslo_log import log as logging\u000aLOG = logging.getLogger(__name__)\u000a\u000a# User imports\u000aimport time\u000a\u000a\u000a\u000aclass Worker(Plugin.Plugin):\u000a def __init__(self, name, th_result, plugin_conf=None):\u000a super(Worker, self).__init__(name, th_result, plugin_conf)\u000a\u000a def run(self):\u000a LOG.info("Plugin " + self.name + " starting...")\u000a while(self._is_running):\u000a print(self.plugin_conf[''message''])\u000a time.sleep(1) \u000a
p1
.',0,'{}','eee383360cc14c44b9bf21e1e003a4f3')
('2017-02-20 10:38:26',NULL,133,'edff22cd-9148-4ad8-b35b-c0c80abf1e8a','zero','0','Vfrom iotronic_lightningrod.plugins import Plugin\u000a\u000afrom oslo_log import log as logging\u000a\u000aLOG = logging.getLogger(__name__)\u000a\u000a\u000a# User imports\u000a\u000a\u000aclass Worker(Plugin.Plugin):\u000a def __init__(self, name, is_running):\u000a super(Worker, self).__init__(name, is_running)\u000a\u000a def run(self):\u000a LOG.info("Plugin process completed!")\u000a #self.Done()
p1
.',1,'{}','eee383360cc14c44b9bf21e1e003a4f3');

View File

@ -1,195 +0,0 @@
#! /usr/bin/python
import sys
import requests
import json
import os
token = None
iotronic_url = "http://192.168.17.102:1288/v1"
try:
os.environ['OS_AUTH_URL']
except Exception:
print("load the rc")
sys.exit(1)
url = os.environ['OS_AUTH_URL'] + "/auth/tokens"
def get_token(user, project, psw):
payload = {"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": user,
"domain": {"id": "default"},
"password": psw
}
}
},
"scope": {
"project": {
"name": project,
"domain": {"id": "default"}
}
}
}
}
headers = {
'content-type': "application/json",
}
response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
token=response.headers.get('X-Subject-Token')
if token:
r=json.loads(response.text)['token']['roles']
roles=[str(x['name']) for x in r]
print(user + " in " + project + ' with roles: '+ " ".join(roles) )
return token
def node_manager(argv):
actions = ['show', 'list', 'create', 'delete', 'update']
if argv[0] not in actions or len(argv) == 0:
print("node list|create|delete|show|update")
sys.exit()
global iotronic_url, token
if not token:
token = get_token(os.environ['OS_USERNAME'], os.environ['OS_PROJECT_NAME'], os.environ['OS_PASSWORD'])
# print(token)
headers = {'content-type': "application/json", 'x-auth-token': token, }
if argv[0] == 'list':
url = iotronic_url + "/nodes"
response = requests.request("GET", url, headers=headers)
elif argv[0] == 'create':
code = argv[1]
name = argv[2]
lat = argv[3]
lon = argv[4]
alt = argv[5]
typ = argv[6]
url = iotronic_url + "/nodes"
payload = {
"code": code,
"mobile": False,
"location": [
{
"latitude": lat,
"altitude": alt,
"longitude": lon
}
],
"type": typ,
"name": name
}
response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
elif argv[0] == 'delete':
node = argv[1]
url = iotronic_url + "/nodes/" + node
response = requests.request("DELETE", url, headers=headers)
elif argv[0] == 'show':
node = argv[1]
url = iotronic_url + "/nodes/" + node
response = requests.request("GET", url, headers=headers)
elif argv[0] == 'update':
node = argv[1]
values = {}
for opt in argv[2:]:
key, val = opt.split(':')
values[key] = val
url = iotronic_url + "/nodes/" + node
payload = values
response = requests.request("PATCH", url, data=json.dumps(payload), headers=headers)
else:
print ("node list|create|delete|show|update")
sys.exit()
try:
print(json.dumps(response.json(), indent=2))
except Exception:
pass
def plugin_manager(argv):
actions = ['show', 'list', 'create', 'delete', 'update', 'inject']
if argv[0] not in actions or len(argv) == 0:
print("plugin list|create|delete|show|update|inject")
sys.exit()
global iotronic_url, token
if not token:
token = get_token(os.environ['OS_USERNAME'], os.environ['OS_PROJECT_NAME'], os.environ['OS_PASSWORD'])
headers = {'content-type': "application/json", 'x-auth-token': token, }
if argv[0] == 'list':
url = iotronic_url + "/plugins"
response = requests.request("GET", url, headers=headers)
elif argv[0] == 'create':
f=argv[1]
with open(f, 'r') as fil:
t = fil.read()
url = iotronic_url + "/plugins"
payload={"name": f, "config": t}
response = requests.request("POST", url, data=json.dumps(payload), headers=headers)
elif argv[0] == 'delete':
plugin = argv[1]
url = iotronic_url + "/plugins/" + plugin
response = requests.request("DELETE", url, headers=headers)
elif argv[0] == 'show':
plugin = argv[1]
url = iotronic_url + "/plugins/" + plugin
response = requests.request("GET", url, headers=headers)
elif argv[0] == 'update':
plugin = argv[1]
values = {}
for opt in argv[2:]:
key, val = opt.split(':')
values[key] = val
url = iotronic_url + "/plugins/" + plugin
payload = values
response = requests.request("PATCH", url, data=json.dumps(payload), headers=headers)
else:
print ("node list|create|delete|show|update")
sys.exit()
try:
print(json.dumps(response.json(), indent=2))
except Exception:
pass
if __name__ == "__main__":
argv = sys.argv
if len(argv) <= 2:
print("USAGE: iotronic node|plugin [OPTIONS]")
sys.exit()
if argv[1] == 'node':
node_manager(argv[2:])
elif argv[1] == 'plugin':
plugin_manager(argv[2:])
else:
print("USAGE: iotronic node|plugin [OPTIONS]")

View File

@ -0,0 +1,36 @@
# Copyright 2017 MDSLAB - University of Messina
# 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 iotronic_lightningrod.plugins import Plugin
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
# User imports
import time
class Worker(Plugin.Plugin):
def __init__(self, uuid, name, q_result=None, params=None):
super(Worker, self).__init__(uuid, name, q_result, params)
def run(self):
LOG.info("Plugin " + self.name + " starting...")
LOG.info(self.params)
while (self._is_running):
print(self.params['message'])
time.sleep(1)

View File

@ -0,0 +1,33 @@
# Copyright 2017 MDSLAB - University of Messina
# 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 iotronic_lightningrod.plugins import Plugin
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
# User imports
class Worker(Plugin.Plugin):
def __init__(self, uuid, name, q_result, params=None):
super(Worker, self).__init__(uuid, name, q_result, params)
def run(self):
LOG.info("Input parameters: " + str(self.params))
LOG.info("Plugin " + self.name + " process completed!")
self.q_result.put("ZERO RESULT")

View File

@ -1,14 +0,0 @@
from iotronic_lightningrod.plugins import Plugin
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
# User imports
class Worker(Plugin.Plugin):
def __init__(self, name, is_running):
super(Worker, self).__init__(name, is_running)
def run(self):
LOG.info("Plugin process completed!")
#self.Done()