move everything into pecan
This commit is contained in:
@@ -6,3 +6,4 @@ wsgiref>=0.1.2
|
||||
pecan>=0.4.5
|
||||
oslo.config>=1.2.0
|
||||
WSME>=0.6
|
||||
six>=1.5.2
|
||||
|
||||
@@ -112,7 +112,7 @@ class TeethAgent(object):
|
||||
self.listen_address = listen_address
|
||||
self.mode_implementation = None
|
||||
self.version = pkg_resources.get_distribution('teeth-agent').version
|
||||
self.api = app.VersionSelectorApplication()
|
||||
self.api = app.VersionSelectorApplication(self)
|
||||
self.command_results = collections.OrderedDict()
|
||||
self.heartbeater = TeethAgentHeartbeater(self)
|
||||
self.hardware = hardware.get_manager()
|
||||
@@ -187,7 +187,10 @@ class TeethAgent(object):
|
||||
except Exception as e:
|
||||
# Other errors are considered command execution errors, and are
|
||||
# recorded as an
|
||||
result = base.SyncCommandResult(command_name, kwargs, False, e)
|
||||
result = base.SyncCommandResult(command_name,
|
||||
kwargs,
|
||||
False,
|
||||
unicode(e))
|
||||
|
||||
self.command_results[result.id] = result
|
||||
return result
|
||||
|
||||
@@ -16,19 +16,29 @@ limitations under the License.
|
||||
|
||||
from oslo.config import cfg
|
||||
import pecan
|
||||
from pecan import hooks
|
||||
|
||||
from teeth_agent.api import config
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class AgentHook(hooks.PecanHook):
|
||||
def __init__(self, agent, *args, **kwargs):
|
||||
super(AgentHook, self).__init__(*args, **kwargs)
|
||||
self.agent = agent
|
||||
|
||||
def before(self, state):
|
||||
state.request.agent = self.agent
|
||||
|
||||
|
||||
def get_pecan_config():
|
||||
# Set up the pecan configuration
|
||||
filename = config.__file__.replace('.pyc', '.py')
|
||||
return pecan.configuration.conf_from_file(filename)
|
||||
|
||||
|
||||
def setup_app(pecan_config=None, extra_hooks=None):
|
||||
def setup_app(agent, pecan_config=None, extra_hooks=None):
|
||||
#policy.init()
|
||||
|
||||
#app_hooks = [hooks.ConfigHook(),
|
||||
@@ -39,6 +49,8 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||
#if extra_hooks:
|
||||
#app_hooks.extend(extra_hooks)
|
||||
|
||||
app_hooks = [AgentHook(agent)]
|
||||
|
||||
if not pecan_config:
|
||||
pecan_config = get_pecan_config()
|
||||
|
||||
@@ -50,7 +62,7 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||
debug=True,
|
||||
#debug=CONF.debug,
|
||||
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
||||
#hooks=app_hooks,
|
||||
hooks=app_hooks,
|
||||
#wrap_app=middleware.ParsableErrorMiddleware,
|
||||
)
|
||||
|
||||
@@ -58,9 +70,9 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||
|
||||
|
||||
class VersionSelectorApplication(object):
|
||||
def __init__(self):
|
||||
def __init__(self, agent):
|
||||
pc = get_pecan_config()
|
||||
self.v1 = setup_app(pecan_config=pc)
|
||||
self.v1 = setup_app(agent, pecan_config=pc)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
return self.v1(environ, start_response)
|
||||
|
||||
@@ -13,13 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
Version 1 of the Ironic API
|
||||
|
||||
NOTE: IN PROGRESS AND NOT FULLY IMPLEMENTED.
|
||||
|
||||
Should maintain feature parity with Nova Baremetal Extension.
|
||||
|
||||
Specification can be found at ironic/doc/api/v1.rst
|
||||
Version 1 of the Ironic Python Agent API
|
||||
"""
|
||||
|
||||
import pecan
|
||||
@@ -29,11 +23,8 @@ from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from teeth_agent.api.controllers.v1 import base
|
||||
#from ironic.api.controllers.v1 import chassis
|
||||
#from ironic.api.controllers.v1 import driver
|
||||
from teeth_agent.api.controllers.v1 import command
|
||||
from teeth_agent.api.controllers.v1 import link
|
||||
#from ironic.api.controllers.v1 import node
|
||||
#from ironic.api.controllers.v1 import port
|
||||
|
||||
|
||||
class MediaType(base.APIBase):
|
||||
@@ -59,17 +50,8 @@ class V1(base.APIBase):
|
||||
links = [link.Link]
|
||||
"Links that point to a specific URL for this version and documentation"
|
||||
|
||||
#chassis = [link.Link]
|
||||
#"Links to the chassis resource"
|
||||
|
||||
#nodes = [link.Link]
|
||||
#"Links to the nodes resource"
|
||||
|
||||
#ports = [link.Link]
|
||||
#"Links to the ports resource"
|
||||
|
||||
#drivers = [link.Link]
|
||||
#"Links to the drivers resource"
|
||||
commands = [link.Link]
|
||||
"Links to the command resource"
|
||||
|
||||
@classmethod
|
||||
def convert(self):
|
||||
@@ -84,46 +66,22 @@ class V1(base.APIBase):
|
||||
'api-spec-v1.html',
|
||||
bookmark=True, type='text/html')
|
||||
]
|
||||
v1.command = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'commands', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'commands', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.media_types = [MediaType('application/json',
|
||||
'application/vnd.openstack.ironic.v1+json')]
|
||||
#v1.chassis = [link.Link.make_link('self', pecan.request.host_url,
|
||||
#'chassis', ''),
|
||||
#link.Link.make_link('bookmark',
|
||||
#pecan.request.host_url,
|
||||
#'chassis', '',
|
||||
#bookmark=True)
|
||||
#]
|
||||
#v1.nodes = [link.Link.make_link('self', pecan.request.host_url,
|
||||
#'nodes', ''),
|
||||
#link.Link.make_link('bookmark',
|
||||
#pecan.request.host_url,
|
||||
#'nodes', '',
|
||||
#bookmark=True)
|
||||
#]
|
||||
#v1.ports = [link.Link.make_link('self', pecan.request.host_url,
|
||||
#'ports', ''),
|
||||
#link.Link.make_link('bookmark',
|
||||
#pecan.request.host_url,
|
||||
#'ports', '',
|
||||
#bookmark=True)
|
||||
#]
|
||||
#v1.drivers = [link.Link.make_link('self', pecan.request.host_url,
|
||||
#'drivers', ''),
|
||||
#link.Link.make_link('bookmark',
|
||||
#pecan.request.host_url,
|
||||
#'drivers', '',
|
||||
#bookmark=True)
|
||||
#]
|
||||
return v1
|
||||
|
||||
|
||||
class Controller(rest.RestController):
|
||||
"""Version 1 API controller root."""
|
||||
|
||||
#nodes = node.NodesController()
|
||||
#ports = port.PortsController()
|
||||
#chassis = chassis.ChassisController()
|
||||
#drivers = driver.DriversController()
|
||||
commands = command.CommandController()
|
||||
|
||||
@wsme_pecan.wsexpose(V1)
|
||||
def get(self):
|
||||
|
||||
@@ -12,36 +12,44 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
|
||||
class MultiType(wtypes.UserType):
|
||||
"""A complex type that represents one or more types.
|
||||
|
||||
Used for validating that a value is an instance of one of the types.
|
||||
|
||||
:param *types: Variable-length list of types.
|
||||
|
||||
"""
|
||||
def __init__(self, *types):
|
||||
self.types = types
|
||||
|
||||
def __str__(self):
|
||||
return ' | '.join(map(str, self.types))
|
||||
|
||||
def validate(self, value):
|
||||
for t in self.types:
|
||||
if t is wtypes.text and isinstance(value, wtypes.bytes):
|
||||
value = value.decode()
|
||||
if isinstance(value, t):
|
||||
return value
|
||||
else:
|
||||
raise ValueError(
|
||||
"Wrong type. Expected '{type}', got '{value}'".format(
|
||||
type=self.types, value=type(value)))
|
||||
|
||||
|
||||
json_type = MultiType(list, dict, six.integer_types, wtypes.text)
|
||||
|
||||
|
||||
class APIBase(wtypes.Base):
|
||||
|
||||
created_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"The time in UTC at which the object is created"
|
||||
|
||||
updated_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"The time in UTC at which the object is updated"
|
||||
|
||||
def as_dict(self):
|
||||
"""Render this object as a dict of its fields."""
|
||||
return dict((k, getattr(self, k))
|
||||
for k in self.fields
|
||||
if hasattr(self, k) and
|
||||
getattr(self, k) != wsme.Unset)
|
||||
|
||||
def unset_fields_except(self, except_list=None):
|
||||
"""Unset fields so they don't appear in the message body.
|
||||
|
||||
:param except_list: A list of fields that won't be touched.
|
||||
|
||||
"""
|
||||
if except_list is None:
|
||||
except_list = []
|
||||
|
||||
for k in self.as_dict():
|
||||
if k not in except_list:
|
||||
setattr(self, k, wsme.Unset)
|
||||
|
||||
88
teeth_agent/api/controllers/v1/command.py
Normal file
88
teeth_agent/api/controllers/v1/command.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
from wsme import types
|
||||
from wsmeext import pecan as wsme_pecan
|
||||
|
||||
from teeth_agent.api.controllers.v1 import base
|
||||
|
||||
|
||||
class CommandResult(base.APIBase):
|
||||
id = types.text
|
||||
command_name = types.text
|
||||
command_params = base.json_type
|
||||
command_status = types.text
|
||||
command_error = types.text
|
||||
command_result = types.text
|
||||
|
||||
@classmethod
|
||||
def from_result(cls, result):
|
||||
instance = cls()
|
||||
for field in ('id', 'command_name', 'command_params', 'command_status',
|
||||
'command_error', 'command_result'):
|
||||
setattr(instance, field, getattr(result, field))
|
||||
return instance
|
||||
|
||||
|
||||
class CommandResultList(base.APIBase):
|
||||
commands = [CommandResult]
|
||||
|
||||
@classmethod
|
||||
def from_results(cls, results):
|
||||
instance = cls()
|
||||
instance.commands = [CommandResult.from_result(result)
|
||||
for result in results]
|
||||
return instance
|
||||
|
||||
|
||||
class Command(base.APIBase):
|
||||
"""A command representation."""
|
||||
name = types.text
|
||||
params = base.json_type
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, obj):
|
||||
instance = cls()
|
||||
instance.name = obj['name']
|
||||
instance.params = obj['params']
|
||||
return instance
|
||||
|
||||
|
||||
class CommandController(rest.RestController):
|
||||
"""Controller for issuing commands and polling for command status."""
|
||||
|
||||
@wsme_pecan.wsexpose(CommandResultList)
|
||||
def get_all(self):
|
||||
agent = pecan.request.agent
|
||||
results = agent.list_command_results()
|
||||
return CommandResultList.from_results(results)
|
||||
|
||||
@wsme_pecan.wsexpose(CommandResult, types.text, types.text)
|
||||
def get_one(self, result_id, wait=False):
|
||||
agent = pecan.request.agent
|
||||
result = agent.get_command_result(result_id)
|
||||
|
||||
#if wait and wait.lower() == 'true':
|
||||
#result.join()
|
||||
|
||||
return CommandResult.from_result(result)
|
||||
|
||||
@wsme_pecan.wsexpose(CommandResult, body=Command)
|
||||
def post(self, command):
|
||||
agent = pecan.request.agent
|
||||
result = agent.execute_command(command.name, **command.params)
|
||||
return result
|
||||
@@ -112,7 +112,7 @@ class AsyncCommandResult(BaseCommandResult):
|
||||
e = errors.CommandExecutionError(str(e))
|
||||
|
||||
with self.command_state_lock:
|
||||
self.command_error = e
|
||||
self.command_error = '{}: {}'.format(e.message, e.details)
|
||||
self.command_status = AgentCommandStatus.FAILED
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user