diff --git a/install.sh b/install.sh index 453afb8..4fb1d6a 100755 --- a/install.sh +++ b/install.sh @@ -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 { diff --git a/iotronic/api/controllers/v1/__init__.py b/iotronic/api/controllers/v1/__init__.py index 3f92102..c1b53bc 100644 --- a/iotronic/api/controllers/v1/__init__.py +++ b/iotronic/api/controllers/v1/__init__.py @@ -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): diff --git a/iotronic/api/controllers/v1/board.py b/iotronic/api/controllers/v1/board.py new file mode 100644 index 0000000..51d856c --- /dev/null +++ b/iotronic/api/controllers/v1/board.py @@ -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) diff --git a/iotronic/api/controllers/v1/node.py b/iotronic/api/controllers/v1/node.py deleted file mode 100644 index b7718c3..0000000 --- a/iotronic/api/controllers/v1/node.py +++ /dev/null @@ -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) diff --git a/iotronic/api/controllers/v1/plugin.py b/iotronic/api/controllers/v1/plugin.py index c89a470..f18f823 100644 --- a/iotronic/api/controllers/v1/plugin.py +++ b/iotronic/api/controllers/v1/plugin.py @@ -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, diff --git a/iotronic/api/controllers/v1/utils.py b/iotronic/api/controllers/v1/utils.py index d0beb53..44aac60 100644 --- a/iotronic/api/controllers/v1/utils.py +++ b/iotronic/api/controllers/v1/utils.py @@ -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) diff --git a/iotronic/common/exception.py b/iotronic/common/exception.py index acbf4fc..9fcf024 100644 --- a/iotronic/common/exception.py +++ b/iotronic/common/exception.py @@ -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.") diff --git a/iotronic/common/policy.py b/iotronic/common/policy.py index e7c17c9..2df9d87 100644 --- a/iotronic/common/policy.py +++ b/iotronic/common/policy.py @@ -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 diff --git a/iotronic/common/service.py b/iotronic/common/service.py index a1c74a4..fbe2cc7 100644 --- a/iotronic/common/service.py +++ b/iotronic/common/service.py @@ -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.'), ] diff --git a/iotronic/common/states.py b/iotronic/common/states.py index 0ba2fa3..b26d80f 100644 --- a/iotronic/common/states.py +++ b/iotronic/common/states.py @@ -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' diff --git a/iotronic/common/utils.py b/iotronic/common/utils.py index 62e8bde..0a1e2e3 100644 --- a/iotronic/common/utils.py +++ b/iotronic/common/utils.py @@ -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. diff --git a/iotronic/conductor/endpoints.py b/iotronic/conductor/endpoints.py index 8a0790d..30bdd5e 100644 --- a/iotronic/conductor/endpoints.py +++ b/iotronic/conductor/endpoints.py @@ -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 diff --git a/iotronic/conductor/provisioner.py b/iotronic/conductor/provisioner.py index 7fdcb95..d8d9386 100644 --- a/iotronic/conductor/provisioner.py +++ b/iotronic/conductor/provisioner.py @@ -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'] = "" + if 'board' not in self.config['iotronic']: + self.config['iotronic']['board'] = {} + self.config['iotronic']['board']['token'] = "" diff --git a/iotronic/conductor/rpcapi.py b/iotronic/conductor/rpcapi.py index 5e103ab..d0b37bc 100644 --- a/iotronic/conductor/rpcapi.py +++ b/iotronic/conductor/rpcapi.py @@ -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) diff --git a/iotronic/db/api.py b/iotronic/db/api.py index f9e8128..f0745b3 100644 --- a/iotronic/db/api.py +++ b/iotronic/db/api.py @@ -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. + + """ diff --git a/iotronic/db/sqlalchemy/api.py b/iotronic/db/sqlalchemy/api.py index f7f2d0f..2bf471e 100644 --- a/iotronic/db/sqlalchemy/api.py +++ b/iotronic/db/sqlalchemy/api.py @@ -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() diff --git a/iotronic/db/sqlalchemy/models.py b/iotronic/db/sqlalchemy/models.py index 0f23617..d4b3465 100644 --- a/iotronic/db/sqlalchemy/models.py +++ b/iotronic/db/sqlalchemy/models.py @@ -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)) diff --git a/iotronic/objects/__init__.py b/iotronic/objects/__init__.py index 476365c..4387e2b 100644 --- a/iotronic/objects/__init__.py +++ b/iotronic/objects/__init__.py @@ -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, ) diff --git a/iotronic/objects/node.py b/iotronic/objects/board.py similarity index 59% rename from iotronic/objects/node.py rename to iotronic/objects/board.py index 9bdd0d5..630f40e 100644 --- a/iotronic/objects/node.py +++ b/iotronic/objects/board.py @@ -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: diff --git a/iotronic/objects/injectionplugin.py b/iotronic/objects/injectionplugin.py new file mode 100644 index 0000000..0fb4494 --- /dev/null +++ b/iotronic/objects/injectionplugin.py @@ -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() diff --git a/iotronic/objects/location.py b/iotronic/objects/location.py index f8f6f2a..ab735a0 100644 --- a/iotronic/objects/location.py +++ b/iotronic/objects/location.py @@ -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): diff --git a/iotronic/objects/plugin.py b/iotronic/objects/plugin.py index 7edf41d..065f746 100644 --- a/iotronic/objects/plugin.py +++ b/iotronic/objects/plugin.py @@ -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] diff --git a/iotronic/objects/sessionwp.py b/iotronic/objects/sessionwp.py index 14c181f..7c8b748 100644 --- a/iotronic/objects/sessionwp.py +++ b/iotronic/objects/sessionwp.py @@ -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] diff --git a/iotronic/openstack/common/service.py b/iotronic/openstack/common/service.py index 5f7b9c7..2478419 100644 --- a/iotronic/openstack/common/service.py +++ b/iotronic/openstack/common/service.py @@ -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 diff --git a/iotronic/wamp/agent.py b/iotronic/wamp/agent.py index f5fc18f..a79a356 100644 --- a/iotronic/wamp/agent.py +++ b/iotronic/wamp/agent.py @@ -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: diff --git a/iotronic/wamp/functions.py b/iotronic/wamp/functions.py index 6a9817a..10497c7 100644 --- a/iotronic/wamp/functions.py +++ b/iotronic/wamp/functions.py @@ -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']) diff --git a/iotronic/wamp/wampmessage.py b/iotronic/wamp/wampmessage.py index 6229561..f6a2dff 100644 --- a/iotronic/wamp/wampmessage.py +++ b/iotronic/wamp/wampmessage.py @@ -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): diff --git a/utils/iotronic.sql b/utils/iotronic.sql index 900d0b9..1a1f43e 100644 --- a/utils/iotronic.sql +++ b/utils/iotronic.sql @@ -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) \ No newline at end of file + ('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'); \ No newline at end of file diff --git a/utils/iotronic_curl_client b/utils/iotronic_curl_client deleted file mode 100755 index e390864..0000000 --- a/utils/iotronic_curl_client +++ /dev/null @@ -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]") diff --git a/utils/test_plugins/runner.py b/utils/test_plugins/runner.py new file mode 100644 index 0000000..8b6f5de --- /dev/null +++ b/utils/test_plugins/runner.py @@ -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) diff --git a/utils/test_plugins/zero.py b/utils/test_plugins/zero.py new file mode 100644 index 0000000..a1dbb87 --- /dev/null +++ b/utils/test_plugins/zero.py @@ -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") diff --git a/utils/zero b/utils/zero deleted file mode 100644 index 85bfa59..0000000 --- a/utils/zero +++ /dev/null @@ -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() \ No newline at end of file