From 4279b4766d0f565a55efe6dc95f6074a1000c640 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Fri, 21 Apr 2023 13:30:48 -0700 Subject: [PATCH] Add Azure gallery image support The shared and community gallery images are another way to specify what image to use when creating a VM. Shared galleries are intended for use within an organization, and community galleries are public. This adds support for using these images. It requires an API version bump since the virtual machine attributes to specify them are new. Change-Id: Ia981fcbeea6680a9d14ee8e4ec401bf227a7cc12 --- doc/source/azure.rst | 60 +++++++++++++++++ nodepool/driver/azure/adapter.py | 24 ++++++- nodepool/driver/azure/azul.py | 2 +- nodepool/driver/azure/config.py | 25 ++++++- .../tests/fixtures/azure-gallery-image.yaml | 59 +++++++++++++++++ nodepool/tests/unit/fake_azure.py | 2 +- nodepool/tests/unit/test_driver_azure.py | 66 +++++++++++++++++++ .../gallery-images-a8f5c81b7b1bf973.yaml | 5 ++ 8 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 nodepool/tests/fixtures/azure-gallery-image.yaml create mode 100644 releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml diff --git a/doc/source/azure.rst b/doc/source/azure.rst index 34e12ecfc..e1ae4e0cd 100644 --- a/doc/source/azure.rst +++ b/doc/source/azure.rst @@ -318,6 +318,8 @@ section of the configuration. :type: dict Specifies a private image to use via filters. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-reference`, or :attr:`providers.[azure].cloud-images.image-id` must be provided. @@ -363,14 +365,72 @@ section of the configuration. :type: str Specifies a private image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-reference`, or :attr:`providers.[azure].cloud-images.image-filter` must be provided. + .. attr:: shared-gallery-image + :type: dict + + Specifies a shared gallery image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.community-gallery-image`, + :attr:`providers.[azure].cloud-images.image-reference`, + :attr:`providers.[azure].cloud-images.image-id`, or + :attr:`providers.[azure].cloud-images.image-filter` must be + provided. + + .. attr:: gallery-name + :type: str + :required: + + The name of the image gallery. + + .. attr:: name + :type: str + :required: + + The name of the image. + + .. attr:: version + :type: str + + The image version. Omit to use the latest version. + + .. attr:: community-gallery-image + :type: dict + + Specifies a community gallery image to use by ID. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.image-reference`, + :attr:`providers.[azure].cloud-images.image-id`, or + :attr:`providers.[azure].cloud-images.image-filter` must be + provided. + + .. attr:: gallery-name + :type: str + :required: + + The name of the image gallery. + + .. attr:: name + :type: str + :required: + + The name of the image. + + .. attr:: version + :type: str + + The image version. Omit to use the latest version. + .. attr:: image-reference :type: dict Specifies a public image to use. Either this field, + :attr:`providers.[azure].cloud-images.shared-gallery-image`, + :attr:`providers.[azure].cloud-images.community-gallery-image`, :attr:`providers.[azure].cloud-images.image-id`, or :attr:`providers.[azure].cloud-images.image-filter` must be provided. diff --git a/nodepool/driver/azure/adapter.py b/nodepool/driver/azure/adapter.py index 3eaa18a33..de551479f 100644 --- a/nodepool/driver/azure/adapter.py +++ b/nodepool/driver/azure/adapter.py @@ -681,7 +681,7 @@ class AzureAdapter(statemachine.Adapter): remote_image = self._getImage(image_external_id) image_reference = {'id': remote_image['id']} elif image_reference: - # This is a cloud image with aser supplied image-filter; + # This is a cloud image with user supplied image-filter; # we already found the reference. image = label.cloud_image else: @@ -690,6 +690,28 @@ class AzureAdapter(statemachine.Adapter): image = label.cloud_image if label.cloud_image.image_reference: image_reference = label.cloud_image.image_reference + elif label.cloud_image.community_gallery_image: + g = label.cloud_image.community_gallery_image + gallery_image_id = ( + f"/CommunityGalleries/{g['gallery-name']}" + f"/Images/{g['name']}" + ) + if g.get('version'): + gallery_image_id += f"/Versions/{g['version']}" + image_reference = { + 'communityGalleryImageId': gallery_image_id + } + elif label.cloud_image.shared_gallery_image: + g = label.cloud_image.shared_gallery_image + gallery_image_id = ( + f"/SharedGalleries/{g['gallery-name']}" + f"/Images/{g['name']}" + ) + if g.get('version'): + gallery_image_id += f"/Versions/{g['version']}" + image_reference = { + 'sharedGalleryImageId': gallery_image_id + } else: image_reference = {'id': label.cloud_image.image_id} os_profile = {'computerName': hostname} diff --git a/nodepool/driver/azure/azul.py b/nodepool/driver/azure/azul.py index e579b7ceb..af7ef42f8 100644 --- a/nodepool/driver/azure/azul.py +++ b/nodepool/driver/azure/azul.py @@ -240,7 +240,7 @@ class AzureCloud: self, providerId='Microsoft.Compute', resource='virtualMachines', - apiVersion='2020-12-01') + apiVersion='2022-11-01') self.disks = AzureResourceProviderCRUD( self, providerId='Microsoft.Compute', diff --git a/nodepool/driver/azure/config.py b/nodepool/driver/azure/config.py index 87487d8eb..1e790e597 100644 --- a/nodepool/driver/azure/config.py +++ b/nodepool/driver/azure/config.py @@ -39,6 +39,10 @@ class AzureProviderCloudImage(ConfigValue): self.image_reference = image.get('image-reference') self.image_filter = image.get('image-filter') self.image_id = image.get('image-id') + self.shared_gallery_image = image.get( + 'shared-gallery-image') + self.community_gallery_image = image.get( + 'community-gallery-image') self.python_path = image.get('python-path', 'auto') self.shell_type = image.get('shell-type') self.connection_type = image.get('connection-type', 'ssh') @@ -67,6 +71,12 @@ class AzureProviderCloudImage(ConfigValue): 'tags': dict, } + azure_gallery_image = { + v.Required('gallery-name'): str, + v.Required('name'): str, + 'version': str, + } + return v.All({ v.Required('name'): str, v.Required('username'): str, @@ -77,15 +87,24 @@ class AzureProviderCloudImage(ConfigValue): v.Exclusive('image-reference', 'spec'): azure_image_reference, v.Exclusive('image-id', 'spec'): str, v.Exclusive('image-filter', 'spec'): azure_image_filter, + v.Exclusive('community-gallery-image', 'spec'): + azure_gallery_image, + v.Exclusive('shared-gallery-image', 'spec'): + azure_gallery_image, 'connection-type': str, 'connection-port': int, 'python-path': str, 'shell-type': str, }, { v.Required( - v.Any('image-reference', 'image-id', 'image-filter'), - msg=('Provide either "image-reference", ' - '"image-filter", or "image-id" keys') + v.Any('image-reference', 'image-id', 'image-filter', + 'community-gallery-image', + 'shared-gallery-image', + ), + msg=('Provide one of "image-reference", ' + '"image-filter", "image-id", ' + '"community-gallery-image", or ' + '"shared-gallery-image" keys') ): object, object: object, }) diff --git a/nodepool/tests/fixtures/azure-gallery-image.yaml b/nodepool/tests/fixtures/azure-gallery-image.yaml new file mode 100644 index 000000000..3c436a4b1 --- /dev/null +++ b/nodepool/tests/fixtures/azure-gallery-image.yaml @@ -0,0 +1,59 @@ +webapp: + port: 8005 + listen_address: '0.0.0.0' + +zookeeper-servers: + - host: {zookeeper_host} + port: {zookeeper_port} + chroot: {zookeeper_chroot} + +zookeeper-tls: + ca: {zookeeper_ca} + cert: {zookeeper_cert} + key: {zookeeper_key} + +labels: + - name: community-bionic + min-ready: 0 + - name: shared-bionic + min-ready: 0 + +providers: + - name: azure + driver: azure + zuul-public-key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+mplenM+m6pNY9Un3fpO9eqf808Jrfb3d1gXg7BZVawCvtEZ/cDYvLQ3OF1AeL2kcIC0UAIglM5JXae7yO5CJbJRdkbXvv0u1LvpLxYSPM4ATR0r4IseC5YVxkfJQNi4ixSwTqD4ScEkuCXcSqSU9M+hB+KlnwXoR4IcYHf7vD2Z0Mdwm2ikk3SeERmspmMxx/uz0SPn58QxONuoTlNWQKqDWsV6bRyoPa6HWccMrIH1/e7E69Nw/30oioOQpKBgaDCauh+QkDtSkjRpRMOV47ZFh16Q9DqMgLx+FD8z6++9rsHlB65Zas1xyQsiRCFG09s00b7OR7Xz9ukQ5+vXV + resource-group-location: centralus + location: centralus + resource-group: nodepool + auth-path: {auth_path} + subnet-id: /subscriptions/c35cf7df-ed75-4c85-be00-535409a85120/resourceGroups/nodepool/providers/Microsoft.Network/virtualNetworks/NodePool/subnets/default + cloud-images: + - name: community-bionic + username: zuul + shell-type: sh + community-gallery-image: + gallery-name: community-gallery + name: community-image + version: latest + - name: shared-bionic + username: zuul + shell-type: sh + shared-gallery-image: + gallery-name: shared-gallery + name: shared-image + version: latest + pools: + - name: main + max-servers: 10 + node-attributes: + key1: value1 + key2: value2 + labels: + - name: community-bionic + cloud-image: community-bionic + hardware-profile: + vm-size: Standard_B1ls + - name: shared-bionic + cloud-image: shared-bionic + hardware-profile: + vm-size: Standard_B1ls diff --git a/nodepool/tests/unit/fake_azure.py b/nodepool/tests/unit/fake_azure.py index f0f155868..e796c3b7a 100644 --- a/nodepool/tests/unit/fake_azure.py +++ b/nodepool/tests/unit/fake_azure.py @@ -325,7 +325,7 @@ class FakeAzureFixture(fixtures.Fixture): self._setup_crud(ResourceGroupsCRUD, '2020-06-01', resource_grouped=False) - self._setup_crud(VirtualMachinesCRUD, '2020-12-01') + self._setup_crud(VirtualMachinesCRUD, '2022-11-01') self._setup_crud(NetworkInterfacesCRUD, '2020-07-01') self._setup_crud(PublicIPAddressesCRUD, '2020-07-01') self._setup_crud(DisksCRUD, '2020-06-30') diff --git a/nodepool/tests/unit/test_driver_azure.py b/nodepool/tests/unit/test_driver_azure.py index 9c63c83fa..570cda575 100644 --- a/nodepool/tests/unit/test_driver_azure.py +++ b/nodepool/tests/unit/test_driver_azure.py @@ -213,6 +213,72 @@ class TestDriverAzure(tests.DBTestCase): "/resourceGroups/nodepool/providers/Microsoft.Compute" "/images/test-image-1234") + def test_azure_community_gallery_image(self): + configfile = self.setup_config( + 'azure-gallery-image.yaml', + auth_path=self.fake_azure.auth_file.name) + pool = self.useNodepool(configfile, watermark_sleep=1) + self.startPool(pool) + req = zk.NodeRequest() + req.state = zk.REQUESTED + req.node_types.append('community-bionic') + + self.zk.storeNodeRequest(req) + req = self.waitForNodeRequest(req) + + self.assertEqual(req.state, zk.FULFILLED) + self.assertNotEqual(req.nodes, []) + node = self.zk.getNode(req.nodes[0]) + self.assertEqual(node.allocated_to, req.id) + self.assertEqual(node.state, zk.READY) + self.assertIsNotNone(node.launcher) + self.assertEqual(node.connection_type, 'ssh') + self.assertEqual(node.shell_type, 'sh') + self.assertEqual(node.attributes, + {'key1': 'value1', 'key2': 'value2'}) + self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY']) + + self.assertEqual( + self.fake_azure.crud['Microsoft.Compute/virtualMachines']. + requests[0]['properties']['storageProfile'] + ['imageReference']['communityGalleryImageId'], + "/CommunityGalleries/community-gallery" + "/Images/community-image" + "/Versions/latest") + + def test_azure_shared_gallery_image(self): + configfile = self.setup_config( + 'azure-gallery-image.yaml', + auth_path=self.fake_azure.auth_file.name) + pool = self.useNodepool(configfile, watermark_sleep=1) + self.startPool(pool) + req = zk.NodeRequest() + req.state = zk.REQUESTED + req.node_types.append('shared-bionic') + + self.zk.storeNodeRequest(req) + req = self.waitForNodeRequest(req) + + self.assertEqual(req.state, zk.FULFILLED) + self.assertNotEqual(req.nodes, []) + node = self.zk.getNode(req.nodes[0]) + self.assertEqual(node.allocated_to, req.id) + self.assertEqual(node.state, zk.READY) + self.assertIsNotNone(node.launcher) + self.assertEqual(node.connection_type, 'ssh') + self.assertEqual(node.shell_type, 'sh') + self.assertEqual(node.attributes, + {'key1': 'value1', 'key2': 'value2'}) + self.assertEqual(node.host_keys, ['ssh-rsa FAKEKEY']) + + self.assertEqual( + self.fake_azure.crud['Microsoft.Compute/virtualMachines']. + requests[0]['properties']['storageProfile'] + ['imageReference']['sharedGalleryImageId'], + "/SharedGalleries/shared-gallery" + "/Images/shared-image" + "/Versions/latest") + def test_azure_image_filter_name(self): self.fake_azure.crud['Microsoft.Compute/images'].items.append( make_image('test1', {'foo': 'bar'})) diff --git a/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml b/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml new file mode 100644 index 000000000..d751ed4a8 --- /dev/null +++ b/releasenotes/notes/gallery-images-a8f5c81b7b1bf973.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The Azure driver now support specifying community and shared + gallery images.