// createVirtualMachine creates a virtual machine and related resources. // // All resources created are tagged with the specified "vmTags", so if // this function fails then all resources can be deleted by tag. func (env *azureEnviron) createVirtualMachine( vmName string, vmTags, envTags map[string]string, instanceSpec *instances.InstanceSpec, instanceConfig *instancecfg.InstanceConfig, storageAccountType string, ) error { deploymentsClient := resources.DeploymentsClient{env.resources} var apiPort int if instanceConfig.Controller != nil { apiPortValue := instanceConfig.Controller.Config.APIPort() apiPort = apiPortValue } else { apiPorts := instanceConfig.APIInfo.Ports() if len(apiPorts) != 1 { return errors.Errorf("expected one API port, found %v", apiPorts) } apiPort = apiPorts[0] } resources := networkTemplateResources(env.location, envTags, apiPort) resources = append(resources, storageAccountTemplateResource( env.location, envTags, env.storageAccountName, storageAccountType, )) osProfile, seriesOS, err := newOSProfile( vmName, instanceConfig, env.provider.config.RandomWindowsAdminPassword, ) if err != nil { return errors.Annotate(err, "creating OS profile") } storageProfile, err := newStorageProfile(vmName, env.storageAccountName, instanceSpec) if err != nil { return errors.Annotate(err, "creating storage profile") } var vmDependsOn []string var availabilitySetSubResource *compute.SubResource availabilitySetName, err := availabilitySetName( vmName, vmTags, instanceConfig.Controller != nil, ) if err != nil { return errors.Annotate(err, "getting availability set name") } if availabilitySetName != "" { availabilitySetId := fmt.Sprintf( `[resourceId('Microsoft.Compute/availabilitySets','%s')]`, availabilitySetName, ) resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/availabilitySets", Name: availabilitySetName, Location: env.location, Tags: envTags, }) availabilitySetSubResource = &compute.SubResource{ ID: to.StringPtr(availabilitySetId), } vmDependsOn = append(vmDependsOn, availabilitySetId) } publicIPAddressName := vmName + "-public-ip" publicIPAddressId := fmt.Sprintf(`[resourceId('Microsoft.Network/publicIPAddresses', '%s')]`, publicIPAddressName) resources = append(resources, armtemplates.Resource{ APIVersion: network.APIVersion, Type: "Microsoft.Network/publicIPAddresses", Name: publicIPAddressName, Location: env.location, Tags: vmTags, Properties: &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Dynamic, }, }) // Controller and non-controller machines are assigned to separate // subnets. This enables us to create controller-specific NSG rules // just by targeting the controller subnet. subnetName := internalSubnetName subnetPrefix := internalSubnetPrefix if instanceConfig.Controller != nil { subnetName = controllerSubnetName subnetPrefix = controllerSubnetPrefix } subnetId := fmt.Sprintf( `[concat(resourceId('Microsoft.Network/virtualNetworks', '%s'), '/subnets/%s')]`, internalNetworkName, subnetName, ) privateIP, err := machineSubnetIP(subnetPrefix, instanceConfig.MachineId) if err != nil { return errors.Annotatef(err, "computing private IP address") } nicName := vmName + "-primary" nicId := fmt.Sprintf(`[resourceId('Microsoft.Network/networkInterfaces', '%s')]`, nicName) ipConfigurations := []network.InterfaceIPConfiguration{{ Name: to.StringPtr("primary"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ Primary: to.BoolPtr(true), PrivateIPAddress: to.StringPtr(privateIP.String()), PrivateIPAllocationMethod: network.Static, Subnet: &network.Subnet{ID: to.StringPtr(subnetId)}, PublicIPAddress: &network.PublicIPAddress{ ID: to.StringPtr(publicIPAddressId), }, }, }} resources = append(resources, armtemplates.Resource{ APIVersion: network.APIVersion, Type: "Microsoft.Network/networkInterfaces", Name: nicName, Location: env.location, Tags: vmTags, Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &ipConfigurations, }, DependsOn: []string{ publicIPAddressId, fmt.Sprintf( `[resourceId('Microsoft.Network/virtualNetworks', '%s')]`, internalNetworkName, ), }, }) nics := []compute.NetworkInterfaceReference{{ ID: to.StringPtr(nicId), Properties: &compute.NetworkInterfaceReferenceProperties{ Primary: to.BoolPtr(true), }, }} vmDependsOn = append(vmDependsOn, nicId) vmDependsOn = append(vmDependsOn, fmt.Sprintf( `[resourceId('Microsoft.Storage/storageAccounts', '%s')]`, env.storageAccountName, )) resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines", Name: vmName, Location: env.location, Tags: vmTags, Properties: &compute.VirtualMachineProperties{ HardwareProfile: &compute.HardwareProfile{ VMSize: compute.VirtualMachineSizeTypes( instanceSpec.InstanceType.Name, ), }, StorageProfile: storageProfile, OsProfile: osProfile, NetworkProfile: &compute.NetworkProfile{ &nics, }, AvailabilitySet: availabilitySetSubResource, }, DependsOn: vmDependsOn, }) // On Windows and CentOS, we must add the CustomScript VM // extension to run the CustomData script. switch seriesOS { case os.Windows, os.CentOS: properties, err := vmExtensionProperties(seriesOS) if err != nil { return errors.Annotate( err, "creating virtual machine extension", ) } resources = append(resources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines/extensions", Name: vmName + "/" + extensionName, Location: env.location, Tags: vmTags, Properties: properties, DependsOn: []string{"Microsoft.Compute/virtualMachines/" + vmName}, }) } logger.Debugf("- creating virtual machine deployment") template := armtemplates.Template{Resources: resources} // NOTE(axw) VMs take a long time to go to "Succeeded", so we do not // block waiting for them to be fully provisioned. This means we won't // return an error from StartInstance if the VM fails provisioning; // we will instead report the error via the instance's status. deploymentsClient.ResponseInspector = asyncCreationRespondDecorator( deploymentsClient.ResponseInspector, ) if err := createDeployment( env.callAPI, deploymentsClient, env.resourceGroup, vmName, // deployment name template, ); err != nil { return errors.Trace(err) } return nil }