func newOSProfile( vmName string, instanceConfig *instancecfg.InstanceConfig, randomAdminPassword func() string, ) (*compute.OSProfile, os.OSType, error) { logger.Debugf("creating OS profile for %q", vmName) customData, err := providerinit.ComposeUserData(instanceConfig, nil, AzureRenderer{}) if err != nil { return nil, os.Unknown, errors.Annotate(err, "composing user data") } osProfile := &compute.OSProfile{ ComputerName: to.StringPtr(vmName), CustomData: to.StringPtr(string(customData)), } seriesOS, err := jujuseries.GetOSFromSeries(instanceConfig.Series) if err != nil { return nil, os.Unknown, errors.Trace(err) } switch seriesOS { case os.Ubuntu, os.CentOS: // SSH keys are handled by custom data, but must also be // specified in order to forego providing a password, and // disable password authentication. publicKeys := []compute.SSHPublicKey{{ Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), KeyData: to.StringPtr(instanceConfig.AuthorizedKeys), }} osProfile.AdminUsername = to.StringPtr("ubuntu") osProfile.LinuxConfiguration = &compute.LinuxConfiguration{ DisablePasswordAuthentication: to.BoolPtr(true), SSH: &compute.SSHConfiguration{PublicKeys: &publicKeys}, } case os.Windows: osProfile.AdminUsername = to.StringPtr("JujuAdministrator") // A password is required by Azure, but we will never use it. // We generate something sufficiently long and random that it // should be infeasible to guess. osProfile.AdminPassword = to.StringPtr(randomAdminPassword()) osProfile.WindowsConfiguration = &compute.WindowsConfiguration{ ProvisionVMAgent: to.BoolPtr(true), EnableAutomaticUpdates: to.BoolPtr(true), // TODO(?) add WinRM configuration here. } default: return nil, os.Unknown, errors.NotSupportedf("%s", seriesOS) } return osProfile, seriesOS, nil }
func (c *Config) toVirtualMachineCaptureParameters() *compute.VirtualMachineCaptureParameters { return &compute.VirtualMachineCaptureParameters{ DestinationContainerName: &c.CaptureContainerName, VhdPrefix: &c.CaptureNamePrefix, OverwriteVhds: to.BoolPtr(false), } }
func (s *environSuite) TestStartInstanceCentOS(c *gc.C) { // Starting a CentOS VM, we should not expect an image query. s.PatchValue(&s.ubuntuServerSKUs, nil) env := s.openEnviron(c) s.sender = s.startInstanceSenders(false) s.requests = nil args := makeStartInstanceParams(c, s.controllerUUID, "centos7") _, err := env.StartInstance(args) c.Assert(err, jc.ErrorIsNil) vmExtensionSettings := map[string]interface{}{ "commandToExecute": `bash -c 'base64 -d /var/lib/waagent/CustomData | bash'`, } s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{ imageReference: ¢os7ImageReference, diskSizeGB: 32, vmExtension: &compute.VirtualMachineExtensionProperties{ Publisher: to.StringPtr("Microsoft.OSTCExtensions"), Type: to.StringPtr("CustomScriptForLinux"), TypeHandlerVersion: to.StringPtr("1.4"), AutoUpgradeMinorVersion: to.BoolPtr(true), Settings: &vmExtensionSettings, }, osProfile: &linuxOsProfile, }) }
func (s *TemplateBuilder) BuildWindows(keyVaultName, winRMCertificateUrl string) error { resource, err := s.getResourceByType(resourceVirtualMachine) if err != nil { return err } profile := resource.Properties.OsProfile profile.Secrets = &[]compute.VaultSecretGroup{ { SourceVault: &compute.SubResource{ ID: to.StringPtr(s.toResourceID(resourceKeyVaults, keyVaultName)), }, VaultCertificates: &[]compute.VaultCertificate{ { CertificateStore: to.StringPtr("My"), CertificateURL: to.StringPtr(winRMCertificateUrl), }, }, }, } profile.WindowsConfiguration = &compute.WindowsConfiguration{ ProvisionVMAgent: to.BoolPtr(true), WinRM: &compute.WinRMConfiguration{ Listeners: &[]compute.WinRMListener{ { Protocol: "https", CertificateURL: to.StringPtr(winRMCertificateUrl), }, }, }, } return nil }
// vmExtension creates a CustomScript VM extension for the given VM // which will execute the CustomData on the machine as a script. func vmExtensionProperties(os jujuos.OSType) (*compute.VirtualMachineExtensionProperties, error) { var commandToExecute, extensionPublisher, extensionType, extensionVersion string switch os { case jujuos.Windows: commandToExecute = windowsExecuteCustomScriptCommand extensionPublisher = windowsCustomScriptPublisher extensionType = windowsCustomScriptType extensionVersion = windowsCustomScriptVersion case jujuos.CentOS: commandToExecute = linuxExecuteCustomScriptCommand extensionPublisher = linuxCustomScriptPublisher extensionType = linuxCustomScriptType extensionVersion = linuxCustomScriptVersion default: // Ubuntu renders CustomData as cloud-config, and interprets // it with cloud-init. Windows and CentOS do not use cloud-init // on Azure. return nil, errors.NotSupportedf("CustomScript extension for OS %q", os) } extensionSettings := map[string]interface{}{ "commandToExecute": commandToExecute, } return &compute.VirtualMachineExtensionProperties{ Publisher: to.StringPtr(extensionPublisher), Type: to.StringPtr(extensionType), TypeHandlerVersion: to.StringPtr(extensionVersion), AutoUpgradeMinorVersion: to.BoolPtr(true), Settings: &extensionSettings, }, nil }
func (s *environSuite) testStartInstanceWindows( c *gc.C, cons constraints.Value, expect uint64, requestValue int, ) { // Starting a Windows VM, we should not expect an image query. s.PatchValue(&s.ubuntuServerSKUs, nil) env := s.openEnviron(c) s.sender = s.startInstanceSenders(false) s.requests = nil args := makeStartInstanceParams(c, s.controllerUUID, "win2012") args.Constraints = cons result, err := env.StartInstance(args) c.Assert(err, jc.ErrorIsNil) c.Assert(result, gc.NotNil) c.Assert(result.Hardware.RootDisk, jc.DeepEquals, &expect) vmExtensionSettings := map[string]interface{}{ "commandToExecute": `` + `move C:\AzureData\CustomData.bin C:\AzureData\CustomData.ps1 && ` + `powershell.exe -ExecutionPolicy Unrestricted -File C:\AzureData\CustomData.ps1 && ` + `del /q C:\AzureData\CustomData.ps1`, } s.assertStartInstanceRequests(c, s.requests, assertStartInstanceRequestsParams{ imageReference: &win2012ImageReference, diskSizeGB: requestValue, vmExtension: &compute.VirtualMachineExtensionProperties{ Publisher: to.StringPtr("Microsoft.Compute"), Type: to.StringPtr("CustomScriptExtension"), TypeHandlerVersion: to.StringPtr("1.4"), AutoUpgradeMinorVersion: to.BoolPtr(true), Settings: &vmExtensionSettings, }, osProfile: &windowsOsProfile, }) }
// This ensures load balancer exists and the frontend ip config is setup. // This also reconciles the Service's Ports with the LoadBalancer config. // This entails adding rules/probes for expected Ports and removing stale rules/ports. func (az *Cloud) reconcileLoadBalancer(lb network.LoadBalancer, pip *network.PublicIPAddress, clusterName string, service *v1.Service, nodeNames []string) (network.LoadBalancer, bool, error) { lbName := getLoadBalancerName(clusterName) serviceName := getServiceName(service) lbFrontendIPConfigName := getFrontendIPConfigName(service) lbFrontendIPConfigID := az.getFrontendIPConfigID(lbName, lbFrontendIPConfigName) lbBackendPoolName := getBackendPoolName(clusterName) lbBackendPoolID := az.getBackendPoolID(lbName, lbBackendPoolName) wantLb := len(service.Spec.Ports) > 0 dirtyLb := false // Ensure LoadBalancer's Backend Pool Configuration if wantLb { if lb.Properties.BackendAddressPools == nil || len(*lb.Properties.BackendAddressPools) == 0 { lb.Properties.BackendAddressPools = &[]network.BackendAddressPool{ { Name: to.StringPtr(lbBackendPoolName), }, } glog.V(10).Infof("reconcile(%s)(%t): lb backendpool - adding", serviceName, wantLb) dirtyLb = true } else if len(*lb.Properties.BackendAddressPools) != 1 || !strings.EqualFold(*(*lb.Properties.BackendAddressPools)[0].Name, lbBackendPoolName) { return lb, false, fmt.Errorf("loadbalancer is misconfigured with a different backend pool") } } // Ensure LoadBalancer's Frontend IP Configurations dirtyConfigs := false newConfigs := []network.FrontendIPConfiguration{} if lb.Properties.FrontendIPConfigurations != nil { newConfigs = *lb.Properties.FrontendIPConfigurations } if !wantLb { for i := len(newConfigs) - 1; i >= 0; i-- { config := newConfigs[i] if strings.EqualFold(*config.Name, lbFrontendIPConfigName) { glog.V(3).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - dropping", serviceName, wantLb, lbFrontendIPConfigName) newConfigs = append(newConfigs[:i], newConfigs[i+1:]...) dirtyConfigs = true } } } else { foundConfig := false for _, config := range newConfigs { if strings.EqualFold(*config.Name, lbFrontendIPConfigName) { foundConfig = true break } } if !foundConfig { newConfigs = append(newConfigs, network.FrontendIPConfiguration{ Name: to.StringPtr(lbFrontendIPConfigName), Properties: &network.FrontendIPConfigurationPropertiesFormat{ PublicIPAddress: &network.PublicIPAddress{ ID: pip.ID, }, }, }) glog.V(10).Infof("reconcile(%s)(%t): lb frontendconfig(%s) - adding", serviceName, wantLb, lbFrontendIPConfigName) dirtyConfigs = true } } if dirtyConfigs { dirtyLb = true lb.Properties.FrontendIPConfigurations = &newConfigs } // update probes/rules expectedProbes := make([]network.Probe, len(service.Spec.Ports)) expectedRules := make([]network.LoadBalancingRule, len(service.Spec.Ports)) for i, port := range service.Spec.Ports { lbRuleName := getRuleName(service, port) transportProto, _, probeProto, err := getProtocolsFromKubernetesProtocol(port.Protocol) if err != nil { return lb, false, err } if serviceapi.NeedsHealthCheck(service) { podPresencePath, podPresencePort := serviceapi.GetServiceHealthCheckPathPort(service) expectedProbes[i] = network.Probe{ Name: &lbRuleName, Properties: &network.ProbePropertiesFormat{ RequestPath: to.StringPtr(podPresencePath), Protocol: network.ProbeProtocolHTTP, Port: to.Int32Ptr(podPresencePort), IntervalInSeconds: to.Int32Ptr(5), NumberOfProbes: to.Int32Ptr(2), }, } } else { expectedProbes[i] = network.Probe{ Name: &lbRuleName, Properties: &network.ProbePropertiesFormat{ Protocol: probeProto, Port: to.Int32Ptr(port.NodePort), IntervalInSeconds: to.Int32Ptr(5), NumberOfProbes: to.Int32Ptr(2), }, } } expectedRules[i] = network.LoadBalancingRule{ Name: &lbRuleName, Properties: &network.LoadBalancingRulePropertiesFormat{ Protocol: transportProto, FrontendIPConfiguration: &network.SubResource{ ID: to.StringPtr(lbFrontendIPConfigID), }, BackendAddressPool: &network.SubResource{ ID: to.StringPtr(lbBackendPoolID), }, Probe: &network.SubResource{ ID: to.StringPtr(az.getLoadBalancerProbeID(lbName, lbRuleName)), }, FrontendPort: to.Int32Ptr(port.Port), BackendPort: to.Int32Ptr(port.Port), EnableFloatingIP: to.BoolPtr(true), }, } } // remove unwanted probes dirtyProbes := false var updatedProbes []network.Probe if lb.Properties.Probes != nil { updatedProbes = *lb.Properties.Probes } for i := len(updatedProbes) - 1; i >= 0; i-- { existingProbe := updatedProbes[i] if serviceOwnsRule(service, *existingProbe.Name) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - considering evicting", serviceName, wantLb, *existingProbe.Name) keepProbe := false if findProbe(expectedProbes, existingProbe) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - keeping", serviceName, wantLb, *existingProbe.Name) keepProbe = true } if !keepProbe { updatedProbes = append(updatedProbes[:i], updatedProbes[i+1:]...) glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - dropping", serviceName, wantLb, *existingProbe.Name) dirtyProbes = true } } } // add missing, wanted probes for _, expectedProbe := range expectedProbes { foundProbe := false if findProbe(updatedProbes, expectedProbe) { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - already exists", serviceName, wantLb, *expectedProbe.Name) foundProbe = true } if !foundProbe { glog.V(10).Infof("reconcile(%s)(%t): lb probe(%s) - adding", serviceName, wantLb, *expectedProbe.Name) updatedProbes = append(updatedProbes, expectedProbe) dirtyProbes = true } } if dirtyProbes { dirtyLb = true lb.Properties.Probes = &updatedProbes } // update rules dirtyRules := false var updatedRules []network.LoadBalancingRule if lb.Properties.LoadBalancingRules != nil { updatedRules = *lb.Properties.LoadBalancingRules } // update rules: remove unwanted for i := len(updatedRules) - 1; i >= 0; i-- { existingRule := updatedRules[i] if serviceOwnsRule(service, *existingRule.Name) { keepRule := false glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - considering evicting", serviceName, wantLb, *existingRule.Name) if findRule(expectedRules, existingRule) { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - keeping", serviceName, wantLb, *existingRule.Name) keepRule = true } if !keepRule { glog.V(3).Infof("reconcile(%s)(%t): lb rule(%s) - dropping", serviceName, wantLb, *existingRule.Name) updatedRules = append(updatedRules[:i], updatedRules[i+1:]...) dirtyRules = true } } } // update rules: add needed for _, expectedRule := range expectedRules { foundRule := false if findRule(updatedRules, expectedRule) { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) - already exists", serviceName, wantLb, *expectedRule.Name) foundRule = true } if !foundRule { glog.V(10).Infof("reconcile(%s)(%t): lb rule(%s) adding", serviceName, wantLb, *expectedRule.Name) updatedRules = append(updatedRules, expectedRule) dirtyRules = true } } if dirtyRules { dirtyLb = true lb.Properties.LoadBalancingRules = &updatedRules } return lb, dirtyLb, nil }
func (a AzureClient) CreateVirtualMachine(resourceGroup, name, location, size, availabilitySetID, networkInterfaceID, username, sshPublicKey, imageName string, storageAccount *storage.AccountProperties) error { log.Info("Creating virtual machine.", logutil.Fields{ "name": name, "location": location, "size": size, "username": username, "osImage": imageName, }) img, err := parseImageName(imageName) if err != nil { return err } var ( osDiskBlobURL = osDiskStorageBlobURL(storageAccount, name) sshKeyPath = fmt.Sprintf("/home/%s/.ssh/authorized_keys", username) ) log.Debugf("OS disk blob will be placed at: %s", osDiskBlobURL) log.Debugf("SSH key will be placed at: %s", sshKeyPath) _, err = a.virtualMachinesClient().CreateOrUpdate(resourceGroup, name, compute.VirtualMachine{ Location: to.StringPtr(location), Properties: &compute.VirtualMachineProperties{ AvailabilitySet: &compute.SubResource{ ID: to.StringPtr(availabilitySetID), }, HardwareProfile: &compute.HardwareProfile{ VMSize: compute.VirtualMachineSizeTypes(size), }, NetworkProfile: &compute.NetworkProfile{ NetworkInterfaces: &[]compute.NetworkInterfaceReference{ { ID: to.StringPtr(networkInterfaceID), }, }, }, OsProfile: &compute.OSProfile{ ComputerName: to.StringPtr(name), AdminUsername: to.StringPtr(username), LinuxConfiguration: &compute.LinuxConfiguration{ DisablePasswordAuthentication: to.BoolPtr(true), SSH: &compute.SSHConfiguration{ PublicKeys: &[]compute.SSHPublicKey{ { Path: to.StringPtr(sshKeyPath), KeyData: to.StringPtr(sshPublicKey), }, }, }, }, }, StorageProfile: &compute.StorageProfile{ ImageReference: &compute.ImageReference{ Publisher: to.StringPtr(img.publisher), Offer: to.StringPtr(img.offer), Sku: to.StringPtr(img.sku), Version: to.StringPtr(img.version), }, OsDisk: &compute.OSDisk{ Name: to.StringPtr(fmt.Sprintf(fmtOSDiskResourceName, name)), Caching: compute.ReadWrite, CreateOption: compute.FromImage, Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(osDiskBlobURL), }, }, }, }, }, nil) return err }
func (s *environSuite) assertStartInstanceRequests( c *gc.C, requests []*http.Request, args assertStartInstanceRequestsParams, ) startInstanceRequests { nsgId := `[resourceId('Microsoft.Network/networkSecurityGroups', 'juju-internal-nsg')]` securityRules := []network.SecurityRule{{ Name: to.StringPtr("SSHInbound"), Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("Allow SSH access to all machines"), Protocol: network.TCP, SourceAddressPrefix: to.StringPtr("*"), SourcePortRange: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr("*"), DestinationPortRange: to.StringPtr("22"), Access: network.Allow, Priority: to.Int32Ptr(100), Direction: network.Inbound, }, }, { Name: to.StringPtr("JujuAPIInbound"), Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("Allow API connections to controller machines"), Protocol: network.TCP, SourceAddressPrefix: to.StringPtr("*"), SourcePortRange: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr("192.168.16.0/20"), DestinationPortRange: to.StringPtr("17777"), Access: network.Allow, Priority: to.Int32Ptr(101), Direction: network.Inbound, }, }} subnets := []network.Subnet{{ Name: to.StringPtr("juju-internal-subnet"), Properties: &network.SubnetPropertiesFormat{ AddressPrefix: to.StringPtr("192.168.0.0/20"), NetworkSecurityGroup: &network.SecurityGroup{ ID: to.StringPtr(nsgId), }, }, }, { Name: to.StringPtr("juju-controller-subnet"), Properties: &network.SubnetPropertiesFormat{ AddressPrefix: to.StringPtr("192.168.16.0/20"), NetworkSecurityGroup: &network.SecurityGroup{ ID: to.StringPtr(nsgId), }, }, }} subnetName := "juju-internal-subnet" privateIPAddress := "192.168.0.4" if args.availabilitySetName == "juju-controller" { subnetName = "juju-controller-subnet" privateIPAddress = "192.168.16.4" } subnetId := fmt.Sprintf( `[concat(resourceId('Microsoft.Network/virtualNetworks', 'juju-internal-network'), '/subnets/%s')]`, subnetName, ) publicIPAddressId := `[resourceId('Microsoft.Network/publicIPAddresses', 'machine-0-public-ip')]` ipConfigurations := []network.InterfaceIPConfiguration{{ Name: to.StringPtr("primary"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ Primary: to.BoolPtr(true), PrivateIPAddress: to.StringPtr(privateIPAddress), PrivateIPAllocationMethod: network.Static, Subnet: &network.Subnet{ID: to.StringPtr(subnetId)}, PublicIPAddress: &network.PublicIPAddress{ ID: to.StringPtr(publicIPAddressId), }, }, }} nicId := `[resourceId('Microsoft.Network/networkInterfaces', 'machine-0-primary')]` nics := []compute.NetworkInterfaceReference{{ ID: to.StringPtr(nicId), Properties: &compute.NetworkInterfaceReferenceProperties{ Primary: to.BoolPtr(true), }, }} vmDependsOn := []string{ nicId, `[resourceId('Microsoft.Storage/storageAccounts', '` + storageAccountName + `')]`, } addressPrefixes := []string{"192.168.0.0/20", "192.168.16.0/20"} templateResources := []armtemplates.Resource{{ APIVersion: network.APIVersion, Type: "Microsoft.Network/networkSecurityGroups", Name: "juju-internal-nsg", Location: "westus", Tags: to.StringMap(s.envTags), Properties: &network.SecurityGroupPropertiesFormat{ SecurityRules: &securityRules, }, }, { APIVersion: network.APIVersion, Type: "Microsoft.Network/virtualNetworks", Name: "juju-internal-network", Location: "westus", Tags: to.StringMap(s.envTags), Properties: &network.VirtualNetworkPropertiesFormat{ AddressSpace: &network.AddressSpace{&addressPrefixes}, Subnets: &subnets, }, DependsOn: []string{nsgId}, }, { APIVersion: storage.APIVersion, Type: "Microsoft.Storage/storageAccounts", Name: storageAccountName, Location: "westus", Tags: to.StringMap(s.envTags), StorageSku: &storage.Sku{ Name: storage.SkuName("Standard_LRS"), }, }} var availabilitySetSubResource *compute.SubResource if args.availabilitySetName != "" { availabilitySetId := fmt.Sprintf( `[resourceId('Microsoft.Compute/availabilitySets','%s')]`, args.availabilitySetName, ) templateResources = append(templateResources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/availabilitySets", Name: args.availabilitySetName, Location: "westus", Tags: to.StringMap(s.envTags), }) availabilitySetSubResource = &compute.SubResource{ ID: to.StringPtr(availabilitySetId), } vmDependsOn = append([]string{availabilitySetId}, vmDependsOn...) } templateResources = append(templateResources, []armtemplates.Resource{{ APIVersion: network.APIVersion, Type: "Microsoft.Network/publicIPAddresses", Name: "machine-0-public-ip", Location: "westus", Tags: to.StringMap(s.vmTags), Properties: &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Dynamic, }, }, { APIVersion: network.APIVersion, Type: "Microsoft.Network/networkInterfaces", Name: "machine-0-primary", Location: "westus", Tags: to.StringMap(s.vmTags), Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &ipConfigurations, }, DependsOn: []string{ publicIPAddressId, `[resourceId('Microsoft.Network/virtualNetworks', 'juju-internal-network')]`, }, }, { APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines", Name: "machine-0", Location: "westus", Tags: to.StringMap(s.vmTags), Properties: &compute.VirtualMachineProperties{ HardwareProfile: &compute.HardwareProfile{ VMSize: "Standard_D1", }, StorageProfile: &compute.StorageProfile{ ImageReference: args.imageReference, OsDisk: &compute.OSDisk{ Name: to.StringPtr("machine-0"), CreateOption: compute.FromImage, Caching: compute.ReadWrite, Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(fmt.Sprintf( `[concat(reference(resourceId('Microsoft.Storage/storageAccounts', '%s'), '%s').primaryEndpoints.blob, 'osvhds/machine-0.vhd')]`, storageAccountName, storage.APIVersion, )), }, DiskSizeGB: to.Int32Ptr(int32(args.diskSizeGB)), }, }, OsProfile: args.osProfile, NetworkProfile: &compute.NetworkProfile{&nics}, AvailabilitySet: availabilitySetSubResource, }, DependsOn: vmDependsOn, }}...) if args.vmExtension != nil { templateResources = append(templateResources, armtemplates.Resource{ APIVersion: compute.APIVersion, Type: "Microsoft.Compute/virtualMachines/extensions", Name: "machine-0/JujuCustomScriptExtension", Location: "westus", Tags: to.StringMap(s.vmTags), Properties: args.vmExtension, DependsOn: []string{"Microsoft.Compute/virtualMachines/machine-0"}, }) } templateMap := map[string]interface{}{ "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "resources": templateResources, } deployment := &resources.Deployment{ &resources.DeploymentProperties{ Template: &templateMap, Mode: resources.Incremental, }, } // Validate HTTP request bodies. var startInstanceRequests startInstanceRequests if args.vmExtension != nil { // It must be Windows or CentOS, so // there should be no image query. c.Assert(requests, gc.HasLen, numExpectedStartInstanceRequests-1) c.Assert(requests[0].Method, gc.Equals, "GET") // vmSizes c.Assert(requests[1].Method, gc.Equals, "PUT") // create deployment startInstanceRequests.vmSizes = requests[0] startInstanceRequests.deployment = requests[1] } else { c.Assert(requests, gc.HasLen, numExpectedStartInstanceRequests) c.Assert(requests[0].Method, gc.Equals, "GET") // vmSizes c.Assert(requests[1].Method, gc.Equals, "GET") // skus c.Assert(requests[2].Method, gc.Equals, "PUT") // create deployment startInstanceRequests.vmSizes = requests[0] startInstanceRequests.skus = requests[1] startInstanceRequests.deployment = requests[2] } // Marshal/unmarshal the deployment we expect, so it's in map form. var expected resources.Deployment data, err := json.Marshal(&deployment) c.Assert(err, jc.ErrorIsNil) err = json.Unmarshal(data, &expected) c.Assert(err, jc.ErrorIsNil) // Check that we send what we expect. CustomData is non-deterministic, // so don't compare it. // TODO(axw) shouldn't CustomData be deterministic? Look into this. var actual resources.Deployment unmarshalRequestBody(c, startInstanceRequests.deployment, &actual) c.Assert(actual.Properties, gc.NotNil) c.Assert(actual.Properties.Template, gc.NotNil) resources := (*actual.Properties.Template)["resources"].([]interface{}) c.Assert(resources, gc.HasLen, len(templateResources)) vmResourceIndex := len(resources) - 1 if args.vmExtension != nil { vmResourceIndex-- } vmResource := resources[vmResourceIndex].(map[string]interface{}) vmResourceProperties := vmResource["properties"].(map[string]interface{}) osProfile := vmResourceProperties["osProfile"].(map[string]interface{}) osProfile["customData"] = "<juju-goes-here>" c.Assert(actual, jc.DeepEquals, expected) return startInstanceRequests }
Publisher: to.StringPtr("OpenLogic"), Offer: to.StringPtr("CentOS"), Sku: to.StringPtr("7.1"), Version: to.StringPtr("latest"), } sshPublicKeys = []compute.SSHPublicKey{{ Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), KeyData: to.StringPtr(testing.FakeAuthKeys), }} linuxOsProfile = compute.OSProfile{ ComputerName: to.StringPtr("machine-0"), CustomData: to.StringPtr("<juju-goes-here>"), AdminUsername: to.StringPtr("ubuntu"), LinuxConfiguration: &compute.LinuxConfiguration{ DisablePasswordAuthentication: to.BoolPtr(true), SSH: &compute.SSHConfiguration{ PublicKeys: &sshPublicKeys, }, }, } windowsOsProfile = compute.OSProfile{ ComputerName: to.StringPtr("machine-0"), CustomData: to.StringPtr("<juju-goes-here>"), AdminUsername: to.StringPtr("JujuAdministrator"), AdminPassword: to.StringPtr("sorandom"), WindowsConfiguration: &compute.WindowsConfiguration{ ProvisionVMAgent: to.BoolPtr(true), EnableAutomaticUpdates: to.BoolPtr(true), }, }
func (s *instanceSuite) TestInstanceOpenPortsAlreadyOpen(c *gc.C) { internalSubnetId := path.Join( "/subscriptions", fakeSubscriptionId, "resourceGroups/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d", "providers/Microsoft.Network/virtualnetworks/juju-internal-network/subnets/juju-internal-subnet", ) ipConfiguration := network.InterfaceIPConfiguration{ Properties: &network.InterfaceIPConfigurationPropertiesFormat{ Primary: to.BoolPtr(true), PrivateIPAddress: to.StringPtr("10.0.0.4"), Subnet: &network.Subnet{ ID: to.StringPtr(internalSubnetId), }, }, } s.networkInterfaces = []network.Interface{ makeNetworkInterface("nic-0", "machine-0", ipConfiguration), } inst := s.getInstance(c) okSender := mocks.NewSender() okSender.AppendResponse(mocks.NewResponseWithContent("{}")) nsgSender := networkSecurityGroupSender([]network.SecurityRule{{ Name: to.StringPtr("machine-0-tcp-1000"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.Asterisk, DestinationPortRange: to.StringPtr("1000"), Access: network.Allow, Priority: to.Int32Ptr(202), Direction: network.Inbound, }, }}) s.sender = azuretesting.Senders{nsgSender, okSender, okSender} err := inst.OpenPorts("0", []jujunetwork.PortRange{{ Protocol: "tcp", FromPort: 1000, ToPort: 1000, }, { Protocol: "udp", FromPort: 1000, ToPort: 2000, }}) c.Assert(err, jc.ErrorIsNil) c.Assert(s.requests, gc.HasLen, 2) c.Assert(s.requests[0].Method, gc.Equals, "GET") c.Assert(s.requests[0].URL.Path, gc.Equals, internalSecurityGroupPath) c.Assert(s.requests[1].Method, gc.Equals, "PUT") c.Assert(s.requests[1].URL.Path, gc.Equals, securityRulePath("machine-0-udp-1000-2000")) assertRequestBody(c, s.requests[1], &network.SecurityRule{ Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("1000-2000/udp"), Protocol: network.UDP, SourcePortRange: to.StringPtr("*"), SourceAddressPrefix: to.StringPtr("*"), DestinationPortRange: to.StringPtr("1000-2000"), DestinationAddressPrefix: to.StringPtr("10.0.0.4"), Access: network.Allow, Priority: to.Int32Ptr(200), Direction: network.Inbound, }, }) }
// 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 }