Merge branch 'master' into remove-werkzeug

This commit is contained in:
Jim Rollenhagen 2014-03-18 15:06:34 -07:00
commit bb0a406a85
19 changed files with 143 additions and 62 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ ChangeLog
devenv/*
.coverage
coverage.xml
.testrepository
imagebuild/coreos/build
imagebuild/coreos/dist
imagebuild/coreos/oem/authorized_keys

4
.testr.conf Normal file
View File

@ -0,0 +1,4 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 ${PYTHON:-python} -m subunit.run discover -s teeth_agent/tests/ -p "*.py" $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,12 +1,15 @@
language: python
python: 2.7
env:
- TOX_ENV=py26
- TOX_ENV=py27
- TOX_ENV=pep8
install:
- pip install tox
- pip install tox
script:
- tox
branches:
only:
- master
- tox -vv -e $TOX_ENV
notifications:
irc:
channels:

View File

@ -1,8 +0,0 @@
pep257==0.2.4
plumbum==1.3.0
pep8==1.4.6
pyflakes==0.7.3
junitxml==0.7
python-subunit==0.0.15
mock==1.0.1
coverage==3.6

View File

@ -1,5 +1,6 @@
requests==2.0.0
stevedore==0.14
ordereddict>=1.1
wsgiref>=0.1.2
-e git+https://github.com/jimrollenhagen/pecan@7c8db9bca2daf33a4616f0308db75a0c0d55d015#egg=pecan
WSME>=0.6

View File

@ -24,3 +24,19 @@ teeth_agent.modes =
teeth_agent.hardware_managers =
generic = teeth_agent.hardware:GenericHardwareManager
[pbr]
autodoc_index_modules = True
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
[wheel]
universal = 1

View File

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import collections
import random
import threading
import time
@ -30,6 +29,7 @@ from teeth_agent import errors
from teeth_agent import hardware
from teeth_agent.openstack.common import log
from teeth_agent import overlord_agent_api
from teeth_agent import utils
def _time():
@ -45,7 +45,7 @@ class TeethAgentStatus(encoding.Serializable):
def serialize(self):
"""Turn the status into a dict."""
return collections.OrderedDict([
return utils.get_ordereddict([
('mode', self.mode),
('started_at', self.started_at),
('version', self.version),
@ -119,7 +119,7 @@ class TeethAgent(object):
self.mode_implementation = None
self.version = pkg_resources.get_distribution('teeth-agent').version
self.api = app.VersionSelectorApplication(self)
self.command_results = collections.OrderedDict()
self.command_results = utils.get_ordereddict()
self.heartbeater = TeethAgentHeartbeater(self)
self.hardware = hardware.get_manager()
self.command_lock = threading.Lock()
@ -167,10 +167,10 @@ class TeethAgent(object):
self.mode_implementation = _load_mode_implementation(mode_name)
except Exception:
raise errors.InvalidCommandError(
'Unknown mode: {}'.format(mode_name))
'Unknown mode: {0}'.format(mode_name))
elif self.get_mode_name().lower() != mode_name:
raise errors.InvalidCommandError(
'Agent is already in {} mode'.format(self.get_mode_name()))
'Agent is already in {0} mode'.format(self.get_mode_name()))
def execute_command(self, command_name, **kwargs):
"""Execute an agent command."""

View File

@ -77,7 +77,7 @@ class AsyncCommandResult(BaseCommandResult):
self.execute_method = execute_method
self.command_state_lock = threading.Lock()
thread_name = 'agent-command-{}'.format(self.id)
thread_name = 'agent-command-{0}'.format(self.id)
self.execution_thread = threading.Thread(target=self.run,
name=thread_name)
@ -124,7 +124,7 @@ class BaseAgentMode(object):
def execute(self, command_name, **kwargs):
if command_name not in self.command_map:
raise errors.InvalidCommandError(
'Unknown command: {}'.format(command_name))
'Unknown command: {0}'.format(command_name))
result = self.command_map[command_name](command_name, **kwargs)

View File

@ -15,15 +15,15 @@ limitations under the License.
"""
import base64
import collections
import json
import os
from teeth_agent import utils
class ConfigDriveWriter(object):
def __init__(self):
self.metadata = {}
self.files = collections.OrderedDict()
self.files = utils.get_ordereddict()
def add_metadata(self, key, value):
self.metadata[key] = value
@ -43,7 +43,7 @@ class ConfigDriveWriter(object):
metadata['files'] = []
filenumber = 0
for filepath, contents in self.files.iteritems():
content_path = '/content/{:04}'.format(filenumber)
content_path = '/content/{0:04}'.format(filenumber)
file_info = {
'content_path': content_path,
'path': filepath
@ -57,7 +57,7 @@ class ConfigDriveWriter(object):
filenumber += 1
json_metadata = json.dumps(metadata)
metadata_path = '{}/{}/meta_data.json'.format(prefix, version)
metadata_path = '{0}/{1}/meta_data.json'.format(prefix, version)
metadata_path = os.path.join(location, metadata_path)
with open(metadata_path, 'wb') as f:
f.write(json_metadata)

View File

@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
"""
import collections
from teeth_agent import encoding
from teeth_agent import utils
class RESTError(Exception, encoding.Serializable):
@ -27,7 +26,7 @@ class RESTError(Exception, encoding.Serializable):
def serialize(self):
"""Turn a RESTError into a dict."""
return collections.OrderedDict([
return utils.get_ordereddict([
('type', self.__class__.__name__),
('code', self.status_code),
('message', self.message),
@ -87,7 +86,7 @@ class InvalidCommandParamsError(InvalidContentError):
class RequestedObjectNotFoundError(NotFound):
def __init__(self, type_descr, obj_id):
details = '{} with id {} not found.'.format(type_descr, obj_id)
details = '{0} with id {1} not found.'.format(type_descr, obj_id)
super(RequestedObjectNotFoundError, self).__init__(details)
self.details = details
@ -118,7 +117,7 @@ class ImageDownloadError(RESTError):
def __init__(self, image_id):
super(ImageDownloadError, self).__init__()
self.details = 'Could not download image with id {}.'.format(image_id)
self.details = 'Could not download image with id {0}.'.format(image_id)
class ImageChecksumError(RESTError):
@ -128,7 +127,7 @@ class ImageChecksumError(RESTError):
def __init__(self, image_id):
super(ImageChecksumError, self).__init__()
self.details = 'Image with id {} failed to verify against checksum.'
self.details = 'Image with id {0} failed to verify against checksum.'
self.details = self.details.format(image_id)
@ -139,7 +138,7 @@ class ImageWriteError(RESTError):
def __init__(self, exit_code, device):
super(ImageWriteError, self).__init__()
self.details = 'Writing image to device {} failed with exit code {}.'
self.details = 'Writing image to device {0} failed with exit code {1}.'
self.details = self.details.format(device, exit_code)
@ -151,7 +150,8 @@ class ConfigDriveWriteError(RESTError):
message = 'Error writing configdrive to device.'
def __init__(self, exit_code, device):
details = 'Writing configdrive to device {} failed with exit code {}.'
details = 'Writing configdrive to device {0} failed with exit code ' \
'{1}.'
details = details.format(device, exit_code)
super(ConfigDriveWriteError, self).__init__(details)
self.details = details
@ -164,5 +164,5 @@ class SystemRebootError(RESTError):
def __init__(self, exit_code):
super(SystemRebootError, self).__init__()
self.details = 'Reboot script failed with exit code {}.'
self.details = 'Reboot script failed with exit code {0}.'
self.details = self.details.format(exit_code)

View File

@ -15,7 +15,6 @@ limitations under the License.
"""
import abc
import collections
import os
import subprocess
@ -23,6 +22,7 @@ import stevedore
from teeth_agent import encoding
from teeth_agent.openstack.common import log
from teeth_agent import utils
_global_manager = None
@ -50,7 +50,7 @@ class HardwareInfo(encoding.Serializable):
self.id = id
def serialize(self):
return collections.OrderedDict([
return utils.get_ordereddict([
('type', self.type),
('id', self.id),
])
@ -104,19 +104,19 @@ class GenericHardwareManager(HardwareManager):
return HardwareSupport.GENERIC
def _get_interface_info(self, interface_name):
addr_path = '{}/class/net/{}/address'.format(self.sys_path,
addr_path = '{0}/class/net/{1}/address'.format(self.sys_path,
interface_name)
addr_file = open(addr_path, 'r')
mac_addr = addr_file.read().strip()
return NetworkInterface(interface_name, mac_addr)
def _is_device(self, interface_name):
device_path = '{}/class/net/{}/device'.format(self.sys_path,
device_path = '{0}/class/net/{1}/device'.format(self.sys_path,
interface_name)
return os.path.exists(device_path)
def list_network_interfaces(self):
iface_names = os.listdir('{}/class/net'.format(self.sys_path))
iface_names = os.listdir('{0}/class/net'.format(self.sys_path))
return [self._get_interface_info(name)
for name in iface_names
if self._is_device(name)]

View File

@ -61,7 +61,7 @@ class APIClient(object):
raise errors.HeartbeatError(str(e))
if response.status_code != requests.codes.NO_CONTENT:
msg = 'Invalid status code: {}'.format(response.status_code)
msg = 'Invalid status code: {0}'.format(response.status_code)
raise errors.HeartbeatError(msg)
try:
@ -79,7 +79,7 @@ class APIClient(object):
response = self._request('GET', path)
if response.status_code != requests.codes.OK:
msg = 'Invalid status code: {}'.format(response.status_code)
msg = 'Invalid status code: {0}'.format(response.status_code)
raise errors.OverlordAPIError(msg)
try:

View File

@ -0,0 +1,15 @@
"""
Copyright 2014 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.
"""

View File

@ -35,7 +35,7 @@ def _configdrive_location():
def _image_location(image_info):
return '/tmp/{}'.format(image_info['id'])
return '/tmp/{0}'.format(image_info['id'])
def _path_to_script(script):
@ -140,7 +140,7 @@ def _validate_image_info(image_info=None, **kwargs):
for field in ['id', 'urls', 'hashes']:
if field not in image_info:
msg = 'Image is missing \'{}\' field.'.format(field)
msg = 'Image is missing \'{0}\' field.'.format(field)
raise errors.InvalidCommandParamsError(msg)
if type(image_info['urls']) != list or not image_info['urls']:

View File

@ -136,7 +136,7 @@ class TestBaseAgent(unittest.TestCase):
self.agent.started_at = started_at
status = self.agent.get_status()
self.assertIsInstance(status, agent.TeethAgentStatus)
self.assertTrue(isinstance(status, agent.TeethAgentStatus))
self.assertEqual(status.started_at, started_at)
self.assertEqual(status.version,
pkg_resources.get_distribution('teeth-agent').version)

View File

@ -17,12 +17,12 @@ limitations under the License.
from __future__ import unicode_literals
import base64
import collections
import json
import mock
import unittest
from teeth_agent import configdrive
from teeth_agent import utils
class ConfigDriveWriterTestCase(unittest.TestCase):
@ -68,7 +68,7 @@ class ConfigDriveWriterTestCase(unittest.TestCase):
metadata = {'admin_pass': 'password', 'hostname': 'test'}
for k, v in metadata.iteritems():
self.writer.add_metadata(k, v)
files = collections.OrderedDict([
files = utils.get_ordereddict([
('/etc/conf0', 'contents0'),
('/etc/conf1', 'contents1'),
])
@ -118,7 +118,7 @@ class ConfigDriveWriterTestCase(unittest.TestCase):
@mock.patch('__builtin__.open', autospec=True)
def test_write_configdrive(self, open_mock, makedirs_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'}
files = collections.OrderedDict([
files = utils.get_ordereddict([
('/etc/conf0', base64.b64encode('contents0')),
('/etc/conf1', base64.b64encode('contents1')),
])

25
teeth_agent/utils.py Normal file
View File

@ -0,0 +1,25 @@
"""
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 collections
import ordereddict
def get_ordereddict(*args, **kwargs):
"""A fix for py26 not having ordereddict."""
try:
return collections.OrderedDict(*args, **kwargs)
except AttributeError:
return ordereddict.OrderedDict(*args, **kwargs)

View File

@ -1,5 +1,8 @@
mock
httmock
coverage
nose
hacking
hacking>=0.8.0,<0.9
coverage>=3.6
discover
mock>=1.0
testrepository>=0.0.18
testtools>=0.9.34
python-subunit>=0.0.18
httmock

43
tox.ini
View File

@ -1,21 +1,42 @@
[tox]
envlist = flake8, unit
minversion = 1.6
skipsdist = True
envlist = py26,py27,pep8
[testenv]
deps =
-rrequirements.txt
-rtest-requirements.txt
[testenv:flake8]
commands = flake8 teeth_agent
[testenv:unit]
usedevelop = True
install_command = pip install --allow-external -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands =
nosetests --all-modules --with-coverage --cover-package=teeth_agent --cover-xml teeth_agent
python setup.py testr --slowest --testr-args='{posargs:}'
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:pep8]
commands =
flake8 {posargs:teeth_agent}
[testenv:cover]
setenv = VIRTUAL_ENV={envdir}
commands =
python setup.py testr --coverage {posargs:teeth_agent}
[testenv:venv]
commands = {posargs:}
[testenv:devenv]
envdir = devenv
usedevelop = True
[flake8]
exclude=*openstack/common*
# E711: ignored because it is normal to use "column == None" in sqlalchemy
ignore = E12,E711
builtins = _
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,*ironic/nova*,tools
[hacking]
import_exceptions = ironic.openstack.common.gettextutils._,testtools.matchers