From 92bd4c43d6d436346f6a4a70eb77ec67d24952b2 Mon Sep 17 00:00:00 2001 From: Yolanda Robla Mota Date: Mon, 7 Nov 2016 14:23:26 +0100 Subject: [PATCH] Support whole disk images in TripleO Enable to upload whole disk images from the client, not forcing to upload kernel and initrd images, and not setting the parameters for those in the glance qcow2 image. Change-Id: I9fbac888b3e709212cc6c72153a3ca465101987c Partially-Implements: blueprint support-full-disk-images --- ...ort-full-disk-images-8dc84619e8517629.yaml | 15 ++ .../overcloud_image/test_overcloud_image.py | 116 +++++++++++++++ tripleoclient/v1/overcloud_image.py | 139 +++++++++++------- 3 files changed, 217 insertions(+), 53 deletions(-) create mode 100644 releasenotes/notes/support-full-disk-images-8dc84619e8517629.yaml diff --git a/releasenotes/notes/support-full-disk-images-8dc84619e8517629.yaml b/releasenotes/notes/support-full-disk-images-8dc84619e8517629.yaml new file mode 100644 index 000000000..718ff1774 --- /dev/null +++ b/releasenotes/notes/support-full-disk-images-8dc84619e8517629.yaml @@ -0,0 +1,15 @@ +--- +features: + - Allow client to support whole disk images. Client + will now accept a --whole-disk flag on the + overcloud image upload command. When this flag is + set, it will only look for qcow2 image, not enforcing + the upload of initrd and vmlinuz images. It will also + not set these properties on the qcow2 image on glance. + This will allow Ironic to consider the uploaded image + as full disk image, giving the possibility to provide + full disk images in TripleO instead of single partition + ones. + Please look at `Ironic documentation `_ + for reference + diff --git a/tripleoclient/tests/v1/overcloud_image/test_overcloud_image.py b/tripleoclient/tests/v1/overcloud_image/test_overcloud_image.py index 502735ece..6d465d64e 100644 --- a/tripleoclient/tests/v1/overcloud_image/test_overcloud_image.py +++ b/tripleoclient/tests/v1/overcloud_image/test_overcloud_image.py @@ -594,3 +594,119 @@ class TestUploadOvercloudImage(TestPluginV1): self.app.client_manager.image.images.update.call_count ) self.assertEqual(mock_subprocess_call.call_count, 2) + + +class TestUploadOvercloudImageFull(TestPluginV1): + def setUp(self): + super(TestUploadOvercloudImageFull, 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 + self.app.client_manager.image.images.create.return_value = ( + mock.Mock(id=10, name='imgname', properties={}, + created_at='2015-07-31T14:37:22.000000')) + self.cmd._read_image_file_pointer = mock.Mock(return_value=b'IMGDATA') + self.cmd._check_file_exists = mock.Mock(return_value=True) + + @mock.patch('subprocess.check_call', autospec=True) + def test_overcloud_create_images(self, mock_subprocess_call): + parsed_args = self.check_parser(self.cmd, ['--whole-disk'], []) + os.path.isfile = mock.Mock(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='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') + ], self.app.client_manager.image.images.create.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('subprocess.check_call', autospec=True) + def test_overcloud_create_noupdate_images(self, mock_subprocess_call): + parsed_args = self.check_parser(self.cmd, ['--whole-disk'], []) + os.path.isfile = mock.Mock(return_value=True) + self.cmd._files_changed = mock.Mock(return_value=True) + + existing_image = mock.Mock(id=10, name='imgname', + properties={}) + self.cmd._get_image = mock.Mock(return_value=existing_image) + self.cmd._image_changed = mock.Mock(return_value=True) + + self.cmd.take_action(parsed_args) + + self.assertEqual( + 0, + self.app.client_manager.image.images.delete.call_count + ) + self.assertEqual( + 0, + self.app.client_manager.image.images.create.call_count + ) + self.assertEqual( + 0, + self.app.client_manager.image.images.update.call_count + ) + + self.assertEqual(mock_subprocess_call.call_count, 0) + + @mock.patch('subprocess.check_call', autospec=True) + def test_overcloud_create_update_images(self, mock_subprocess_call): + parsed_args = self.check_parser( + self.cmd, ['--update-existing', '--whole-disk'], []) + self.cmd._files_changed = mock.Mock(return_value=True) + + existing_image = mock.Mock(id=10, name='imgname', + properties={}, + created_at='2015-07-31T14:37:22.000000') + self.cmd._get_image = mock.Mock(return_value=existing_image) + self.cmd._image_changed = mock.Mock(return_value=True) + self.app.client_manager.image.images.update.return_value = ( + existing_image) + + 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( + 3, + self.app.client_manager.image.images.update.call_count + ) + self.assertEqual(mock_subprocess_call.call_count, 2) diff --git a/tripleoclient/v1/overcloud_image.py b/tripleoclient/v1/overcloud_image.py index 761a3f7ff..f2833d37f 100644 --- a/tripleoclient/v1/overcloud_image.py +++ b/tripleoclient/v1/overcloud_image.py @@ -771,6 +771,14 @@ class UploadOvercloudImage(command.Command): action="store_true", help=_("Update images if already exist"), ) + parser.add_argument( + "--whole-disk", + dest="whole_disk", + action="store_true", + default=False, + help=_("When set, the overcloud-full image to be uploaded " + "will be considered as a whole disk one"), + ) return parser def take_action(self, parsed_args): @@ -781,11 +789,18 @@ class UploadOvercloudImage(command.Command): self.log.debug("checking if image files exist") - image_files = [ - '%s.initramfs' % os.environ['AGENT_NAME'], - '%s.kernel' % os.environ['AGENT_NAME'], - parsed_args.os_image - ] + if parsed_args.whole_disk: + image_files = [ + parsed_args.os_image + ] + overcloud_image_type = 'whole disk' + else: + image_files = [ + '%s.initramfs' % os.environ['AGENT_NAME'], + '%s.kernel' % os.environ['AGENT_NAME'], + parsed_args.os_image + ] + overcloud_image_type = 'partition' for image in image_files: self._check_file_exists(os.path.join(parsed_args.image_path, @@ -793,58 +808,76 @@ class UploadOvercloudImage(command.Command): image_name = parsed_args.os_image.split('.')[0] - self.log.debug("uploading overcloud images to glance") + self.log.debug("uploading %s overcloud images to glance" % + overcloud_image_type) - oc_vmlinuz_name = '%s-vmlinuz' % image_name - oc_vmlinuz_file = '%s.vmlinuz' % image_name - kernel = (self._image_try_update(oc_vmlinuz_name, - oc_vmlinuz_file, - parsed_args) or - glance_client_adaptor.upload_image( - name=oc_vmlinuz_name, - is_public=True, - disk_format='aki', - data=self._read_image_file_pointer( - parsed_args.image_path, oc_vmlinuz_file) - )) + # vmlinuz and initrd only need to be uploaded for a partition image + if not parsed_args.whole_disk: + oc_vmlinuz_name = '%s-vmlinuz' % image_name + oc_vmlinuz_file = '%s.vmlinuz' % image_name + kernel = (self._image_try_update(oc_vmlinuz_name, + oc_vmlinuz_file, + parsed_args) or + glance_client_adaptor.upload_image( + name=oc_vmlinuz_name, + is_public=True, + disk_format='aki', + data=self._read_image_file_pointer( + parsed_args.image_path, oc_vmlinuz_file) + )) - oc_initrd_name = '%s-initrd' % image_name - oc_initrd_file = '%s.initrd' % image_name - ramdisk = (self._image_try_update(oc_initrd_name, - oc_initrd_file, - parsed_args) or - glance_client_adaptor.upload_image( - name=oc_initrd_name, - is_public=True, - disk_format='ari', - data=self._read_image_file_pointer( - parsed_args.image_path, oc_initrd_file) - )) + oc_initrd_name = '%s-initrd' % image_name + oc_initrd_file = '%s.initrd' % image_name + ramdisk = (self._image_try_update(oc_initrd_name, + oc_initrd_file, + parsed_args) or + glance_client_adaptor.upload_image( + name=oc_initrd_name, + is_public=True, + disk_format='ari', + data=self._read_image_file_pointer( + parsed_args.image_path, oc_initrd_file) + )) - oc_name = image_name - oc_file = '%s.qcow2' % image_name - overcloud_image = (self._image_try_update(oc_name, oc_file, - parsed_args) or - glance_client_adaptor.upload_image( - name=oc_name, - is_public=True, - disk_format='qcow2', - container_format='bare', - properties={'kernel_id': kernel.id, - 'ramdisk_id': ramdisk.id}, - data=self._read_image_file_pointer( - parsed_args.image_path, oc_file) - )) + oc_name = image_name + oc_file = '%s.qcow2' % image_name + overcloud_image = (self._image_try_update(oc_name, oc_file, + parsed_args) or + glance_client_adaptor.upload_image( + name=oc_name, + is_public=True, + disk_format='qcow2', + container_format='bare', + properties={'kernel_id': kernel.id, + 'ramdisk_id': ramdisk.id}, + data=self._read_image_file_pointer( + parsed_args.image_path, oc_file) + )) - img_kernel_id = glance_client_adaptor.get_image_property( - overcloud_image, 'kernel_id') - img_ramdisk_id = glance_client_adaptor.get_image_property( - overcloud_image, 'ramdisk_id') - # check overcloud image links - if (img_kernel_id != kernel.id or img_ramdisk_id != ramdisk.id): - self.log.error('Link overcloud image to it\'s initrd and kernel' - ' images is MISSING OR leads to OLD image.' - ' You can keep it or fix it manually.') + img_kernel_id = glance_client_adaptor.get_image_property( + overcloud_image, 'kernel_id') + img_ramdisk_id = glance_client_adaptor.get_image_property( + overcloud_image, 'ramdisk_id') + # check overcloud image links + if (img_kernel_id != kernel.id or img_ramdisk_id != ramdisk.id): + self.log.error('Link overcloud image to it\'s initrd and ' + 'kernel images is MISSING OR leads to OLD ' + 'image. You can keep it or fix it manually.') + + else: + oc_name = image_name + oc_file = '%s.qcow2' % image_name + overcloud_image = (self._image_try_update(oc_name, oc_file, + parsed_args) or + glance_client_adaptor.upload_image( + name=oc_name, + is_public=True, + disk_format='qcow2', + container_format='bare', + properties={}, + data=self._read_image_file_pointer( + parsed_args.image_path, oc_file) + )) self.log.debug("uploading bm images to glance")