write configdrive to disk from metadata

This commit is contained in:
Jim Rollenhagen 2014-01-21 14:25:45 -08:00
parent 7bf6a8e1cc
commit b1c3ba28fa
4 changed files with 123 additions and 85 deletions

@ -17,6 +17,7 @@ limitations under the License.
import base64
import collections
import json
import os
class ConfigDriveWriter(object):
@ -30,9 +31,7 @@ class ConfigDriveWriter(object):
def add_file(self, filepath, contents):
self.files[filepath] = contents
def serialize(self, prefix='openstack'):
out = {}
def write(self, location, prefix='teeth', version='latest'):
metadata = {}
for k, v in self.metadata.iteritems():
metadata[k] = v
@ -48,12 +47,31 @@ class ConfigDriveWriter(object):
}
metadata['files'].append(file_info)
metadata_path = prefix + content_path
out[metadata_path] = contents
content_path = os.path.join(location, content_path[1:])
with open(content_path, 'wb') as f:
f.write(contents)
filenumber += 1
json_metadata = json.dumps(metadata)
metadata_path = '{}/latest/meta_data.json'.format(prefix)
out[metadata_path] = base64.b64encode(json_metadata)
metadata_path = '{}/{}/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)
return out
def write_configdrive(location, metadata, files, prefix='teeth',
version='latest'):
"""Generates and writes a valid configdrive to `location`.
`files` are passed in as a dict {path: base64_contents}.
"""
writer = ConfigDriveWriter()
for k, v in metadata.iteritems():
writer.add_metadata(k, v)
for path, b64_contents in files.iteritems():
contents = base64.b64decode(b64_contents)
writer.add_file(path, contents)
writer.write(location)

@ -21,6 +21,7 @@ import subprocess
import requests
from teeth_agent import base
from teeth_agent import configdrive
from teeth_agent import errors
@ -32,18 +33,6 @@ def _image_location(image_info):
return '/tmp/{}'.format(image_info['id'])
def _write_local_config_drive(location, data):
"""Writes a config_drive directory at `location`."""
if not os.path.exists(location):
os.makedirs(location)
for path, contents in data.iteritems():
filename = os.path.join(location, path)
with open(filename, 'w') as f:
file_data = contents.decode('base64')
f.write(file_data)
def _path_to_script(script):
cwd = os.path.dirname(os.path.realpath(__file__))
return os.path.join(cwd, script)
@ -123,11 +112,12 @@ class PrepareImageCommand(base.AsyncCommandResult):
def execute(self):
image_info = self.command_params['image_info']
location = _configdrive_location()
data = self.command_params['configdrive']
metadata = self.command_params['user_metadata']
files = self.command_params['files']
device = self.command_params['device']
_download_image(image_info)
_write_local_config_drive(location, data)
configdrive.write(location, metadata, files)
_write_image(image_info, location, device)

@ -16,6 +16,7 @@ limitations under the License.
import base64
import json
import mock
import unittest
from teeth_agent import configdrive
@ -36,52 +37,106 @@ class ConfigDriveWriterTestCase(unittest.TestCase):
files = self.writer.files
self.assertEqual(files, {'/etc/filename': 'contents'})
def test_serialize_no_files(self):
self.writer.add_metadata('admin_pass', 'password')
self.writer.add_metadata('hostname', 'test')
@mock.patch('__builtin__.open', autospec=True)
def test_write_no_files(self, open_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'}
json_metadata = json.dumps(metadata)
metadata_path = '/lol/teeth/latest/meta_data.json'
for k, v in metadata.iteritems():
self.writer.add_metadata(k, v)
metadata = self.writer.metadata
metadata = base64.b64encode(json.dumps(metadata))
expected = {'openstack/latest/meta_data.json': metadata}
open_mock.return_value.__enter__ = lambda s: s
open_mock.return_value.__exit__ = mock.Mock()
write_mock = open_mock.return_value.write
data = self.writer.serialize()
self.assertEqual(data, expected)
self.writer.write('/lol', prefix='teeth', version='latest')
open_mock.assert_called_once_with(metadata_path, 'wb')
write_mock.assert_called_once_with(json_metadata)
def test_serialize_with_prefix(self):
self.writer.add_metadata('admin_pass', 'password')
self.writer.add_metadata('hostname', 'test')
@mock.patch('__builtin__.open', autospec=True)
def test_write_with_files(self, open_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'}
for k, v in metadata.iteritems():
self.writer.add_metadata(k, v)
files = {
'/etc/conf0': 'contents0',
'/etc/conf1': 'contents1',
}
for path, contents in files.iteritems():
self.writer.add_file(path, contents)
metadata = self.writer.metadata
metadata = base64.b64encode(json.dumps(metadata))
expected = {'teeth/latest/meta_data.json': metadata}
data = self.writer.serialize(prefix='teeth')
self.assertEqual(data, expected)
def test_serialize_with_files(self):
self.writer.add_metadata('admin_pass', 'password')
self.writer.add_metadata('hostname', 'test')
self.writer.add_file('/etc/conf0', 'contents0')
self.writer.add_file('/etc/conf1', 'contents1')
open_mock.return_value.__enter__ = lambda s: s
open_mock.return_value.__exit__ = mock.Mock()
write_mock = open_mock.return_value.write
metadata = self.writer.metadata
metadata['files'] = [
{'content_path': '/content/0000', 'path': '/etc/conf0'},
{'content_path': '/content/0001', 'path': '/etc/conf1'},
]
metadata = base64.b64encode(json.dumps(metadata))
expected = {
'openstack/latest/meta_data.json': metadata,
'openstack/content/0000': 'contents0',
'openstack/content/0001': 'contents1'
}
data = self.writer.serialize()
self.assertEqual(len(data.keys()), len(expected.keys()))
for k, v in expected.items():
if '.json' in k:
_actual = json.loads(base64.b64decode(data[k]))
_expected = json.loads(base64.b64decode(v))
self.assertEqual(_actual, _expected)
else:
self.assertEqual(data[k], v)
self.writer.write('/lol', prefix='teeth', version='latest')
# have to pull out the JSON passed to write and parse it
# because arbitrary dictionary ordering, etc
calls = write_mock.mock_calls
json_data = calls[-1][1][0]
data = json.loads(json_data)
self.assertEqual(data, metadata)
open_calls = [
mock.call('/lol/content/0000', 'wb'),
mock.call().write('contents0'),
mock.call().__exit__(None, None, None),
mock.call('/lol/content/0001', 'wb'),
mock.call().write('contents1'),
mock.call().__exit__(None, None, None),
mock.call('/lol/teeth/latest/meta_data.json', 'wb'),
# already checked
mock.call().write(mock.ANY),
mock.call().__exit__(None, None, None),
]
self.assertEqual(open_mock.mock_calls, open_calls)
@mock.patch('__builtin__.open', autospec=True)
def test_write_configdrive(self, open_mock):
metadata = {'admin_pass': 'password', 'hostname': 'test'}
files = {
'/etc/conf0': base64.b64encode('contents0'),
'/etc/conf1': base64.b64encode('contents1'),
}
metadata['files'] = [
{'content_path': '/content/0000', 'path': '/etc/conf0'},
{'content_path': '/content/0001', 'path': '/etc/conf1'},
]
open_mock.return_value.__enter__ = lambda s: s
open_mock.return_value.__exit__ = mock.Mock()
write_mock = open_mock.return_value.write
configdrive.write_configdrive('/lol',
metadata,
files,
prefix='teeth',
version='latest')
# have to pull out the JSON passed to write and parse it
# because arbitrary dictionary ordering, etc
calls = write_mock.mock_calls
json_data = calls[-1][1][0]
data = json.loads(json_data)
self.assertEqual(data, metadata)
open_calls = [
mock.call('/lol/content/0000', 'wb'),
mock.call().write('contents0'),
mock.call().__exit__(None, None, None),
mock.call('/lol/content/0001', 'wb'),
mock.call().write('contents1'),
mock.call().__exit__(None, None, None),
mock.call('/lol/teeth/latest/meta_data.json', 'wb'),
# already checked
mock.call().write(mock.ANY),
mock.call().__exit__(None, None, None),
]
self.assertEqual(open_mock.mock_calls, open_calls)

@ -99,31 +99,6 @@ class TestBaseTeethAgent(unittest.TestCase):
location = standby._image_location(image_info)
self.assertEqual(location, '/tmp/fake_id')
@mock.patch('__builtin__.open', autospec=True)
@mock.patch('os.makedirs', autospec=True)
@mock.patch('os.path', autospec=True)
def test_write_local_config_drive(self, path_mock, makedirs_mock,
open_mock):
open_mock.return_value.__enter__ = lambda s: s
open_mock.return_value.__exit__ = mock.Mock()
write_mock = open_mock.return_value.write
path_mock.exists.return_value = True
location = '/tmp/configdrive'
filename = '{}/meta_data.json'.format(location)
path_mock.join.return_value = filename
data = {'meta_data.json': 'contents'.encode('base64')}
standby._write_local_config_drive(location, data)
path_mock.exists.assert_called_once_with(location)
self.assertEqual(makedirs_mock.call_count, 0)
open_mock.assert_called_once_with(filename, 'w')
write_mock.assert_called_once_with('contents')
path_mock.exists.return_value = False
standby._write_local_config_drive(location, data)
self.assertEqual(makedirs_mock.call_count, 1)
@mock.patch('__builtin__.open', autospec=True)
@mock.patch('subprocess.call', autospec=True)
def test_write_image(self, call_mock, open_mock):