Merge "Add --architecture support when uploading images"

This commit is contained in:
Zuul 2018-07-12 09:08:46 +00:00 committed by Gerrit Code Review
commit 00d308fd93
5 changed files with 295 additions and 18 deletions

View File

@ -0,0 +1,7 @@
---
features:
- |
In order to allow overcloud and deploy images to vary based on architecture
add a ``--architecture`` option to ``openstack overcloud image upload``.
This option will add hw_architecture to the image meta-data, which will
then be used my nova to limit node selection to matching CPU architectures.

View File

@ -776,18 +776,41 @@ class TestOvercloudNameScenarios(TestWithScenarios):
dict(func=utils.overcloud_kernel,
basename='overcloud-full',
expected=('overcloud-full-vmlinuz', '.vmlinuz'))),
('kernel_arch',
dict(func=utils.overcloud_kernel,
basename='overcloud-full',
arch='x86_64',
expected=('x86_64-overcloud-full-vmlinuz', '.vmlinuz'))),
('ramdisk_default',
dict(func=utils.overcloud_ramdisk,
basename='overcloud-full',
expected=('overcloud-full-initrd', '.initrd'))),
('ramdisk_arch',
dict(func=utils.overcloud_ramdisk,
basename='overcloud-full',
arch='x86_64',
expected=('x86_64-overcloud-full-initrd', '.initrd'))),
('image_default',
dict(func=utils.overcloud_image,
basename='overcloud-full',
expected=('overcloud-full', '.qcow2'))),
('image_arch',
dict(func=utils.overcloud_image,
basename='overcloud-full',
arch='x86_64',
expected=('x86_64-overcloud-full', '.qcow2'))),
]
def test_overcloud_params(self):
observed = self.func(self.basename)
kwargs = dict()
for attr in ['arch']:
if hasattr(self, attr):
kwargs[attr] = getattr(self, attr)
if kwargs:
observed = self.func(self.basename, **kwargs)
else:
observed = self.func(self.basename)
self.assertEqual(self.expected, observed)
@ -797,12 +820,28 @@ class TestDeployNameScenarios(TestWithScenarios):
('kernel_default',
dict(func=utils.deploy_kernel,
expected=('bm-deploy-kernel', '.kernel'))),
('kernel_arch',
dict(func=utils.deploy_kernel,
arch='x86_64',
expected=('x86_64-bm-deploy-kernel', '.kernel'))),
('ramdisk_default',
dict(func=utils.deploy_ramdisk,
expected=('bm-deploy-ramdisk', '.initramfs'))),
('ramdisk_arch',
dict(func=utils.deploy_ramdisk,
arch='x86_64',
expected=('x86_64-bm-deploy-ramdisk', '.initramfs'))),
]
def test_deploy_params(self):
observed = self.func()
kwargs = {}
for attr in ['arch']:
if hasattr(self, attr):
kwargs[attr] = getattr(self, attr)
if kwargs:
observed = self.func(**kwargs)
else:
observed = self.func()
self.assertEqual(self.expected, observed)

View File

@ -311,7 +311,59 @@ class TestUploadOvercloudImage(TestPluginV1):
], self.app.client_manager.image.images.create.call_args_list
)
@mock.patch('os.path.isfile', autospec=True)
@mock.patch('subprocess.check_call', autospec=True)
def test_overcloud_create_images_with_arch_v1(self, mock_subprocess_call,
mock_isfile):
parsed_args = self.check_parser(self.cmd, ['--arch', 'x86_64'], [])
mock_isfile.return_value = False
self.cmd._get_image = mock.Mock(return_value=None)
self.app.client_manager.image.version = 1.0
self.cmd.take_action(parsed_args)
self.assertEqual(
0,
self.app.client_manager.image.images.delete.call_count
)
self.assertEqual(
5,
self.app.client_manager.image.images.create.call_count
)
self.assertEqual(
[mock.call(properties={'hw_architecture': 'x86_64'},
data=b'IMGDATA',
name='x86_64-overcloud-full-vmlinuz',
disk_format='aki',
is_public=True),
mock.call(properties={'hw_architecture': 'x86_64'},
data=b'IMGDATA',
name='x86_64-overcloud-full-initrd',
disk_format='ari',
is_public=True),
mock.call(properties={'hw_architecture': 'x86_64',
'kernel_id': 10, 'ramdisk_id': 10},
name='x86_64-overcloud-full',
data=b'IMGDATA',
container_format='bare',
disk_format='qcow2',
is_public=True),
mock.call(properties={'hw_architecture': 'x86_64'},
data=b'IMGDATA',
name='x86_64-bm-deploy-kernel',
disk_format='aki',
is_public=True),
mock.call(properties={'hw_architecture': 'x86_64'},
data=b'IMGDATA',
name='x86_64-bm-deploy-ramdisk',
disk_format='ari',
is_public=True)
], self.app.client_manager.image.images.create.call_args_list
)
self.assertEqual(mock_subprocess_call.call_count, 2)
# FIXME(tonyb): this is the wrong way around
self.assertEqual(
mock_subprocess_call.call_args_list, [
mock.call('sudo cp -f "./ironic-python-agent.kernel" '
@ -468,6 +520,59 @@ class TestUploadOvercloudImageFull(TestPluginV1):
'"/httpboot/agent.ramdisk"', shell=True)
])
@mock.patch('os.path.isfile', autospec=True)
@mock.patch('subprocess.check_call', autospec=True)
def test_overcloud_create_images_with_arch(self, mock_subprocess_call,
mock_isfile):
parsed_args = self.check_parser(self.cmd,
['--whole-disk', '--arch', 'x86_64'],
[])
mock_isfile.return_value = False
self.cmd._get_image = mock.Mock(return_value=None)
self.cmd.take_action(parsed_args)
self.assertEqual(
0,
self.app.client_manager.image.images.delete.call_count
)
self.assertEqual(
3,
self.app.client_manager.image.images.create.call_count
)
self.assertEqual(
[mock.call(name='x86_64-overcloud-full',
disk_format='qcow2',
container_format='bare',
visibility='public'),
mock.call(name='x86_64-bm-deploy-kernel',
disk_format='aki',
container_format='bare',
visibility='public'),
mock.call(name='x86_64-bm-deploy-ramdisk',
disk_format='ari',
container_format='bare',
visibility='public')
], self.app.client_manager.image.images.create.call_args_list
)
self.assertEqual(
[mock.call(mock.ANY, hw_architecture='x86_64'),
mock.call(mock.ANY, hw_architecture='x86_64'),
mock.call(mock.ANY, hw_architecture='x86_64'),
], self.app.client_manager.image.images.update.call_args_list
)
self.assertEqual(mock_subprocess_call.call_count, 2)
self.assertEqual(
mock_subprocess_call.call_args_list, [
mock.call('sudo cp -f "./ironic-python-agent.kernel" '
'"/httpboot/agent.kernel"', shell=True),
mock.call('sudo cp -f "./ironic-python-agent.initramfs" '
'"/httpboot/agent.ramdisk"', shell=True)
])
@mock.patch('os.path.isfile', autospec=True)
@mock.patch('subprocess.check_call', autospec=True)
def test_overcloud_create_noupdate_images(self, mock_subprocess_call,
@ -527,3 +632,109 @@ class TestUploadOvercloudImageFull(TestPluginV1):
self.app.client_manager.image.images.update.call_count
)
self.assertEqual(mock_subprocess_call.call_count, 2)
class TestUploadOvercloudImageFullMultiArch(TestPluginV1):
# NOTE(tonyb): Really only the id is important below, but the names make
# reading logfiles a little nicer
images = [
mock.Mock(id=10, name='overcloud-full'),
mock.Mock(id=11, name='bm-deploy-kernel'),
mock.Mock(id=12, name='bm-deploy-initrd'),
mock.Mock(id=13, name='ppc64le-overcloud-full'),
mock.Mock(id=14, name='ppc64le-bm-deploy-kernel'),
mock.Mock(id=15, name='ppc64le-bm-deploy-initrd'),
]
def setUp(self):
super(TestUploadOvercloudImageFullMultiArch, self).setUp()
# Get the command object to test
self.cmd = overcloud_image.UploadOvercloudImage(self.app, None)
self.app.client_manager.image = mock.Mock()
self.app.client_manager.image.version = 2.0
# NOTE(tonyb): This is a little fragile. It works because
# GlanceV2ClientAdapter.upload_image() calls
# self.client.images.create() and self.client.images.get() once each
# call so this way we always create() and get() the same mocked "image"
self.app.client_manager.image.images.create.side_effect = self.images
self.app.client_manager.image.images.get.side_effect = self.images
self.cmd._read_image_file_pointer = mock.Mock(return_value=b'IMGDATA')
self.cmd._check_file_exists = mock.Mock(return_value=True)
@mock.patch('os.path.isfile', autospec=True)
@mock.patch('subprocess.check_call', autospec=True)
def test_overcloud_create_images_with_arch(self, mock_subprocess_call,
mock_isfile):
mock_isfile.return_value = False
self.cmd._get_image = mock.Mock(return_value=None)
mock.patch
parsed_args = self.check_parser(self.cmd,
['--whole-disk'],
[])
self.cmd.take_action(parsed_args)
parsed_args = self.check_parser(self.cmd,
['--whole-disk',
'--http-boot', '/httpboot/ppc64le',
'--arch', 'ppc64le'],
[])
self.cmd.take_action(parsed_args)
self.assertEqual(
0,
self.app.client_manager.image.images.delete.call_count
)
self.assertEqual(
6,
self.app.client_manager.image.images.create.call_count
)
self.assertEqual(
[mock.call(name='overcloud-full',
disk_format='qcow2',
container_format='bare',
visibility='public'),
mock.call(name='bm-deploy-kernel',
disk_format='aki',
container_format='bare',
visibility='public'),
mock.call(name='bm-deploy-ramdisk',
disk_format='ari',
container_format='bare',
visibility='public'),
mock.call(name='ppc64le-overcloud-full',
disk_format='qcow2',
container_format='bare',
visibility='public'),
mock.call(name='ppc64le-bm-deploy-kernel',
disk_format='aki',
container_format='bare',
visibility='public'),
mock.call(name='ppc64le-bm-deploy-ramdisk',
disk_format='ari',
container_format='bare',
visibility='public')
], self.app.client_manager.image.images.create.call_args_list
)
self.assertEqual(
[mock.call(13, hw_architecture='ppc64le'),
mock.call(14, hw_architecture='ppc64le'),
mock.call(15, hw_architecture='ppc64le'),
], self.app.client_manager.image.images.update.call_args_list
)
self.assertEqual(mock_subprocess_call.call_count, 4)
# FIXME(tonyb): this is the wrong way around
self.assertEqual(
mock_subprocess_call.call_args_list, [
mock.call('sudo cp -f "./ironic-python-agent.kernel" '
'"/httpboot/agent.kernel"', shell=True),
mock.call('sudo cp -f "./ironic-python-agent.initramfs" '
'"/httpboot/agent.ramdisk"', shell=True),
mock.call('sudo cp -f "./ironic-python-agent.kernel" '
'"/httpboot/ppc64le/agent.kernel"', shell=True),
mock.call('sudo cp -f "./ironic-python-agent.initramfs" '
'"/httpboot/ppc64le/agent.ramdisk"', shell=True),
])

View File

@ -1186,26 +1186,32 @@ def configure_logging(log, level, log_file):
log.addHandler(fhandler)
def overcloud_kernel(basename):
return ('%s-vmlinuz' % basename,
def _name_helper(basename, arch=None):
if arch:
basename = arch + '-' + basename
return basename
def overcloud_kernel(basename, arch=None):
return (_name_helper('%s-vmlinuz' % basename, arch=arch),
'.vmlinuz')
def overcloud_ramdisk(basename):
return ('%s-initrd' % basename,
def overcloud_ramdisk(basename, arch=None):
return (_name_helper('%s-initrd' % basename, arch=arch),
'.initrd')
def overcloud_image(basename):
return (basename,
def overcloud_image(basename, arch=None):
return (_name_helper(basename, arch=arch),
'.qcow2')
def deploy_kernel():
return ('bm-deploy-kernel',
def deploy_kernel(arch=None):
return (_name_helper('bm-deploy-kernel', arch=arch),
'.kernel')
def deploy_ramdisk():
return ('bm-deploy-ramdisk',
def deploy_ramdisk(arch=None):
return (_name_helper('bm-deploy-ramdisk', arch=arch),
'.initramfs')

View File

@ -275,6 +275,13 @@ class UploadOvercloudImage(command.Command):
help=_("When set, the overcloud-full image to be uploaded "
"will be considered as a whole disk one"),
)
parser.add_argument(
"--architecture",
help=_("Architecture type for these images, "
"\'x86_64\', \'i386\' and \'ppc64le\' "
"are common options. This option should match at least "
"one \'arch\' value in instackenv.json"),
)
return parser
def take_action(self, parsed_args):
@ -306,11 +313,15 @@ class UploadOvercloudImage(command.Command):
overcloud_image_type)
properties = {}
arch = parsed_args.architecture
if arch:
properties['hw_architecture'] = arch
# vmlinuz and initrd only need to be uploaded for a partition image
if not parsed_args.whole_disk:
(oc_vmlinuz_name,
oc_vmlinuz_extension) = plugin_utils.overcloud_kernel(image_name)
oc_vmlinuz_extension) = plugin_utils.overcloud_kernel(image_name,
arch=arch)
oc_vmlinuz_file = os.path.join(parsed_args.image_path,
image_name +
oc_vmlinuz_extension)
@ -327,7 +338,8 @@ class UploadOvercloudImage(command.Command):
))
(oc_initrd_name,
oc_initrd_extension) = plugin_utils.overcloud_ramdisk(image_name)
oc_initrd_extension) = plugin_utils.overcloud_ramdisk(image_name,
arch=arch)
oc_initrd_file = os.path.join(parsed_args.image_path,
image_name +
oc_initrd_extension)
@ -344,7 +356,8 @@ class UploadOvercloudImage(command.Command):
))
(oc_name,
oc_extension) = plugin_utils.overcloud_image(image_name)
oc_extension) = plugin_utils.overcloud_image(image_name,
arch=arch)
oc_file = os.path.join(parsed_args.image_path,
image_name +
oc_extension)
@ -374,7 +387,8 @@ class UploadOvercloudImage(command.Command):
else:
(oc_name,
oc_extension) = plugin_utils.overcloud_image(image_name)
oc_extension) = plugin_utils.overcloud_image(image_name,
arch=arch)
oc_file = os.path.join(parsed_args.image_path,
image_name +
oc_extension)
@ -393,7 +407,7 @@ class UploadOvercloudImage(command.Command):
self.log.debug("uploading bm images to glance")
(deploy_kernel_name,
deploy_kernel_extension) = plugin_utils.deploy_kernel()
deploy_kernel_extension) = plugin_utils.deploy_kernel(arch=arch)
deploy_kernel_file = os.path.join(parsed_args.image_path,
parsed_args.ipa_name +
deploy_kernel_extension)
@ -409,7 +423,7 @@ class UploadOvercloudImage(command.Command):
deploy_kernel_file))
(deploy_ramdisk_name,
deploy_ramdisk_extension) = plugin_utils.deploy_ramdisk()
deploy_ramdisk_extension) = plugin_utils.deploy_ramdisk(arch=arch)
deploy_ramdisk_file = os.path.join(parsed_args.image_path,
parsed_args.ipa_name +
deploy_ramdisk_extension)