From fd1f36337b8b5740d9ec690138b785921b57a643 Mon Sep 17 00:00:00 2001 From: Nir Argaman Date: Wed, 17 May 2023 16:32:23 +0300 Subject: [PATCH 1/3] AzureCreateVirtualMachineTask: introduce new task Add new task creating VM on Azure cloud Currently it create Ubuntu --- prebuilt-tasks/pom.xml | 20 +++ .../azure/AzureCreateVirtualMachineTask.java | 153 ++++++++++++++++++ ...reVirtualMachineWorkFlowConfiguration.java | 30 ++++ 3 files changed, 203 insertions(+) create mode 100644 prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java create mode 100644 workflow-examples/src/main/java/com/redhat/parodos/examples/azure/AzureVirtualMachineWorkFlowConfiguration.java diff --git a/prebuilt-tasks/pom.xml b/prebuilt-tasks/pom.xml index 01501f526..bdb8e4652 100644 --- a/prebuilt-tasks/pom.xml +++ b/prebuilt-tasks/pom.xml @@ -16,6 +16,9 @@ 2.6.1 20230227 6.5.0.202303070854-r + 1.37.0 + 1.8.1 + 2.24.0 @@ -134,6 +137,23 @@ spring-test test + + + com.azure + azure-core + ${azure-core-version} + + + com.azure + azure-identity + ${azure-identity-version} + + + com.azure.resourcemanager + azure-resourcemanager + ${azure-resourcemanager-version} + + diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java new file mode 100644 index 000000000..33bf257f7 --- /dev/null +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java @@ -0,0 +1,153 @@ +package com.redhat.parodos.tasks.azure; + +import java.util.List; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.Region; +import com.azure.core.management.profile.AzureProfile; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.resourcemanager.AzureResourceManager; +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.KnownLinuxVirtualMachineImage; +import com.azure.resourcemanager.compute.models.VirtualMachine; +import com.azure.resourcemanager.compute.models.VirtualMachineSizeTypes; +import com.azure.resourcemanager.network.models.Network; +import com.azure.resourcemanager.network.models.NetworkInterface; +import com.azure.resourcemanager.network.models.PublicIpAddress; +import com.azure.resourcemanager.resources.models.ResourceGroup; +import com.redhat.parodos.workflow.exception.MissingParameterException; +import com.redhat.parodos.workflow.parameter.WorkParameter; +import com.redhat.parodos.workflow.parameter.WorkParameterType; +import com.redhat.parodos.workflow.task.enums.WorkFlowTaskOutput; +import com.redhat.parodos.workflow.task.infrastructure.BaseInfrastructureWorkFlowTask; +import com.redhat.parodos.workflows.work.DefaultWorkReport; +import com.redhat.parodos.workflows.work.WorkContext; +import com.redhat.parodos.workflows.work.WorkReport; +import com.redhat.parodos.workflows.work.WorkStatus; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.util.StringUtils; + +@Slf4j +public class AzureCreateVirtualMachineTask extends BaseInfrastructureWorkFlowTask { + + @Override + public @NonNull List getWorkFlowTaskParameters() { + return List.of( + WorkParameter.builder().key("vm-user-name").description("The user name for the Virtual Machine login") + .type(WorkParameterType.TEXT).optional(false).build(), + WorkParameter.builder().key("vm-ssh-public-key") + .description("The SSH public key for the Virtual Machine login").type(WorkParameterType.TEXT) + .optional(false).build(), + WorkParameter.builder().key("azure-tenant-id") + .description("The unique identifier of the Azure Active Directory instance") + .type(WorkParameterType.TEXT).optional(false).build(), + WorkParameter.builder().key("azure-subscription-id") + .description("The GUID that uniquely identifies your subscription to use Azure services") + .type(WorkParameterType.TEXT).optional(false).build(), + WorkParameter.builder().key("azure-client-id").description( + "The unique Application (client) ID assigned to your app by Azure AD when the app was registered") + .type(WorkParameterType.TEXT).optional(false).build(), + WorkParameter.builder().key("azure-client-secret").description("The password of the service principal") + .type(WorkParameterType.TEXT).optional(false).build(), + WorkParameter.builder().key("azure-resources-prefix") + .description("A designated prefix for naming all Azure resources").type(WorkParameterType.TEXT) + .optional(false).build()); + } + + @Override + /** + * Execute task, creates Virtual Machine in Azure Results with Virtual Machine created + * with public IP. This VM can be accessed using ssh (with provided username and ssh + * key) the public IP address is added to the context returned when the task is + * completed. Key: public-ip-address + */ + public WorkReport execute(WorkContext context) { + try { + final String userName = getRequiredParameterValue(context, "vm-user-name"); + final String sshKey = getRequiredParameterValue(context, "vm-ssh-public-key"); + + final String azureTenantId = getRequiredParameterValue(context, "azure-tenant-id"); + final String azureSubscriptionId = getRequiredParameterValue(context, "azure-subscription-id"); + final String azureClientId = getRequiredParameterValue(context, "azure-client-id"); + final String azureClientSecret = getRequiredParameterValue(context, "azure-client-secret"); + final String resourcesPrefix = getRequiredParameterValue(context, "azure-resources-prefix"); + + TokenCredential credential = new ClientSecretCredentialBuilder().tenantId(azureTenantId) + .clientId(azureClientId).clientSecret(azureClientSecret).build(); + + AzureProfile profile; + if (StringUtils.hasLength(azureTenantId) && StringUtils.hasLength(azureSubscriptionId)) { + profile = new AzureProfile(azureTenantId, azureSubscriptionId, AzureEnvironment.AZURE); + } + else { + profile = new AzureProfile(AzureEnvironment.AZURE); + } + + AzureResourceManager azureResourceManager = AzureResourceManager.configure() + .withLogLevel(HttpLogDetailLevel.BASIC).authenticate(credential, profile).withDefaultSubscription(); + + log.info("Creating resource group..."); + ResourceGroup resourceGroup = azureResourceManager.resourceGroups() + .define(resourcesPrefix + "ResourceGroup").withRegion(Region.US_EAST).create(); + + log.info("Creating availability set..."); + AvailabilitySet availabilitySet = azureResourceManager.availabilitySets() + .define(resourcesPrefix + "AvailabilitySet").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").create(); + + log.info("Creating public IP address..."); + PublicIpAddress publicIPAddress = azureResourceManager.publicIpAddresses() + .define(resourcesPrefix + "PublicIP").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withDynamicIP().create(); + + log.info("Creating virtual network..."); + Network network = azureResourceManager.networks().define(resourcesPrefix + "VN").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withAddressSpace("10.0.0.0/16") + .withSubnet(resourcesPrefix + "Subnet", "10.0.0.0/24").create(); + + log.info("Creating network interface..."); + NetworkInterface networkInterface = azureResourceManager.networkInterfaces().define(resourcesPrefix + "NIC") + .withRegion(Region.US_EAST).withExistingResourceGroup(resourcesPrefix + "ResourceGroup") + .withExistingPrimaryNetwork(network).withSubnet(resourcesPrefix + "Subnet") + .withPrimaryPrivateIPAddressDynamic().withExistingPrimaryPublicIPAddress(publicIPAddress).create(); + + log.info("Creating virtual machine..."); + VirtualMachine virtualMachine = azureResourceManager.virtualMachines().define(resourcesPrefix + "VM") + .withRegion(Region.US_EAST).withExistingResourceGroup(resourcesPrefix + "ResourceGroup") + .withExistingPrimaryNetworkInterface(networkInterface) + .withPopularLinuxImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_18_04_LTS) + .withRootUsername(userName).withSsh(sshKey).withComputerName(resourcesPrefix + "VM") + .withExistingAvailabilitySet(availabilitySet).withSize(VirtualMachineSizeTypes.STANDARD_D3_V2) + .create(); + + PublicIpAddress publicIpAddress = virtualMachine.getPrimaryPublicIPAddress(); + if (publicIpAddress == null) { + log.error("VirtualMachine was created but without public IP"); + } + + String ipAddress = publicIpAddress.ipAddress(); + log.info("VirtualMachine was created with public IP {}", ipAddress); + context.put("public-ip-address", ipAddress); + + return new DefaultWorkReport(WorkStatus.COMPLETED, context, null); + } + catch (MissingParameterException e) { + log.error("Task {} failed: missing required parameter, error: {}", getName(), e.getMessage()); + } + catch (Exception e) { + log.error("Task {} failed, with error: {}", getName(), e.getMessage()); + } + + return new DefaultWorkReport(WorkStatus.FAILED, context); + } + + @Override + public @NonNull List getWorkFlowTaskOutputs() { + return List.of(WorkFlowTaskOutput.OTHER); + } + +} diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/AzureVirtualMachineWorkFlowConfiguration.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/AzureVirtualMachineWorkFlowConfiguration.java new file mode 100644 index 000000000..e91228566 --- /dev/null +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/AzureVirtualMachineWorkFlowConfiguration.java @@ -0,0 +1,30 @@ +package com.redhat.parodos.examples.azure; + +import com.redhat.parodos.tasks.azure.AzureCreateVirtualMachineTask; +import com.redhat.parodos.workflow.annotation.Infrastructure; +import com.redhat.parodos.workflow.consts.WorkFlowConstants; +import com.redhat.parodos.workflows.workflow.SequentialFlow; +import com.redhat.parodos.workflows.workflow.WorkFlow; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AzureVirtualMachineWorkFlowConfiguration { + + @Bean + AzureCreateVirtualMachineTask azureCreateVirtualMachineTask() { + return new AzureCreateVirtualMachineTask(); + } + + @Bean(name = "azureVirtualMachineWorkFlow" + WorkFlowConstants.INFRASTRUCTURE_WORKFLOW) + @Infrastructure + WorkFlow azureVirtualMachineWorkFlow( + @Qualifier("azureCreateVirtualMachineTask") AzureCreateVirtualMachineTask azureCreateVirtualMachineTask) { + return SequentialFlow.Builder.aNewSequentialFlow() + .named("azureVirtualMachineWorkFlow" + WorkFlowConstants.INFRASTRUCTURE_WORKFLOW) + .execute(azureCreateVirtualMachineTask).build(); + } + +} From 931f7788d27dcf323231c4aeaf8e73d9e978f7c0 Mon Sep 17 00:00:00 2001 From: Nir Argaman Date: Sun, 21 May 2023 10:05:31 +0300 Subject: [PATCH 2/3] AzureCreateVirtualMachineTask: Add Unit tests In order to include the unit tests this commit also include changes in the task Moving all azure API access to interface (mock it in the test) --- .../azure/AzureCreateVirtualMachineTask.java | 126 ++++++-------- .../tasks/azure/AzureResourceClient.java | 27 +++ .../tasks/azure/AzureResourceClientImpl.java | 85 ++++++++++ .../AzureCreateVirtualMachineTaskTest.java | 154 ++++++++++++++++++ 4 files changed, 318 insertions(+), 74 deletions(-) create mode 100644 prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClient.java create mode 100644 prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClientImpl.java create mode 100644 prebuilt-tasks/src/test/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTaskTest.java diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java index 33bf257f7..8648c69ac 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTask.java @@ -2,17 +2,8 @@ import java.util.List; -import com.azure.core.credential.TokenCredential; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.management.AzureEnvironment; -import com.azure.core.management.Region; -import com.azure.core.management.profile.AzureProfile; -import com.azure.identity.ClientSecretCredentialBuilder; -import com.azure.resourcemanager.AzureResourceManager; import com.azure.resourcemanager.compute.models.AvailabilitySet; -import com.azure.resourcemanager.compute.models.KnownLinuxVirtualMachineImage; import com.azure.resourcemanager.compute.models.VirtualMachine; -import com.azure.resourcemanager.compute.models.VirtualMachineSizeTypes; import com.azure.resourcemanager.network.models.Network; import com.azure.resourcemanager.network.models.NetworkInterface; import com.azure.resourcemanager.network.models.PublicIpAddress; @@ -26,34 +17,52 @@ import com.redhat.parodos.workflows.work.WorkContext; import com.redhat.parodos.workflows.work.WorkReport; import com.redhat.parodos.workflows.work.WorkStatus; +import com.sun.jdi.InternalException; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; - @Slf4j public class AzureCreateVirtualMachineTask extends BaseInfrastructureWorkFlowTask { + static final String VM_USER_NAME_KEY = "vm-user-name"; + static final String VM_SSH_PUBLIC_KEY_KEY = "vm-ssh-public-key"; + static final String AZURE_TENANT_ID_KEY = "azure-tenant-id"; + static final String AZURE_SUBSCRIPTION_ID_KEY = "azure-subscription-id"; + static final String AZURE_CLIENT_ID_KEY = "azure-client-id"; + static final String AZURE_CLIENT_SECRET_KEY = "azure-client-secret"; + static final String AZURE_RESOURCES_PREFIX_KEY = "azure-resources-prefix"; + + private AzureResourceClient azureResourceClient; + + public AzureCreateVirtualMachineTask() { + this(new AzureResourceClientImpl()); + } + + AzureCreateVirtualMachineTask(AzureResourceClient azureResourceClient) { + this.azureResourceClient = azureResourceClient; + } + @Override public @NonNull List getWorkFlowTaskParameters() { return List.of( - WorkParameter.builder().key("vm-user-name").description("The user name for the Virtual Machine login") + WorkParameter.builder().key(VM_USER_NAME_KEY).description("The user name for the Virtual Machine login") .type(WorkParameterType.TEXT).optional(false).build(), - WorkParameter.builder().key("vm-ssh-public-key") + WorkParameter.builder().key(VM_SSH_PUBLIC_KEY_KEY) .description("The SSH public key for the Virtual Machine login").type(WorkParameterType.TEXT) .optional(false).build(), - WorkParameter.builder().key("azure-tenant-id") + WorkParameter.builder().key(AZURE_TENANT_ID_KEY) .description("The unique identifier of the Azure Active Directory instance") .type(WorkParameterType.TEXT).optional(false).build(), - WorkParameter.builder().key("azure-subscription-id") + WorkParameter.builder().key(AZURE_SUBSCRIPTION_ID_KEY) .description("The GUID that uniquely identifies your subscription to use Azure services") .type(WorkParameterType.TEXT).optional(false).build(), - WorkParameter.builder().key("azure-client-id").description( + WorkParameter.builder().key(AZURE_CLIENT_ID_KEY).description( "The unique Application (client) ID assigned to your app by Azure AD when the app was registered") .type(WorkParameterType.TEXT).optional(false).build(), - WorkParameter.builder().key("azure-client-secret").description("The password of the service principal") - .type(WorkParameterType.TEXT).optional(false).build(), - WorkParameter.builder().key("azure-resources-prefix") + WorkParameter.builder().key(AZURE_CLIENT_SECRET_KEY) + .description("The password of the service principal").type(WorkParameterType.TEXT) + .optional(false).build(), + WorkParameter.builder().key(AZURE_RESOURCES_PREFIX_KEY) .description("A designated prefix for naming all Azure resources").type(WorkParameterType.TEXT) .optional(false).build()); } @@ -67,82 +76,51 @@ public class AzureCreateVirtualMachineTask extends BaseInfrastructureWorkFlowTas */ public WorkReport execute(WorkContext context) { try { - final String userName = getRequiredParameterValue(context, "vm-user-name"); - final String sshKey = getRequiredParameterValue(context, "vm-ssh-public-key"); + final String userName = getRequiredParameterValue(context, VM_USER_NAME_KEY); + final String sshKey = getRequiredParameterValue(context, VM_SSH_PUBLIC_KEY_KEY); - final String azureTenantId = getRequiredParameterValue(context, "azure-tenant-id"); - final String azureSubscriptionId = getRequiredParameterValue(context, "azure-subscription-id"); - final String azureClientId = getRequiredParameterValue(context, "azure-client-id"); - final String azureClientSecret = getRequiredParameterValue(context, "azure-client-secret"); - final String resourcesPrefix = getRequiredParameterValue(context, "azure-resources-prefix"); + final String azureTenantId = getRequiredParameterValue(context, AZURE_TENANT_ID_KEY); + final String azureSubscriptionId = getRequiredParameterValue(context, AZURE_SUBSCRIPTION_ID_KEY); + final String azureClientId = getRequiredParameterValue(context, AZURE_CLIENT_ID_KEY); + final String azureClientSecret = getRequiredParameterValue(context, AZURE_CLIENT_SECRET_KEY); + final String resourcesPrefix = getRequiredParameterValue(context, AZURE_RESOURCES_PREFIX_KEY); - TokenCredential credential = new ClientSecretCredentialBuilder().tenantId(azureTenantId) - .clientId(azureClientId).clientSecret(azureClientSecret).build(); + this.azureResourceClient.init(azureTenantId, azureClientId, azureClientSecret, azureSubscriptionId); - AzureProfile profile; - if (StringUtils.hasLength(azureTenantId) && StringUtils.hasLength(azureSubscriptionId)) { - profile = new AzureProfile(azureTenantId, azureSubscriptionId, AzureEnvironment.AZURE); - } - else { - profile = new AzureProfile(AzureEnvironment.AZURE); - } + ResourceGroup resourceGroup = azureResourceClient.createResourceGroup(resourcesPrefix); + + AvailabilitySet availabilitySet = azureResourceClient.createAvailabilitySet(resourcesPrefix); - AzureResourceManager azureResourceManager = AzureResourceManager.configure() - .withLogLevel(HttpLogDetailLevel.BASIC).authenticate(credential, profile).withDefaultSubscription(); - - log.info("Creating resource group..."); - ResourceGroup resourceGroup = azureResourceManager.resourceGroups() - .define(resourcesPrefix + "ResourceGroup").withRegion(Region.US_EAST).create(); - - log.info("Creating availability set..."); - AvailabilitySet availabilitySet = azureResourceManager.availabilitySets() - .define(resourcesPrefix + "AvailabilitySet").withRegion(Region.US_EAST) - .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").create(); - - log.info("Creating public IP address..."); - PublicIpAddress publicIPAddress = azureResourceManager.publicIpAddresses() - .define(resourcesPrefix + "PublicIP").withRegion(Region.US_EAST) - .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withDynamicIP().create(); - - log.info("Creating virtual network..."); - Network network = azureResourceManager.networks().define(resourcesPrefix + "VN").withRegion(Region.US_EAST) - .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withAddressSpace("10.0.0.0/16") - .withSubnet(resourcesPrefix + "Subnet", "10.0.0.0/24").create(); - - log.info("Creating network interface..."); - NetworkInterface networkInterface = azureResourceManager.networkInterfaces().define(resourcesPrefix + "NIC") - .withRegion(Region.US_EAST).withExistingResourceGroup(resourcesPrefix + "ResourceGroup") - .withExistingPrimaryNetwork(network).withSubnet(resourcesPrefix + "Subnet") - .withPrimaryPrivateIPAddressDynamic().withExistingPrimaryPublicIPAddress(publicIPAddress).create(); - - log.info("Creating virtual machine..."); - VirtualMachine virtualMachine = azureResourceManager.virtualMachines().define(resourcesPrefix + "VM") - .withRegion(Region.US_EAST).withExistingResourceGroup(resourcesPrefix + "ResourceGroup") - .withExistingPrimaryNetworkInterface(networkInterface) - .withPopularLinuxImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_18_04_LTS) - .withRootUsername(userName).withSsh(sshKey).withComputerName(resourcesPrefix + "VM") - .withExistingAvailabilitySet(availabilitySet).withSize(VirtualMachineSizeTypes.STANDARD_D3_V2) - .create(); + PublicIpAddress publicIPAddress = azureResourceClient.createPublicIpAddress(resourcesPrefix); + + Network network = azureResourceClient.createNetwork(resourcesPrefix); + + NetworkInterface networkInterface = azureResourceClient.createNetworkInterface(resourcesPrefix, network, + publicIPAddress); + + VirtualMachine virtualMachine = azureResourceClient.createVirtualMachine(resourcesPrefix, networkInterface, + availabilitySet, userName, sshKey); PublicIpAddress publicIpAddress = virtualMachine.getPrimaryPublicIPAddress(); if (publicIpAddress == null) { log.error("VirtualMachine was created but without public IP"); + throw new InternalException("The new created VirtualMachine missing public IP address"); } String ipAddress = publicIpAddress.ipAddress(); log.info("VirtualMachine was created with public IP {}", ipAddress); context.put("public-ip-address", ipAddress); - - return new DefaultWorkReport(WorkStatus.COMPLETED, context, null); } catch (MissingParameterException e) { log.error("Task {} failed: missing required parameter, error: {}", getName(), e.getMessage()); + return new DefaultWorkReport(WorkStatus.FAILED, context, e); } catch (Exception e) { log.error("Task {} failed, with error: {}", getName(), e.getMessage()); + return new DefaultWorkReport(WorkStatus.FAILED, context, e); } - return new DefaultWorkReport(WorkStatus.FAILED, context); + return new DefaultWorkReport(WorkStatus.COMPLETED, context); } @Override diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClient.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClient.java new file mode 100644 index 000000000..ee3aa046c --- /dev/null +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClient.java @@ -0,0 +1,27 @@ +package com.redhat.parodos.tasks.azure; + +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.VirtualMachine; +import com.azure.resourcemanager.network.models.Network; +import com.azure.resourcemanager.network.models.NetworkInterface; +import com.azure.resourcemanager.network.models.PublicIpAddress; +import com.azure.resourcemanager.resources.models.ResourceGroup; + +interface AzureResourceClient { + + void init(String azureTenantId, String azureClientId, String azureClientSecret, String azureSubscriptionId); + + ResourceGroup createResourceGroup(String resourcesPrefix); + + AvailabilitySet createAvailabilitySet(String resourcesPrefix); + + PublicIpAddress createPublicIpAddress(String resourcesPrefix); + + Network createNetwork(String resourcesPrefix); + + NetworkInterface createNetworkInterface(String resourcesPrefix, Network network, PublicIpAddress publicIPAddress); + + VirtualMachine createVirtualMachine(String resourcesPrefix, NetworkInterface networkInterface, + AvailabilitySet availabilitySet, String userName, String sshKey); + +} diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClientImpl.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClientImpl.java new file mode 100644 index 000000000..862857f55 --- /dev/null +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/azure/AzureResourceClientImpl.java @@ -0,0 +1,85 @@ +package com.redhat.parodos.tasks.azure; + +import com.azure.core.credential.TokenCredential; +import com.azure.core.http.policy.HttpLogDetailLevel; +import com.azure.core.management.AzureEnvironment; +import com.azure.core.management.Region; +import com.azure.core.management.profile.AzureProfile; +import com.azure.identity.ClientSecretCredentialBuilder; +import com.azure.resourcemanager.AzureResourceManager; +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.KnownLinuxVirtualMachineImage; +import com.azure.resourcemanager.compute.models.VirtualMachine; +import com.azure.resourcemanager.compute.models.VirtualMachineSizeTypes; +import com.azure.resourcemanager.network.models.Network; +import com.azure.resourcemanager.network.models.NetworkInterface; +import com.azure.resourcemanager.network.models.PublicIpAddress; +import com.azure.resourcemanager.resources.models.ResourceGroup; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class AzureResourceClientImpl implements AzureResourceClient { + + private AzureResourceManager azureResourceManager; + + @Override + public void init(String azureTenantId, String azureClientId, String azureClientSecret, String azureSubscriptionId) { + TokenCredential credential = new ClientSecretCredentialBuilder().tenantId(azureTenantId).clientId(azureClientId) + .clientSecret(azureClientSecret).build(); + AzureProfile profile = new AzureProfile(azureTenantId, azureSubscriptionId, AzureEnvironment.AZURE); + azureResourceManager = AzureResourceManager.configure().withLogLevel(HttpLogDetailLevel.BASIC) + .authenticate(credential, profile).withDefaultSubscription(); + } + + @Override + public ResourceGroup createResourceGroup(String resourcesPrefix) { + log.info("Creating resource group..."); + return azureResourceManager.resourceGroups().define(resourcesPrefix + "ResourceGroup") + .withRegion(Region.US_EAST).create(); + } + + @Override + public AvailabilitySet createAvailabilitySet(String resourcesPrefix) { + log.info("Creating availability set..."); + return azureResourceManager.availabilitySets().define(resourcesPrefix + "AvailabilitySet") + .withRegion(Region.US_EAST).withExistingResourceGroup(resourcesPrefix + "ResourceGroup").create(); + } + + @Override + public PublicIpAddress createPublicIpAddress(String resourcesPrefix) { + log.info("Creating public IP address..."); + return azureResourceManager.publicIpAddresses().define(resourcesPrefix + "PublicIP").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withDynamicIP().create(); + } + + @Override + public Network createNetwork(String resourcesPrefix) { + log.info("Creating virtual network..."); + return azureResourceManager.networks().define(resourcesPrefix + "VN").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withAddressSpace("10.0.0.0/16") + .withSubnet(resourcesPrefix + "Subnet", "10.0.0.0/24").create(); + } + + @Override + public NetworkInterface createNetworkInterface(String resourcesPrefix, Network network, + PublicIpAddress publicIPAddress) { + log.info("Creating network interface..."); + return azureResourceManager.networkInterfaces().define(resourcesPrefix + "NIC").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup").withExistingPrimaryNetwork(network) + .withSubnet(resourcesPrefix + "Subnet").withPrimaryPrivateIPAddressDynamic() + .withExistingPrimaryPublicIPAddress(publicIPAddress).create(); + } + + @Override + public VirtualMachine createVirtualMachine(String resourcesPrefix, NetworkInterface networkInterface, + AvailabilitySet availabilitySet, String userName, String sshKey) { + log.info("Creating virtual machine..."); + return azureResourceManager.virtualMachines().define(resourcesPrefix + "VM").withRegion(Region.US_EAST) + .withExistingResourceGroup(resourcesPrefix + "ResourceGroup") + .withExistingPrimaryNetworkInterface(networkInterface) + .withPopularLinuxImage(KnownLinuxVirtualMachineImage.UBUNTU_SERVER_18_04_LTS).withRootUsername(userName) + .withSsh(sshKey).withComputerName(resourcesPrefix + "VM").withExistingAvailabilitySet(availabilitySet) + .withSize(VirtualMachineSizeTypes.STANDARD_D3_V2).create(); + } + +} diff --git a/prebuilt-tasks/src/test/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTaskTest.java b/prebuilt-tasks/src/test/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTaskTest.java new file mode 100644 index 000000000..34b794d8f --- /dev/null +++ b/prebuilt-tasks/src/test/java/com/redhat/parodos/tasks/azure/AzureCreateVirtualMachineTaskTest.java @@ -0,0 +1,154 @@ +package com.redhat.parodos.tasks.azure; + +import java.util.HashMap; + +import com.azure.resourcemanager.compute.models.AvailabilitySet; +import com.azure.resourcemanager.compute.models.VirtualMachine; +import com.azure.resourcemanager.network.models.Network; +import com.azure.resourcemanager.network.models.NetworkInterface; +import com.azure.resourcemanager.network.models.PublicIpAddress; +import com.azure.resourcemanager.resources.models.ResourceGroup; +import com.redhat.parodos.workflow.context.WorkContextDelegate; +import com.redhat.parodos.workflow.exception.MissingParameterException; +import com.redhat.parodos.workflows.work.WorkContext; +import com.redhat.parodos.workflows.work.WorkReport; +import com.redhat.parodos.workflows.work.WorkStatus; +import com.sun.jdi.InternalException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AzureCreateVirtualMachineTaskTest { + + private static WorkContext ctx; + + private static AzureResourceClient azureResourceClient; + + // Define all Azure resources mocks + private static AzureCreateVirtualMachineTask underTest; + + private static ResourceGroup resourceGroup; + + private static AvailabilitySet availabilitySet; + + private static PublicIpAddress publicIPAddress; + + private static Network network; + + private static NetworkInterface networkInterface; + + private static VirtualMachine virtualMachine; + + private static final String[] requiredParamsKeys = { AzureCreateVirtualMachineTask.VM_USER_NAME_KEY, + AzureCreateVirtualMachineTask.VM_SSH_PUBLIC_KEY_KEY, AzureCreateVirtualMachineTask.AZURE_TENANT_ID_KEY, + AzureCreateVirtualMachineTask.AZURE_SUBSCRIPTION_ID_KEY, AzureCreateVirtualMachineTask.AZURE_CLIENT_ID_KEY, + AzureCreateVirtualMachineTask.AZURE_CLIENT_SECRET_KEY, + AzureCreateVirtualMachineTask.AZURE_RESOURCES_PREFIX_KEY }; + + private static final String resourcesPrefix = AzureCreateVirtualMachineTask.AZURE_RESOURCES_PREFIX_KEY + + "-testValue"; + + private static final String userName = AzureCreateVirtualMachineTask.VM_USER_NAME_KEY + "-testValue"; + + private static final String sshKey = AzureCreateVirtualMachineTask.VM_SSH_PUBLIC_KEY_KEY + "-testValue"; + + @BeforeEach + public void setUp() throws Exception { + // Initiate all Azure resources mocks + azureResourceClient = mock(AzureResourceClient.class); + resourceGroup = mock(ResourceGroup.class); + availabilitySet = mock(AvailabilitySet.class); + publicIPAddress = mock(PublicIpAddress.class); + network = mock(Network.class); + networkInterface = mock(NetworkInterface.class); + virtualMachine = mock(VirtualMachine.class); + + underTest = new AzureCreateVirtualMachineTask(azureResourceClient); + underTest.setBeanName("AzureCreateVirtualMachineTask"); + ctx = new WorkContext(); + + HashMap map = new HashMap<>(); + for (String paramKey : requiredParamsKeys) { + map.put(paramKey, paramKey + "-testValue"); + WorkContextDelegate.write(ctx, WorkContextDelegate.ProcessType.WORKFLOW_TASK_EXECUTION, underTest.getName(), + WorkContextDelegate.Resource.ARGUMENTS, map); + } + + when(azureResourceClient.createResourceGroup(resourcesPrefix)).thenReturn(resourceGroup); + when(azureResourceClient.createAvailabilitySet(resourcesPrefix)).thenReturn(availabilitySet); + when(azureResourceClient.createPublicIpAddress(resourcesPrefix)).thenReturn(publicIPAddress); + when(azureResourceClient.createNetwork(resourcesPrefix)).thenReturn(network); + when(azureResourceClient.createNetworkInterface(resourcesPrefix, network, publicIPAddress)) + .thenReturn(networkInterface); + when(azureResourceClient.createVirtualMachine(resourcesPrefix, networkInterface, availabilitySet, userName, + sshKey)).thenReturn(virtualMachine); + } + + @Test + public void testHappyFlow() { + // given + String publicIP = "11.11.11.11"; + when(virtualMachine.getPrimaryPublicIPAddress()).thenReturn(publicIPAddress); + when(publicIPAddress.ipAddress()).thenReturn(publicIP); + + // when + WorkReport result = underTest.execute(ctx); + + // then + verify(azureResourceClient, times(1)).createResourceGroup(resourcesPrefix); + verify(azureResourceClient, times(1)).createAvailabilitySet(resourcesPrefix); + verify(azureResourceClient, times(1)).createPublicIpAddress(resourcesPrefix); + verify(azureResourceClient, times(1)).createNetwork(resourcesPrefix); + verify(azureResourceClient, times(1)).createNetworkInterface(resourcesPrefix, network, publicIPAddress); + verify(azureResourceClient, times(1)).createVirtualMachine(resourcesPrefix, networkInterface, availabilitySet, + userName, sshKey); + + assertEquals(WorkStatus.COMPLETED, result.getStatus()); + assertEquals(publicIP, ctx.get("public-ip-address")); + + } + + @Test + public void testVmMissingPublicIPErr() { + // given + String publicIP = "11.11.11.11"; + when(virtualMachine.getPrimaryPublicIPAddress()).thenReturn(null); + + // when + WorkReport result = underTest.execute(ctx); + + // then + assertEquals(WorkStatus.FAILED, result.getStatus()); + assertEquals(InternalException.class, result.getError().getClass()); + assertEquals("The new created VirtualMachine missing public IP address", result.getError().getMessage()); + + } + + @Test + public void testMissingRequiredParamErr() { + // given + WorkContext ctx = new WorkContext(); + HashMap map = new HashMap<>(); + + for (String paramKey : requiredParamsKeys) { + // when + WorkReport result = underTest.execute(ctx); + + // then + assertEquals(WorkStatus.FAILED, result.getStatus()); + assertEquals(MissingParameterException.class, result.getError().getClass()); + assertEquals("missing parameter(s) for ParameterName: " + paramKey, result.getError().getMessage()); + + // Adding this key to the context for next for loop + map.put(paramKey, paramKey + "-testValue"); + WorkContextDelegate.write(ctx, WorkContextDelegate.ProcessType.WORKFLOW_TASK_EXECUTION, underTest.getName(), + WorkContextDelegate.Resource.ARGUMENTS, map); + } + } + +} From 463e04a2de9f25f7855f4977d0224ff5b400f9db Mon Sep 17 00:00:00 2001 From: Nir Argaman Date: Sun, 21 May 2023 12:08:28 +0300 Subject: [PATCH 3/3] AzureCreateVirtualMachineTask: Add instructions how to use example workflow configuration --- .../redhat/parodos/examples/azure/README.md | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 workflow-examples/src/main/java/com/redhat/parodos/examples/azure/README.md diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/README.md b/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/README.md new file mode 100644 index 000000000..6296ae052 --- /dev/null +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/azure/README.md @@ -0,0 +1,117 @@ +# Running AzureVirtualMachine WorkFlow Configuration instructions + +AzureVirtualMachineWorkFlow Configuration is an illustration of configuration utilizing the AzureVirtualMachineCreateVM Task. +Below are guidelines on executing this example workflow configuration using curl. + +## 1. Prerequisites + +* Have account in azure, at least contributor user is required (In this case, second step should be requested from Admin). +* You updated workflow-service running and accessible from your machine. + +## 2. Create App Registration in Azure account + +The workflow-service java application needs read and create permissions in your Azure subscription to run the AzureVirtualMachineCreateVM Task. Create a service principal, which its credentials would be used by the application. Service principals provide a way to create a noninteractive account associated with your identity to which you grant only the privileges your app needs to run. + +This step must be done by Admin user in Azure. +It can be done only ones, make sure it has the required expiration date. + +### Configure the environment variable + +- **`AZURE_SUBSCRIPTION_ID`**: Use the id value from `az account show` in the Azure CLI 2.0. + +### Create a service principal by using the Azure CLI 2.0, and capture the output + +```shell +az ad sp create-for-rbac \ +--name AzureJavaTest \ +--role Contributor \ +--scopes /subscriptions/${AZURE_SUBSCRIPTION_ID} +``` + +## 3. Execute the workflow using curl + +### Configure the environment variables + +- **`AZURE_SUBSCRIPTION_ID`**: Use the id value from `az account show` in the Azure CLI 2.0. +- **`AZURE_CLIENT_ID`**: Use the appId value from the output taken from a service principal output. +- **`AZURE_CLIENT_SECRET`**: Use the password value from the service principal output. +- **`AZURE_TENANT_ID`**: Use the tenant value from the service principal output. +- **`AZURE_RESOURCES_PREFIX`**: A designated prefix for naming all Azure resources +- **`VM_USER_NAME`**: The username for the Virtual Machine login +- **`VM_SSH_PUBLIC_KEY`**: The SSH public key for the Virtual Machine login +- **`PARODOS_PROJECT_ID`**: Parodos project ID + +### Execute the workflow + +```shell +curl -X 'POST' -u test:test \ + 'http://localhost:8080/api/v1/workflows' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d '{ + "arguments": [], + "projectId": "'"${PARODOS_PROJECT_ID}"'", + "workFlowName": "azureVirtualMachineWorkFlow_INFRASTRUCTURE_WORKFLOW", + "works": [ + { + "arguments": [ + { + "key": "azure-tenant-id", + "value": "'"${AZURE_TENANT_ID}"'" + }, + { + "key": "azure-subscription-id", + "value": "'"${AZURE_SUBSCRIPTION_ID}"'" + }, + { + "key": "azure-client-id", + "value": "'"${AZURE_CLIENT_ID}"'" + }, + { + "key": "azure-client-secret", + "value": "'"${AZURE_CLIENT_SECRET}"'" + }, + { + "key": "azure-resources-prefix", + "value": "'"${AZURE_RESOURCES_PREFIX}"'" + }, + { + "key": "vm-user-name", + "value": "'"${VM_USER_NAME}"'" + }, + { + "key": "vm-ssh-public-key", + "value": "'"${VM_SSH_PUBLIC_KEY}"'" + } + ], + "type": "TASK", + "workName": "azureCreateVirtualMachineTask" + } + ] +}' +``` +## 4. Validate access to the VM + +### Configure the environment variables + +- **`VM_PUBLIC_IP`**: The public IP was assigned to the VM, you can find it in the workflow-service logs +- **`VM_USER_NAME`**: The username for the Virtual Machine login +- **`PRIVATE_SSH_KEY_PATH`**: The private ssh key which its public key was used for the VM creation in previous step + +### Access the VM using ssh + +```shell +ssh -i ${PRIVATE_SSH_KEY_PATH} ${VM_USER_NAME}@${VM_PUBLIC_IP} +``` +## 5. Delete all resources in Azure + +### Configure the environment variables + +- **`AZURE_RESOURCES_PREFIX`**: A designated prefix for naming all Azure resources + +### delete the VM + +```shell +az group delete -n "${AZURE_RESOURCES_PREFIX}ResourceGroup" +``` +