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:
parent
18ec57b109
commit
e61b769d87
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
])
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue