Add --architecture support when uploading images

Adding --architecture when uploading images will set a hw_architecture image
meta data property and prefix all images with the arch to make them
stand out in an openstack image list

While tripleo doesn't yet take advantage of using the image meta data
it's still helpful for manual testing to ensure that image selection
implies appropriate hardware.

Support is added in such a way that that image names are not altered if
no --architecture is specified.

Conflicts:
tripleoclient/utils.py: Due to the following not existing on stable/queens
                        Ie4b7951147f5a1aec654982e21296a749fdd865c
                        I61763f62c5d44f098ad60f9426871caef16cd6de
                        I340be11bc9471df22f038629679634c3542d34d6

Blueprint: multiarch-support
Change-Id: I352759014ce4c249d8391b03317012000221f96f
(cherry picked from commit b5500956d3)
This commit is contained in:
Tony Breeds 2018-01-15 13:36:46 +11:00 committed by Trevor Vardeman
parent 18ec57b109
commit e61b769d87
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

@ -1089,18 +1089,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)
@ -1110,12 +1133,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" '
@ -470,6 +522,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,
@ -529,3 +634,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

@ -1270,26 +1270,32 @@ def run_command_and_log(log, cmd, cwd=None, env=None, retcode_only=True):
return proc
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

@ -276,6 +276,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):
@ -308,11 +315,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)
@ -329,7 +340,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)
@ -346,7 +358,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)
@ -376,7 +389,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)
@ -395,7 +409,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)
@ -411,7 +425,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)