move everything into pecan

This commit is contained in:
Jim Rollenhagen
2014-03-11 14:13:13 -07:00
parent 1c2389b99f
commit 4f8bce3726
7 changed files with 153 additions and 83 deletions

View File

@@ -6,3 +6,4 @@ wsgiref>=0.1.2
pecan>=0.4.5
oslo.config>=1.2.0
WSME>=0.6
six>=1.5.2

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)

View 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

View File

@@ -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