From 2229d1bd851ba52e2c1174e39ea9e372cff853b9 Mon Sep 17 00:00:00 2001 From: Divyansh Date: Mon, 19 Jul 2021 14:20:26 -0700 Subject: [PATCH] Add support for testing image in shared gallery --- lisa/sut_orchestrator/azure/arm_template.json | 18 +++- lisa/sut_orchestrator/azure/common.py | 88 +++++++++++++++++++ lisa/sut_orchestrator/azure/platform_.py | 11 ++- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/lisa/sut_orchestrator/azure/arm_template.json b/lisa/sut_orchestrator/azure/arm_template.json index d2779b9706..2ad006f75f 100644 --- a/lisa/sut_orchestrator/azure/arm_template.json +++ b/lisa/sut_orchestrator/azure/arm_template.json @@ -278,7 +278,7 @@ "linuxConfiguration": "[if(empty(parameters('admin_key_data')), json('null'), lisa.getLinuxConfiguration(concat('/home/', parameters('admin_username'), '/.ssh/authorized_keys'), parameters('admin_key_data')))]" }, "storageProfile": { - "imageReference": "[if(not(empty(parameters('nodes')[copyIndex('vmCopy')]['vhd'])), lisa.getOsDiskVhd(parameters('nodes')[copyIndex('vmCopy')]['name']), lisa.getOsDiskMarketplace(parameters('nodes')[copyIndex('vmCopy')]))]", + "imageReference": "[if(not(empty(parameters('nodes')[copyIndex('vmCopy')]['vhd'])), lisa.getOsDiskVhd(parameters('nodes')[copyIndex('vmCopy')]['name']), if(not(empty(parameters('nodes')[copyIndex('vmCopy')]['shared_gallery'])), lisa.getOsDiskSharedGallery(parameters('nodes')[copyIndex('vmCopy')]['shared_gallery']), lisa.getOsDiskMarketplace(parameters('nodes')[copyIndex('vmCopy')])))]", "osDisk": { "name": "[concat(parameters('nodes')[copyIndex('vmCopy')]['name'], '-osDisk')]", "managedDisk": { @@ -327,6 +327,20 @@ "value": "[parameters('node')['marketplace']]" } }, + "getOsDiskSharedGallery": { + "parameters": [ + { + "name": "node", + "type": "object" + } + ], + "output": { + "type": "object", + "value": { + "id": "[resourceId(parameters('node')['subscription_id'], 'None', 'Microsoft.Compute/galleries/images/versions', parameters('node')['image_gallery'], parameters('node')['image_definition'], parameters('node')['image_version'])]" + } + } + }, "getOsDiskVhd": { "parameters": [ { @@ -371,4 +385,4 @@ } } ] -} +} \ No newline at end of file diff --git a/lisa/sut_orchestrator/azure/common.py b/lisa/sut_orchestrator/azure/common.py index 01812db638..d84b6e92ef 100644 --- a/lisa/sut_orchestrator/azure/common.py +++ b/lisa/sut_orchestrator/azure/common.py @@ -60,22 +60,40 @@ class AzureVmMarketplaceSchema: version: str = "Latest" +@dataclass_json() +@dataclass +class SharedImageGallerySchema: + subscription_id: str = "" + image_gallery: str = "" + image_definition: str = "" + image_version: str = "" + + @dataclass_json() @dataclass class AzureNodeSchema: name: str = "" vm_size: str = "" location: str = "" + # Required by shared gallery images which are present in + # subscription different from where LISA is run + subscription_id: str = "" marketplace_raw: Optional[Union[Dict[Any, Any], str]] = field( default=None, metadata=schema.metadata(data_key="marketplace") ) + shared_gallery_raw: Optional[Union[Dict[Any, Any], str]] = field( + default=None, metadata=schema.metadata(data_key="shared_gallery") + ) vhd: str = "" nic_count: int = 1 + # for marketplace image, which need to accept terms purchase_plan: Optional[AzureVmPurchasePlanSchema] = None _marketplace: InitVar[Optional[AzureVmMarketplaceSchema]] = None + _shared_gallery: InitVar[Optional[SharedImageGallerySchema]] = None + @property def marketplace(self) -> Optional[AzureVmMarketplaceSchema]: # this is a safe guard and prevent mypy error on typing @@ -132,10 +150,80 @@ def marketplace(self, value: Optional[AzureVmMarketplaceSchema]) -> None: else: self.marketplace_raw = value.to_dict() # type: ignore + @property + def shared_gallery(self) -> Optional[SharedImageGallerySchema]: + # this is a safe guard and prevent mypy error on typing + if not hasattr(self, "_shared_gallery"): + self._shared_gallery: Optional[SharedImageGallerySchema] = None + shared_gallery: Optional[SharedImageGallerySchema] = self._shared_gallery + if shared_gallery: + return shared_gallery + if isinstance(self.shared_gallery_raw, dict): + # Users decide the cases of image names, + # the inconsistent cases cause the mismatched error in notifiers. + # The lower() normalizes the image names, + # it has no impact on deployment. + self.shared_gallery_raw = dict( + (k, v.lower()) for k, v in self.shared_gallery_raw.items() + ) + shared_gallery = SharedImageGallerySchema.schema().load( # type: ignore + self.shared_gallery_raw + ) + if not shared_gallery.subscription_id: # type: ignore + shared_gallery.subscription_id = self.subscription_id # type: ignore + # this step makes shared_gallery_raw is validated, and + # filter out any unwanted content. + self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore + elif self.shared_gallery_raw: + assert isinstance( + self.shared_gallery_raw, str + ), f"actual: {type(self.shared_gallery_raw)}" + # Users decide the cases of image names, + # the inconsistent cases cause the mismatched error in notifiers. + # The lower() normalizes the image names, + # it has no impact on deployment. + shared_gallery_strings = re.split( + r"[/]+", self.shared_gallery_raw.strip().lower() + ) + if len(shared_gallery_strings) == 4: + shared_gallery = SharedImageGallerySchema(*shared_gallery_strings) + # shared_gallery_raw is used + self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore + elif len(shared_gallery_strings) == 3: + shared_gallery = SharedImageGallerySchema( + self.subscription_id, *shared_gallery_strings + ) + # shared_gallery_raw is used + self.shared_gallery_raw = shared_gallery.to_dict() # type: ignore + else: + raise LisaException( + f"Invalid value for the provided shared gallery " + f"parameter: '{self.shared_gallery_raw}'." + f"The shared gallery parameter should be in the format: " + f"'//" + f"/' or '/" + f"/'" + ) + self._shared_gallery = shared_gallery + return shared_gallery + + @shared_gallery.setter + def shared_gallery(self, value: Optional[SharedImageGallerySchema]) -> None: + self._shared_gallery = value + if value is None: + self.shared_gallery_raw = None + else: + self.shared_gallery_raw = value.to_dict() # type: ignore + def get_image_name(self) -> str: result = "" if self.vhd: result = self.vhd + elif self.shared_gallery: + assert isinstance( + self.shared_gallery_raw, dict + ), f"actual type: {type(self.shared_gallery_raw)}" + result = " ".join([x for x in self.shared_gallery_raw.values()]) elif self.marketplace: assert isinstance( self.marketplace_raw, dict diff --git a/lisa/sut_orchestrator/azure/platform_.py b/lisa/sut_orchestrator/azure/platform_.py index 97d036146e..547d6c8b6b 100644 --- a/lisa/sut_orchestrator/azure/platform_.py +++ b/lisa/sut_orchestrator/azure/platform_.py @@ -839,6 +839,9 @@ def _create_deployment_parameters( azure_node_runbook = node_space.get_extended_runbook( AzureNodeSchema, type_name=AZURE ) + # Subscription Id is used by Shared Gallery images located + # in subscription different from where LISA is run + azure_node_runbook.subscription_id = self.subscription_id # init node node = environment.nodes.from_requirement( @@ -859,7 +862,13 @@ def _create_deployment_parameters( if azure_node_runbook.vhd: # vhd is higher priority azure_node_runbook.marketplace = None - elif not azure_node_runbook.marketplace: + azure_node_runbook.shared_gallery = None + elif azure_node_runbook.shared_gallery: + azure_node_runbook.marketplace = None + elif azure_node_runbook.marketplace: + # marketplace value is already set in runbook + pass + else: # set to default marketplace, if nothing specified azure_node_runbook.marketplace = AzureVmMarketplaceSchema()