write configdrive to disk from metadata
This commit is contained in:
parent
7bf6a8e1cc
commit
b1c3ba28fa
teeth_agent
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user