Remove dependency on teeth-rest.
This commit: - Removes all references to teeth-rest. - Brings in encoding.py and errors.py from teeth-rest. - Removes the "view" thing from the encoding module. - Adds structlog as a dep. This was missing and overlooked because teeth-rest was installing it in the environment.
This commit is contained in:
parent
7ecb8978d0
commit
2c77d82204
@ -13,7 +13,7 @@ RUN apt-get update && apt-get -y install \
|
||||
# Install requirements separately, because pip understands a git+https url while setuptools doesn't
|
||||
RUN pip install -r /tmp/teeth-agent/requirements.txt
|
||||
|
||||
# This will succeed because all the dependencies (including pesky teeth_rest) were installed previously
|
||||
# This will succeed because all the dependencies were installed previously
|
||||
RUN pip install /tmp/teeth-agent
|
||||
|
||||
CMD [ "/usr/local/bin/teeth-agent" ]
|
||||
|
@ -1,8 +1,8 @@
|
||||
Werkzeug==0.9.4
|
||||
requests==2.0.0
|
||||
stevedore==0.14
|
||||
-e git+https://github.com/racker/teeth-rest.git@e876c0fddd5ce2f5223ab16936f711b0d57e19c4#egg=teeth_rest
|
||||
wsgiref>=0.1.2
|
||||
pecan>=0.4.5
|
||||
WSME>=0.6
|
||||
six>=1.5.2
|
||||
structlog==0.4.1
|
||||
|
@ -22,12 +22,11 @@ import time
|
||||
import pkg_resources
|
||||
from stevedore import driver
|
||||
import structlog
|
||||
from teeth_rest import encoding
|
||||
from teeth_rest import errors as rest_errors
|
||||
from wsgiref import simple_server
|
||||
|
||||
from teeth_agent.api import app
|
||||
from teeth_agent import base
|
||||
from teeth_agent import encoding
|
||||
from teeth_agent import errors
|
||||
from teeth_agent import hardware
|
||||
from teeth_agent import overlord_agent_api
|
||||
@ -39,7 +38,7 @@ class TeethAgentStatus(encoding.Serializable):
|
||||
self.started_at = started_at
|
||||
self.version = version
|
||||
|
||||
def serialize(self, view):
|
||||
def serialize(self):
|
||||
"""Turn the status into a dict."""
|
||||
return collections.OrderedDict([
|
||||
('mode', self.mode),
|
||||
@ -181,7 +180,7 @@ class TeethAgent(object):
|
||||
try:
|
||||
result = self.mode_implementation.execute(command_part,
|
||||
**kwargs)
|
||||
except rest_errors.InvalidContentError as e:
|
||||
except errors.InvalidContentError as e:
|
||||
# Any command may raise a InvalidContentError which will be
|
||||
# returned to the caller directly.
|
||||
raise e
|
||||
|
@ -18,9 +18,8 @@ import threading
|
||||
import uuid
|
||||
|
||||
import structlog
|
||||
from teeth_rest import encoding
|
||||
from teeth_rest import errors as rest_errors
|
||||
|
||||
from teeth_agent import encoding
|
||||
from teeth_agent import errors
|
||||
|
||||
|
||||
@ -39,7 +38,7 @@ class BaseCommandResult(encoding.Serializable):
|
||||
self.command_error = None
|
||||
self.command_result = None
|
||||
|
||||
def serialize(self, view):
|
||||
def serialize(self):
|
||||
return dict((
|
||||
(u'id', self.id),
|
||||
(u'command_name', self.command_name),
|
||||
@ -82,9 +81,9 @@ class AsyncCommandResult(BaseCommandResult):
|
||||
self.execution_thread = threading.Thread(target=self.run,
|
||||
name=thread_name)
|
||||
|
||||
def serialize(self, view):
|
||||
def serialize(self):
|
||||
with self.command_state_lock:
|
||||
return super(AsyncCommandResult, self).serialize(view)
|
||||
return super(AsyncCommandResult, self).serialize()
|
||||
|
||||
def start(self):
|
||||
self.execution_thread.start()
|
||||
@ -107,7 +106,7 @@ class AsyncCommandResult(BaseCommandResult):
|
||||
self.command_status = AgentCommandStatus.SUCCEEDED
|
||||
|
||||
except Exception as e:
|
||||
if not isinstance(e, rest_errors.RESTError):
|
||||
if not isinstance(e, errors.RESTError):
|
||||
e = errors.CommandExecutionError(str(e))
|
||||
|
||||
with self.command_state_lock:
|
||||
|
53
teeth_agent/encoding.py
Normal file
53
teeth_agent/encoding.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""
|
||||
Copyright 2013 Rackspace, Inc.
|
||||
|
||||
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 json
|
||||
import uuid
|
||||
|
||||
|
||||
class Serializable(object):
|
||||
"""Base class for things that can be serialized."""
|
||||
def serialize(self):
|
||||
"""Turn this object into a dict."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class RESTJSONEncoder(json.JSONEncoder):
|
||||
"""A slightly customized JSON encoder."""
|
||||
def encode(self, o):
|
||||
"""Turn an object into JSON.
|
||||
|
||||
Appends a newline to responses when configured to pretty-print,
|
||||
in order to make use of curl less painful from most shells.
|
||||
"""
|
||||
delimiter = ''
|
||||
|
||||
# if indent is None, newlines are still inserted, so we should too.
|
||||
if self.indent is not None:
|
||||
delimiter = '\n'
|
||||
|
||||
return super(RESTJSONEncoder, self).encode(o) + delimiter
|
||||
|
||||
def default(self, o):
|
||||
"""Turn an object into a serializable object. In particular, by
|
||||
calling :meth:`.Serializable.serialize`.
|
||||
"""
|
||||
if isinstance(o, Serializable):
|
||||
return o.serialize()
|
||||
elif isinstance(o, uuid.UUID):
|
||||
return str(o)
|
||||
else:
|
||||
return json.JSONEncoder.default(self, o)
|
@ -14,10 +14,50 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from teeth_rest import errors
|
||||
import collections
|
||||
|
||||
from teeth_agent import encoding
|
||||
|
||||
|
||||
class CommandExecutionError(errors.RESTError):
|
||||
class RESTError(Exception, encoding.Serializable):
|
||||
"""Base class for errors generated in teeth."""
|
||||
message = 'An error occurred'
|
||||
details = 'An unexpected error occurred. Please try back later.'
|
||||
status_code = 500
|
||||
|
||||
def serialize(self):
|
||||
"""Turn a RESTError into a dict."""
|
||||
return collections.OrderedDict([
|
||||
('type', self.__class__.__name__),
|
||||
('code', self.status_code),
|
||||
('message', self.message),
|
||||
('details', self.details),
|
||||
])
|
||||
|
||||
|
||||
class InvalidContentError(RESTError):
|
||||
"""Error which occurs when a user supplies invalid content, either
|
||||
because that content cannot be parsed according to the advertised
|
||||
`Content-Type`, or due to a content validation error.
|
||||
"""
|
||||
message = 'Invalid request body'
|
||||
status_code = 400
|
||||
|
||||
def __init__(self, details):
|
||||
self.details = details
|
||||
|
||||
|
||||
class NotFound(RESTError):
|
||||
"""Error which occurs when a user supplies invalid content, either
|
||||
because that content cannot be parsed according to the advertised
|
||||
`Content-Type`, or due to a content validation error.
|
||||
"""
|
||||
message = 'Not found'
|
||||
status_code = 404
|
||||
details = 'The requested URL was not found.'
|
||||
|
||||
|
||||
class CommandExecutionError(RESTError):
|
||||
"""Error raised when a command fails to execute."""
|
||||
|
||||
message = 'Command execution failed'
|
||||
@ -27,7 +67,7 @@ class CommandExecutionError(errors.RESTError):
|
||||
self.details = details
|
||||
|
||||
|
||||
class InvalidCommandError(errors.InvalidContentError):
|
||||
class InvalidCommandError(InvalidContentError):
|
||||
"""Error which is raised when an unknown command is issued."""
|
||||
|
||||
messsage = 'Invalid command'
|
||||
@ -36,7 +76,7 @@ class InvalidCommandError(errors.InvalidContentError):
|
||||
super(InvalidCommandError, self).__init__(details)
|
||||
|
||||
|
||||
class InvalidCommandParamsError(errors.InvalidContentError):
|
||||
class InvalidCommandParamsError(InvalidContentError):
|
||||
"""Error which is raised when command parameters are invalid."""
|
||||
|
||||
message = 'Invalid command parameters'
|
||||
@ -45,14 +85,14 @@ class InvalidCommandParamsError(errors.InvalidContentError):
|
||||
super(InvalidCommandParamsError, self).__init__(details)
|
||||
|
||||
|
||||
class RequestedObjectNotFoundError(errors.NotFound):
|
||||
class RequestedObjectNotFoundError(NotFound):
|
||||
def __init__(self, type_descr, obj_id):
|
||||
details = '{} with id {} not found.'.format(type_descr, obj_id)
|
||||
super(RequestedObjectNotFoundError, self).__init__(details)
|
||||
self.details = details
|
||||
|
||||
|
||||
class OverlordAPIError(errors.RESTError):
|
||||
class OverlordAPIError(RESTError):
|
||||
"""Error raised when a call to the agent API fails."""
|
||||
|
||||
message = 'Error in call to teeth-agent-api.'
|
||||
@ -71,7 +111,7 @@ class HeartbeatError(OverlordAPIError):
|
||||
super(HeartbeatError, self).__init__(details)
|
||||
|
||||
|
||||
class ImageDownloadError(errors.RESTError):
|
||||
class ImageDownloadError(RESTError):
|
||||
"""Error raised when an image cannot be downloaded."""
|
||||
|
||||
message = 'Error downloading image.'
|
||||
@ -81,7 +121,7 @@ class ImageDownloadError(errors.RESTError):
|
||||
self.details = 'Could not download image with id {}.'.format(image_id)
|
||||
|
||||
|
||||
class ImageChecksumError(errors.RESTError):
|
||||
class ImageChecksumError(RESTError):
|
||||
"""Error raised when an image fails to verify against its checksum."""
|
||||
|
||||
message = 'Error verifying image checksum.'
|
||||
@ -92,7 +132,7 @@ class ImageChecksumError(errors.RESTError):
|
||||
self.details = self.details.format(image_id)
|
||||
|
||||
|
||||
class ImageWriteError(errors.RESTError):
|
||||
class ImageWriteError(RESTError):
|
||||
"""Error raised when an image cannot be written to a device."""
|
||||
|
||||
message = 'Error writing image to device.'
|
||||
@ -103,7 +143,7 @@ class ImageWriteError(errors.RESTError):
|
||||
self.details = self.details.format(device, exit_code)
|
||||
|
||||
|
||||
class ConfigDriveWriteError(errors.RESTError):
|
||||
class ConfigDriveWriteError(RESTError):
|
||||
"""Error raised when a configdrive directory cannot be written to a
|
||||
device.
|
||||
"""
|
||||
@ -117,7 +157,7 @@ class ConfigDriveWriteError(errors.RESTError):
|
||||
self.details = details
|
||||
|
||||
|
||||
class SystemRebootError(errors.RESTError):
|
||||
class SystemRebootError(RESTError):
|
||||
"""Error raised when a system cannot reboot."""
|
||||
|
||||
message = 'Error rebooting system.'
|
||||
|
@ -22,7 +22,7 @@ import subprocess
|
||||
import stevedore
|
||||
import structlog
|
||||
|
||||
from teeth_rest import encoding
|
||||
from teeth_agent import encoding
|
||||
|
||||
_global_manager = None
|
||||
|
||||
@ -49,7 +49,7 @@ class HardwareInfo(encoding.Serializable):
|
||||
self.type = type
|
||||
self.id = id
|
||||
|
||||
def serialize(self, view):
|
||||
def serialize(self):
|
||||
return collections.OrderedDict([
|
||||
('type', self.type),
|
||||
('id', self.id),
|
||||
|
@ -38,7 +38,7 @@ def _format_event(logger, method, event):
|
||||
have enough keys to format.
|
||||
"""
|
||||
if 'event' not in event:
|
||||
# nothing to format, e.g. _log_request in teeth_rest/component
|
||||
# nothing to format
|
||||
return event
|
||||
# Get a list of fields that need to be filled.
|
||||
formatter = string.Formatter()
|
||||
|
@ -17,8 +17,8 @@ limitations under the License.
|
||||
import json
|
||||
|
||||
import requests
|
||||
from teeth_rest import encoding
|
||||
|
||||
from teeth_agent import encoding
|
||||
from teeth_agent import errors
|
||||
|
||||
|
||||
@ -28,8 +28,7 @@ class APIClient(object):
|
||||
def __init__(self, api_url):
|
||||
self.api_url = api_url.rstrip('/')
|
||||
self.session = requests.Session()
|
||||
self.encoder = encoding.RESTJSONEncoder(
|
||||
encoding.SerializationViews.PUBLIC)
|
||||
self.encoder = encoding.RESTJSONEncoder()
|
||||
|
||||
def _request(self, method, path, data=None):
|
||||
request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path)
|
||||
|
@ -22,10 +22,10 @@ import mock
|
||||
import pkg_resources
|
||||
from wsgiref import simple_server
|
||||
|
||||
from teeth_rest import encoding
|
||||
|
||||
from teeth_agent import agent
|
||||
from teeth_agent import base
|
||||
from teeth_agent import encoding
|
||||
from teeth_agent import errors
|
||||
from teeth_agent import hardware
|
||||
|
||||
@ -118,9 +118,7 @@ class TestHeartbeater(unittest.TestCase):
|
||||
|
||||
class TestBaseAgent(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.encoder = encoding.RESTJSONEncoder(
|
||||
encoding.SerializationViews.PUBLIC,
|
||||
indent=4)
|
||||
self.encoder = encoding.RESTJSONEncoder(indent=4)
|
||||
self.agent = agent.TeethAgent('https://fake_api.example.org:8081/',
|
||||
('localhost', 9999),
|
||||
'192.168.1.1')
|
||||
|
@ -22,7 +22,6 @@ import unittest
|
||||
from werkzeug import test
|
||||
from werkzeug import wrappers
|
||||
|
||||
from teeth_rest import encoding
|
||||
|
||||
from teeth_agent import agent
|
||||
from teeth_agent.api import app
|
||||
@ -99,7 +98,7 @@ class TestTeethAPI(unittest.TestCase):
|
||||
self.assertEqual(kwargs, {'key': 'value'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
expected_result = result.serialize(encoding.SerializationViews.PUBLIC)
|
||||
expected_result = result.serialize()
|
||||
self.assertEqual(data, expected_result)
|
||||
|
||||
def test_execute_agent_command_validation(self):
|
||||
@ -150,7 +149,7 @@ class TestTeethAPI(unittest.TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(json.loads(response.data), {
|
||||
u'commands': [
|
||||
cmd_result.serialize(None),
|
||||
cmd_result.serialize(),
|
||||
],
|
||||
})
|
||||
|
||||
@ -160,8 +159,7 @@ class TestTeethAPI(unittest.TestCase):
|
||||
True,
|
||||
{'test': 'result'})
|
||||
|
||||
serialized_cmd_result = cmd_result.serialize(
|
||||
encoding.SerializationViews.PUBLIC)
|
||||
serialized_cmd_result = cmd_result.serialize()
|
||||
|
||||
mock_agent = mock.create_autospec(agent.TeethAgent)
|
||||
mock_agent.get_command_result.return_value = cmd_result
|
||||
|
Loading…
Reference in New Issue
Block a user