Merge branch 'master' into remove-werkzeug
This commit is contained in:
commit
bb0a406a85
|
@ -9,6 +9,7 @@ ChangeLog
|
|||
devenv/*
|
||||
.coverage
|
||||
coverage.xml
|
||||
.testrepository
|
||||
imagebuild/coreos/build
|
||||
imagebuild/coreos/dist
|
||||
imagebuild/coreos/oem/authorized_keys
|
||||
|
|
|
@ -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
|
13
.travis.yml
13
.travis.yml
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
16
setup.cfg
16
setup.cfg
|
@ -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
|
|
@ -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."""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
"""
|
|
@ -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']:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')),
|
||||
])
|
||||
|
|
|
@ -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)
|
|
@ -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
43
tox.ini
|
@ -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
|
Loading…
Reference in New Issue