Compatibility fixes for Python 3.3

1) Added a py33 environment to tox
2) Updated tests to mock the correctly named builtins.open based on
python version
3) Other minor compatibility fixes

Tests for Python 3.3 will not pass due to this bug:
https://github.com/eventlet/eventlet/issues/83 among possibly others in
eventlet.

Change-Id: Ie4b512a926fa690ee77a71a89851c871ea1f6be0
This commit is contained in:
Jay Faulkner 2014-04-04 13:30:42 -07:00
parent cae81837ce
commit c121bef9f0
11 changed files with 68 additions and 32 deletions

View File

@ -19,6 +19,7 @@ import threading
import time import time
import pkg_resources import pkg_resources
import six
from stevedore import extension from stevedore import extension
from wsgiref import simple_server from wsgiref import simple_server
@ -148,7 +149,7 @@ class IronicPythonAgent(object):
return self.node['uuid'] return self.node['uuid']
def list_command_results(self): def list_command_results(self):
return self.command_results.values() return list(self.command_results.values())
def get_command_result(self, result_id): def get_command_result(self, result_id):
try: try:
@ -171,7 +172,7 @@ class IronicPythonAgent(object):
extension_part, command_part = self._split_command(command_name) extension_part, command_part = self._split_command(command_name)
if len(self.command_results) > 0: if len(self.command_results) > 0:
last_command = self.command_results.values()[-1] last_command = list(self.command_results.values())[-1]
if not last_command.is_done(): if not last_command.is_done():
raise errors.CommandExecutionError('agent is busy') raise errors.CommandExecutionError('agent is busy')
@ -192,7 +193,7 @@ class IronicPythonAgent(object):
result = base.SyncCommandResult(command_name, result = base.SyncCommandResult(command_name,
kwargs, kwargs,
False, False,
unicode(e)) six.text_type(e))
self.command_results[result.id] = result self.command_results[result.id] = result
return result return result

View File

@ -17,6 +17,7 @@ limitations under the License.
import threading import threading
import uuid import uuid
import six
from ironic_python_agent import encoding from ironic_python_agent import encoding
from ironic_python_agent import errors from ironic_python_agent import errors
@ -31,7 +32,7 @@ class AgentCommandStatus(object):
class BaseCommandResult(encoding.Serializable): class BaseCommandResult(encoding.Serializable):
def __init__(self, command_name, command_params): def __init__(self, command_name, command_params):
self.id = unicode(uuid.uuid4()) self.id = six.text_type(uuid.uuid4())
self.command_name = command_name self.command_name = command_name
self.command_params = command_params self.command_params = command_params
self.command_status = AgentCommandStatus.RUNNING self.command_status = AgentCommandStatus.RUNNING

View File

@ -38,13 +38,13 @@ class ConfigDriveWriter(object):
os.makedirs(os.path.join(location, prefix, 'content')) os.makedirs(os.path.join(location, prefix, 'content'))
metadata = {} metadata = {}
for k, v in self.metadata.iteritems(): for k, v in self.metadata.items():
metadata[k] = v metadata[k] = v
if self.files: if self.files:
metadata['files'] = [] metadata['files'] = []
filenumber = 0 filenumber = 0
for filepath, contents in self.files.iteritems(): for filepath, contents in self.files.items():
content_path = '/content/{0:04}'.format(filenumber) content_path = '/content/{0:04}'.format(filenumber)
file_info = { file_info = {
'content_path': content_path, 'content_path': content_path,
@ -72,10 +72,10 @@ def write_configdrive(location, metadata, files, prefix='openstack',
""" """
writer = ConfigDriveWriter() writer = ConfigDriveWriter()
for k, v in metadata.iteritems(): for k, v in metadata.items():
writer.add_metadata(k, v) writer.add_metadata(k, v)
for path, b64_contents in files.iteritems(): for path, b64_contents in files.items():
contents = base64.b64decode(b64_contents) contents = base64.b64decode(b64_contents)
writer.add_file(path, contents) writer.add_file(path, contents)

View File

@ -15,9 +15,11 @@ limitations under the License.
""" """
import abc import abc
import functools
import os import os
import subprocess import subprocess
import six
import stevedore import stevedore
from ironic_python_agent import encoding from ironic_python_agent import encoding
@ -169,7 +171,15 @@ def get_manager():
# There will always be at least one extension available (the # There will always be at least one extension available (the
# GenericHardwareManager). # GenericHardwareManager).
preferred_extension = sorted(extension_manager, _compare_extensions)[0] if six.PY2:
preferred_extension = sorted(
extension_manager,
_compare_extensions)[0]
else:
preferred_extension = sorted(
extension_manager,
key=functools.cmp_to_key(_compare_extensions))[0]
preferred_manager = preferred_extension.obj preferred_manager = preferred_extension.obj
if preferred_manager.evaluate_hardware_support() <= 0: if preferred_manager.evaluate_hardware_support() <= 0:

View File

@ -118,7 +118,7 @@ def _download_image(image_info):
def _verify_image(image_info, image_location): def _verify_image(image_info, image_location):
hashes = image_info['hashes'] hashes = image_info['hashes']
for k, v in hashes.iteritems(): for k, v in hashes.items():
algo = getattr(hashlib, k, None) algo = getattr(hashlib, k, None)
if algo is None: if algo is None:
continue continue

View File

@ -20,6 +20,7 @@ import time
import mock import mock
from oslotest import base as test_base from oslotest import base as test_base
import pkg_resources import pkg_resources
import six
from stevedore import extension from stevedore import extension
from wsgiref import simple_server from wsgiref import simple_server
@ -32,6 +33,11 @@ from ironic_python_agent import hardware
EXPECTED_ERROR = RuntimeError('command execution failed') EXPECTED_ERROR = RuntimeError('command execution failed')
if six.PY2:
OPEN_FUNCTION_NAME = '__builtin__.open'
else:
OPEN_FUNCTION_NAME = 'builtins.open'
def foo_execute(*args, **kwargs): def foo_execute(*args, **kwargs):
if kwargs['fail']: if kwargs['fail']:
@ -248,13 +254,13 @@ class TestBaseAgent(test_base.BaseTestCase):
class TestAgentCmd(test_base.BaseTestCase): class TestAgentCmd(test_base.BaseTestCase):
@mock.patch('ironic_python_agent.openstack.common.log.getLogger') @mock.patch('ironic_python_agent.openstack.common.log.getLogger')
@mock.patch('__builtin__.open') @mock.patch(OPEN_FUNCTION_NAME)
def test__get_kernel_params_fail(self, logger_mock, open_mock): def test__get_kernel_params_fail(self, logger_mock, open_mock):
open_mock.side_effect = Exception open_mock.side_effect = Exception
params = agent_cmd._get_kernel_params() params = agent_cmd._get_kernel_params()
self.assertEqual(params, {}) self.assertEqual(params, {})
@mock.patch('__builtin__.open') @mock.patch(OPEN_FUNCTION_NAME)
def test__get_kernel_params(self, open_mock): def test__get_kernel_params(self, open_mock):
kernel_line = 'api-url=http://localhost:9999 baz foo=bar\n' kernel_line = 'api-url=http://localhost:9999 baz foo=bar\n'
open_mock.return_value.__enter__ = lambda s: s open_mock.return_value.__enter__ = lambda s: s

View File

@ -21,10 +21,16 @@ import json
import mock import mock
from oslotest import base as test_base from oslotest import base as test_base
import six
from ironic_python_agent import configdrive from ironic_python_agent import configdrive
from ironic_python_agent import utils from ironic_python_agent import utils
if six.PY2:
OPEN_FUNCTION_NAME = '__builtin__.open'
else:
OPEN_FUNCTION_NAME = 'builtins.open'
class ConfigDriveWriterTestCase(test_base.BaseTestCase): class ConfigDriveWriterTestCase(test_base.BaseTestCase):
def setUp(self): def setUp(self):
@ -43,12 +49,12 @@ class ConfigDriveWriterTestCase(test_base.BaseTestCase):
self.assertEqual(files, {'/etc/filename': 'contents'}) self.assertEqual(files, {'/etc/filename': 'contents'})
@mock.patch('os.makedirs', autospec=True) @mock.patch('os.makedirs', autospec=True)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
def test_write_no_files(self, open_mock, makedirs_mock): def test_write_no_files(self, open_mock, makedirs_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'} metadata = {'admin_pass': 'password', 'hostname': 'test'}
json_metadata = json.dumps(metadata) json_metadata = json.dumps(metadata)
metadata_path = '/lol/ironic/latest/meta_data.json' metadata_path = '/lol/ironic/latest/meta_data.json'
for k, v in metadata.iteritems(): for k, v in metadata.items():
self.writer.add_metadata(k, v) self.writer.add_metadata(k, v)
open_mock.return_value.__enter__ = lambda s: s open_mock.return_value.__enter__ = lambda s: s
@ -65,16 +71,16 @@ class ConfigDriveWriterTestCase(test_base.BaseTestCase):
self.assertEqual(makedirs_calls, makedirs_mock.call_args_list) self.assertEqual(makedirs_calls, makedirs_mock.call_args_list)
@mock.patch('os.makedirs', autospec=True) @mock.patch('os.makedirs', autospec=True)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
def test_write_with_files(self, open_mock, makedirs_mock): def test_write_with_files(self, open_mock, makedirs_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'} metadata = {'admin_pass': 'password', 'hostname': 'test'}
for k, v in metadata.iteritems(): for k, v in metadata.items():
self.writer.add_metadata(k, v) self.writer.add_metadata(k, v)
files = utils.get_ordereddict([ files = utils.get_ordereddict([
('/etc/conf0', 'contents0'), ('/etc/conf0', 'contents0'),
('/etc/conf1', 'contents1'), ('/etc/conf1', 'contents1'),
]) ])
for path, contents in files.iteritems(): for path, contents in files.items():
self.writer.add_file(path, contents) self.writer.add_file(path, contents)
open_mock.return_value.__enter__ = lambda s: s open_mock.return_value.__enter__ = lambda s: s
@ -117,12 +123,12 @@ class ConfigDriveWriterTestCase(test_base.BaseTestCase):
self.assertEqual(makedirs_calls, makedirs_mock.call_args_list) self.assertEqual(makedirs_calls, makedirs_mock.call_args_list)
@mock.patch('os.makedirs', autospec=True) @mock.patch('os.makedirs', autospec=True)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
def test_write_configdrive(self, open_mock, makedirs_mock): def test_write_configdrive(self, open_mock, makedirs_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'} metadata = {'admin_pass': 'password', 'hostname': 'test'}
files = utils.get_ordereddict([ files = utils.get_ordereddict([
('/etc/conf0', base64.b64encode('contents0')), ('/etc/conf0', base64.b64encode(b'contents0')),
('/etc/conf1', base64.b64encode('contents1')), ('/etc/conf1', base64.b64encode(b'contents1')),
]) ])
metadata['files'] = [ metadata['files'] = [
{'content_path': '/content/0000', 'path': '/etc/conf0'}, {'content_path': '/content/0000', 'path': '/etc/conf0'},
@ -148,10 +154,10 @@ class ConfigDriveWriterTestCase(test_base.BaseTestCase):
open_calls = [ open_calls = [
mock.call('/lol/openstack/content/0000', 'wb'), mock.call('/lol/openstack/content/0000', 'wb'),
mock.call().write('contents0'), mock.call().write(b'contents0'),
mock.call().__exit__(None, None, None), mock.call().__exit__(None, None, None),
mock.call('/lol/openstack/content/0001', 'wb'), mock.call('/lol/openstack/content/0001', 'wb'),
mock.call().write('contents1'), mock.call().write(b'contents1'),
mock.call().__exit__(None, None, None), mock.call().__exit__(None, None, None),
mock.call('/lol/openstack/latest/meta_data.json', 'wb'), mock.call('/lol/openstack/latest/meta_data.json', 'wb'),
# already checked # already checked

View File

@ -16,9 +16,15 @@ limitations under the License.
import mock import mock
from oslotest import base as test_base from oslotest import base as test_base
import six
from ironic_python_agent import hardware from ironic_python_agent import hardware
if six.PY2:
OPEN_FUNCTION_NAME = '__builtin__.open'
else:
OPEN_FUNCTION_NAME = 'builtins.open'
class TestGenericHardwareManager(test_base.BaseTestCase): class TestGenericHardwareManager(test_base.BaseTestCase):
def setUp(self): def setUp(self):
@ -27,7 +33,7 @@ class TestGenericHardwareManager(test_base.BaseTestCase):
@mock.patch('os.listdir') @mock.patch('os.listdir')
@mock.patch('os.path.exists') @mock.patch('os.path.exists')
@mock.patch('__builtin__.open') @mock.patch(OPEN_FUNCTION_NAME)
def test_list_network_interfaces(self, def test_list_network_interfaces(self,
mocked_open, mocked_open,
mocked_exists, mocked_exists,

View File

@ -16,10 +16,16 @@ limitations under the License.
import mock import mock
from oslotest import base as test_base from oslotest import base as test_base
import six
from ironic_python_agent import errors from ironic_python_agent import errors
from ironic_python_agent import standby from ironic_python_agent import standby
if six.PY2:
OPEN_FUNCTION_NAME = '__builtin__.open'
else:
OPEN_FUNCTION_NAME = 'builtins.open'
class TestStandbyExtension(test_base.BaseTestCase): class TestStandbyExtension(test_base.BaseTestCase):
def setUp(self): def setUp(self):
@ -101,7 +107,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
location = standby._image_location(image_info) location = standby._image_location(image_info)
self.assertEqual(location, '/tmp/fake_id') self.assertEqual(location, '/tmp/fake_id')
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('subprocess.call', autospec=True) @mock.patch('subprocess.call', autospec=True)
def test_write_image(self, call_mock, open_mock): def test_write_image(self, call_mock, open_mock):
image_info = self._build_fake_image_info() image_info = self._build_fake_image_info()
@ -124,7 +130,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
call_mock.assert_called_once_with(command) call_mock.assert_called_once_with(command)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('subprocess.call', autospec=True) @mock.patch('subprocess.call', autospec=True)
def test_copy_configdrive_to_disk(self, call_mock, open_mock): def test_copy_configdrive_to_disk(self, call_mock, open_mock):
device = '/dev/sda' device = '/dev/sda'
@ -147,7 +153,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
call_mock.assert_called_once_with(command) call_mock.assert_called_once_with(command)
@mock.patch('hashlib.md5', autospec=True) @mock.patch('hashlib.md5', autospec=True)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('requests.get', autospec=True) @mock.patch('requests.get', autospec=True)
def test_download_image(self, requests_mock, open_mock, md5_mock): def test_download_image(self, requests_mock, open_mock, md5_mock):
image_info = self._build_fake_image_info() image_info = self._build_fake_image_info()
@ -159,7 +165,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
read_mock = open_mock.return_value.read read_mock = open_mock.return_value.read
read_mock.return_value = 'content' read_mock.return_value = 'content'
hexdigest_mock = md5_mock.return_value.hexdigest hexdigest_mock = md5_mock.return_value.hexdigest
hexdigest_mock.return_value = image_info['hashes'].values()[0] hexdigest_mock.return_value = list(image_info['hashes'].values())[0]
standby._download_image(image_info) standby._download_image(image_info)
requests_mock.assert_called_once_with(image_info['urls'][0], requests_mock.assert_called_once_with(image_info['urls'][0],
@ -179,7 +185,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
image_info) image_info)
@mock.patch('ironic_python_agent.standby._verify_image', autospec=True) @mock.patch('ironic_python_agent.standby._verify_image', autospec=True)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('requests.get', autospec=True) @mock.patch('requests.get', autospec=True)
def test_download_image_verify_fails(self, requests_mock, open_mock, def test_download_image_verify_fails(self, requests_mock, open_mock,
verify_mock): verify_mock):
@ -191,7 +197,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
standby._download_image, standby._download_image,
image_info) image_info)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('hashlib.sha1', autospec=True) @mock.patch('hashlib.sha1', autospec=True)
@mock.patch('hashlib.md5', autospec=True) @mock.patch('hashlib.md5', autospec=True)
def test_verify_image_success(self, md5_mock, sha1_mock, open_mock): def test_verify_image_success(self, md5_mock, sha1_mock, open_mock):
@ -208,7 +214,7 @@ class TestStandbyExtension(test_base.BaseTestCase):
# make sure we only check one hash, even though both are valid # make sure we only check one hash, even though both are valid
self.assertEqual(md5_mock.call_count + sha1_mock.call_count, 1) self.assertEqual(md5_mock.call_count + sha1_mock.call_count, 1)
@mock.patch('__builtin__.open', autospec=True) @mock.patch(OPEN_FUNCTION_NAME, autospec=True)
@mock.patch('hashlib.md5', autospec=True) @mock.patch('hashlib.md5', autospec=True)
def test_verify_image_failure(self, md5_mock, open_mock): def test_verify_image_failure(self, md5_mock, open_mock):
image_info = self._build_fake_image_info() image_info = self._build_fake_image_info()

View File

@ -15,7 +15,6 @@ limitations under the License.
""" """
import collections import collections
import ordereddict
from ironic_python_agent.openstack.common import gettextutils as gtu from ironic_python_agent.openstack.common import gettextutils as gtu
from ironic_python_agent.openstack.common import log as logging from ironic_python_agent.openstack.common import log as logging
@ -29,6 +28,7 @@ def get_ordereddict(*args, **kwargs):
try: try:
return collections.OrderedDict(*args, **kwargs) return collections.OrderedDict(*args, **kwargs)
except AttributeError: except AttributeError:
import ordereddict
return ordereddict.OrderedDict(*args, **kwargs) return ordereddict.OrderedDict(*args, **kwargs)

View File

@ -8,4 +8,4 @@ eventlet>=0.13.0
oslo.config>=1.2.0 oslo.config>=1.2.0
Babel>=1.3 Babel>=1.3
iso8601>=0.1.9 iso8601>=0.1.9
oslotest==1.0 oslotest==1.0