// createVolume updates the provided VirtualMachine's StorageProfile with the // parameters for creating a new data disk. We don't actually interact with // the Azure API until after all changes to the VirtualMachine are made. func (v *azureVolumeSource) createVolume( vm *compute.VirtualMachine, p storage.VolumeParams, ) (*storage.Volume, *storage.VolumeAttachment, error) { lun, err := nextAvailableLUN(vm) if err != nil { return nil, nil, errors.Annotate(err, "choosing LUN") } dataDisksRoot := dataDiskVhdRoot(v.env.config.location, v.env.config.storageAccount) dataDiskName := p.Tag.String() vhdURI := dataDisksRoot + dataDiskName + vhdExtension sizeInGib := mibToGib(p.Size) dataDisk := compute.DataDisk{ Lun: to.IntPtr(lun), DiskSizeGB: to.IntPtr(int(sizeInGib)), Name: to.StringPtr(dataDiskName), Vhd: &compute.VirtualHardDisk{to.StringPtr(vhdURI)}, Caching: compute.ReadWrite, CreateOption: compute.Empty, } var dataDisks []compute.DataDisk if vm.Properties.StorageProfile.DataDisks != nil { dataDisks = *vm.Properties.StorageProfile.DataDisks } dataDisks = append(dataDisks, dataDisk) vm.Properties.StorageProfile.DataDisks = &dataDisks // Data disks associate VHDs to machines. In Juju's storage model, // the VHD is the volume and the disk is the volume attachment. volume := storage.Volume{ p.Tag, storage.VolumeInfo{ VolumeId: dataDiskName, Size: gibToMib(sizeInGib), // We don't currently support persistent volumes in // Azure, as it requires removal of "comp=media" when // deleting VMs, complicating cleanup. Persistent: true, }, } volumeAttachment := storage.VolumeAttachment{ p.Tag, p.Attachment.Machine, storage.VolumeAttachmentInfo{ BusAddress: diskBusAddress(lun), }, } return &volume, &volumeAttachment, nil }
func (v *azureVolumeSource) attachVolume( vm *compute.VirtualMachine, p storage.VolumeAttachmentParams, ) (_ *storage.VolumeAttachment, updated bool, _ error) { dataDisksRoot := dataDiskVhdRoot(v.env.config.location, v.env.config.storageAccount) dataDiskName := p.VolumeId vhdURI := dataDisksRoot + dataDiskName + vhdExtension var dataDisks []compute.DataDisk if vm.Properties.StorageProfile.DataDisks != nil { dataDisks = *vm.Properties.StorageProfile.DataDisks } for _, disk := range dataDisks { if to.String(disk.Name) != p.VolumeId { continue } if to.String(disk.Vhd.URI) != vhdURI { continue } // Disk is already attached. volumeAttachment := &storage.VolumeAttachment{ p.Volume, p.Machine, storage.VolumeAttachmentInfo{ BusAddress: diskBusAddress(to.Int(disk.Lun)), }, } return volumeAttachment, false, nil } lun, err := nextAvailableLUN(vm) if err != nil { return nil, false, errors.Annotate(err, "choosing LUN") } dataDisk := compute.DataDisk{ Lun: to.IntPtr(lun), Name: to.StringPtr(dataDiskName), Vhd: &compute.VirtualHardDisk{to.StringPtr(vhdURI)}, Caching: compute.ReadWrite, CreateOption: compute.Attach, } dataDisks = append(dataDisks, dataDisk) vm.Properties.StorageProfile.DataDisks = &dataDisks volumeAttachment := storage.VolumeAttachment{ p.Volume, p.Machine, storage.VolumeAttachmentInfo{ BusAddress: diskBusAddress(lun), }, } return &volumeAttachment, true, nil }
func makeSecurityRule(name, ipAddress, ports string) network.SecurityRule { return network.SecurityRule{ Name: to.StringPtr(name), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationAddressPrefix: to.StringPtr(ipAddress), DestinationPortRange: to.StringPtr(ports), Access: network.Allow, Priority: to.IntPtr(200), Direction: network.Inbound, }, } }
func (s *storageSuite) TestCreateVolumes(c *gc.C) { // machine-1 has a single data disk with LUN 0. machine1DataDisks := []compute.DataDisk{{Lun: to.IntPtr(0)}} // machine-2 has 32 data disks; no LUNs free. machine2DataDisks := make([]compute.DataDisk, 32) for i := range machine2DataDisks { machine2DataDisks[i].Lun = to.IntPtr(i) } // volume-0 and volume-2 are attached to machine-0 // volume-1 is attached to machine-1 // volume-3 is attached to machine-42, but machine-42 is missing // volume-42 is attached to machine-2, but machine-2 has no free LUNs makeVolumeParams := func(volume, machine string, size uint64) storage.VolumeParams { return storage.VolumeParams{ Tag: names.NewVolumeTag(volume), Size: size, Provider: "azure", Attachment: &storage.VolumeAttachmentParams{ AttachmentParams: storage.AttachmentParams{ Provider: "azure", Machine: names.NewMachineTag(machine), InstanceId: instance.Id("machine-" + machine), }, Volume: names.NewVolumeTag(volume), }, } } params := []storage.VolumeParams{ makeVolumeParams("0", "0", 1), makeVolumeParams("1", "1", 1025), makeVolumeParams("2", "0", 1024), makeVolumeParams("3", "42", 40), makeVolumeParams("42", "2", 50), } virtualMachines := []compute.VirtualMachine{{ Name: to.StringPtr("machine-0"), Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{}, }, }, { Name: to.StringPtr("machine-1"), Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{DataDisks: &machine1DataDisks}, }, }, { Name: to.StringPtr("machine-2"), Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{DataDisks: &machine2DataDisks}, }, }} // There should be a couple of API calls to list instances, // and one update per modified instance. nics := []network.Interface{ makeNetworkInterface("nic-0", "machine-0"), makeNetworkInterface("nic-1", "machine-1"), makeNetworkInterface("nic-2", "machine-2"), } nicsSender := azuretesting.NewSenderWithValue(network.InterfaceListResult{ Value: &nics, }) nicsSender.PathPattern = `.*/Microsoft\.Network/networkInterfaces` virtualMachinesSender := azuretesting.NewSenderWithValue(compute.VirtualMachineListResult{ Value: &virtualMachines, }) virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` updateVirtualMachine1Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) updateVirtualMachine1Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-1` volumeSource := s.volumeSource(c) s.sender = azuretesting.Senders{ nicsSender, virtualMachinesSender, updateVirtualMachine0Sender, updateVirtualMachine1Sender, } results, err := volumeSource.CreateVolumes(params) c.Assert(err, jc.ErrorIsNil) c.Assert(results, gc.HasLen, len(params)) c.Check(results[0].Error, jc.ErrorIsNil) c.Check(results[1].Error, jc.ErrorIsNil) c.Check(results[2].Error, jc.ErrorIsNil) c.Check(results[3].Error, gc.ErrorMatches, "instance machine-42 not found") c.Check(results[4].Error, gc.ErrorMatches, "choosing LUN: all LUNs are in use") // Validate HTTP request bodies. c.Assert(s.requests, gc.HasLen, 4) c.Assert(s.requests[0].Method, gc.Equals, "GET") // list NICs c.Assert(s.requests[1].Method, gc.Equals, "GET") // list virtual machines c.Assert(s.requests[2].Method, gc.Equals, "PUT") // update machine-0 c.Assert(s.requests[3].Method, gc.Equals, "PUT") // update machine-1 machine0DataDisks := []compute.DataDisk{{ Lun: to.IntPtr(0), DiskSizeGB: to.IntPtr(1), Name: to.StringPtr("volume-0"), Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-0.vhd", fakeStorageAccount, ))}, Caching: compute.ReadWrite, CreateOption: compute.Empty, }, { Lun: to.IntPtr(1), DiskSizeGB: to.IntPtr(1), Name: to.StringPtr("volume-2"), Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-2.vhd", fakeStorageAccount, ))}, Caching: compute.ReadWrite, CreateOption: compute.Empty, }} virtualMachines[0].Properties.StorageProfile.DataDisks = &machine0DataDisks assertRequestBody(c, s.requests[2], &virtualMachines[0]) machine1DataDisks = append(machine1DataDisks, compute.DataDisk{ Lun: to.IntPtr(1), DiskSizeGB: to.IntPtr(2), Name: to.StringPtr("volume-1"), Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-1.vhd", fakeStorageAccount, ))}, Caching: compute.ReadWrite, CreateOption: compute.Empty, }) assertRequestBody(c, s.requests[3], &virtualMachines[1]) }
func (s *storageSuite) TestDetachVolumes(c *gc.C) { // machine-0 has a three data disks: volume-0, volume-1 and volume-2 machine0DataDisks := []compute.DataDisk{{ Lun: to.IntPtr(0), Name: to.StringPtr("volume-0"), Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-0.vhd", fakeStorageAccount, )), }, }, { Lun: to.IntPtr(1), Name: to.StringPtr("volume-1"), Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-1.vhd", fakeStorageAccount, )), }, }, { Lun: to.IntPtr(2), Name: to.StringPtr("volume-2"), Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.core.windows.net/datavhds/volume-2.vhd", fakeStorageAccount, )), }, }} makeParams := func(volume, machine string) storage.VolumeAttachmentParams { return storage.VolumeAttachmentParams{ AttachmentParams: storage.AttachmentParams{ Provider: "azure", Machine: names.NewMachineTag(machine), InstanceId: instance.Id("machine-" + machine), }, Volume: names.NewVolumeTag(volume), VolumeId: "volume-" + volume, } } params := []storage.VolumeAttachmentParams{ makeParams("1", "0"), makeParams("1", "0"), makeParams("42", "1"), makeParams("2", "42"), } virtualMachines := []compute.VirtualMachine{{ Name: to.StringPtr("machine-0"), Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{DataDisks: &machine0DataDisks}, }, }, { Name: to.StringPtr("machine-1"), Properties: &compute.VirtualMachineProperties{ StorageProfile: &compute.StorageProfile{}, }, }} // There should be a couple of API calls to list instances, // and one update per modified instance. nics := []network.Interface{ makeNetworkInterface("nic-0", "machine-0"), makeNetworkInterface("nic-1", "machine-1"), } nicsSender := azuretesting.NewSenderWithValue(network.InterfaceListResult{ Value: &nics, }) nicsSender.PathPattern = `.*/Microsoft\.Network/networkInterfaces` virtualMachinesSender := azuretesting.NewSenderWithValue(compute.VirtualMachineListResult{ Value: &virtualMachines, }) virtualMachinesSender.PathPattern = `.*/Microsoft\.Compute/virtualMachines` updateVirtualMachine0Sender := azuretesting.NewSenderWithValue(&compute.VirtualMachine{}) updateVirtualMachine0Sender.PathPattern = `.*/Microsoft\.Compute/virtualMachines/machine-0` volumeSource := s.volumeSource(c) s.sender = azuretesting.Senders{ nicsSender, virtualMachinesSender, updateVirtualMachine0Sender, } results, err := volumeSource.DetachVolumes(params) c.Assert(err, jc.ErrorIsNil) c.Assert(results, gc.HasLen, len(params)) c.Check(results[0], jc.ErrorIsNil) c.Check(results[1], jc.ErrorIsNil) c.Check(results[2], jc.ErrorIsNil) c.Check(results[3], gc.ErrorMatches, "instance machine-42 not found") // Validate HTTP request bodies. c.Assert(s.requests, gc.HasLen, 3) c.Assert(s.requests[0].Method, gc.Equals, "GET") // list NICs c.Assert(s.requests[1].Method, gc.Equals, "GET") // list virtual machines c.Assert(s.requests[2].Method, gc.Equals, "PUT") // update machine-0 machine0DataDisks = []compute.DataDisk{ machine0DataDisks[0], machine0DataDisks[2], } virtualMachines[0].Properties.StorageProfile.DataDisks = &machine0DataDisks assertRequestBody(c, s.requests[2], &virtualMachines[0]) }
func (s *instanceSuite) TestInstanceOpenPortsAlreadyOpen(c *gc.C) { internalSubnetId := path.Join( "/subscriptions", fakeSubscriptionId, "resourceGroups/arbitrary/providers/Microsoft.Network/virtualnetworks/juju-internal/subnets", "juju-testenv-environment-"+testing.EnvironmentTag.Id(), ) ipConfiguration := network.InterfaceIPConfiguration{ Properties: &network.InterfaceIPConfigurationPropertiesFormat{ PrivateIPAddress: to.StringPtr("10.0.0.4"), Subnet: &network.SubResource{ ID: to.StringPtr(internalSubnetId), }, }, } s.networkInterfaces = []network.Interface{ makeNetworkInterface("nic-0", "machine-0", ipConfiguration), } inst := s.getInstance(c) okSender := mocks.NewSender() okSender.EmitContent("{}") nsgSender := networkSecurityGroupSender([]network.SecurityRule{{ Name: to.StringPtr("machine-0-tcp-1000"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolAsterisk, DestinationPortRange: to.StringPtr("1000"), Access: network.Allow, Priority: to.IntPtr(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.SecurityRuleProtocolUDP, SourcePortRange: to.StringPtr("*"), SourceAddressPrefix: to.StringPtr("*"), DestinationPortRange: to.StringPtr("1000-2000"), DestinationAddressPrefix: to.StringPtr("10.0.0.4"), Access: network.Allow, Priority: to.IntPtr(200), Direction: network.Inbound, }, }) }
func (s *instanceSuite) TestInstancePorts(c *gc.C) { inst := s.getInstance(c) nsgSender := networkSecurityGroupSender([]network.SecurityRule{{ Name: to.StringPtr("machine-0-xyzzy"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolUDP, DestinationPortRange: to.StringPtr("*"), Access: network.Allow, Priority: to.IntPtr(200), Direction: network.Inbound, }, }, { Name: to.StringPtr("machine-0-tcpcp"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationPortRange: to.StringPtr("1000-2000"), Access: network.Allow, Priority: to.IntPtr(201), Direction: network.Inbound, }, }, { Name: to.StringPtr("machine-0-http"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolAsterisk, DestinationPortRange: to.StringPtr("80"), Access: network.Allow, Priority: to.IntPtr(202), Direction: network.Inbound, }, }, { Name: to.StringPtr("machine-00-ignored"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationPortRange: to.StringPtr("80"), Access: network.Allow, Priority: to.IntPtr(202), Direction: network.Inbound, }, }, { Name: to.StringPtr("machine-0-ignored"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationPortRange: to.StringPtr("80"), Access: network.Deny, Priority: to.IntPtr(202), Direction: network.Inbound, }, }, { Name: to.StringPtr("machine-0-ignored"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationPortRange: to.StringPtr("80"), Access: network.Allow, Priority: to.IntPtr(202), Direction: network.Outbound, }, }, { Name: to.StringPtr("machine-0-ignored"), Properties: &network.SecurityRulePropertiesFormat{ Protocol: network.SecurityRuleProtocolTCP, DestinationPortRange: to.StringPtr("80"), Access: network.Allow, Priority: to.IntPtr(199), // internal range Direction: network.Inbound, }, }}) s.sender = azuretesting.Senders{nsgSender} ports, err := inst.Ports("0") c.Assert(err, jc.ErrorIsNil) c.Assert(ports, jc.DeepEquals, []jujunetwork.PortRange{{ FromPort: 0, ToPort: 65535, Protocol: "udp", }, { FromPort: 1000, ToPort: 2000, Protocol: "tcp", }, { FromPort: 80, ToPort: 80, Protocol: "tcp", }, { FromPort: 80, ToPort: 80, Protocol: "udp", }}) }
func (s *environSuite) SetUpTest(c *gc.C) { s.BaseSuite.SetUpTest(c) s.storageClient = azuretesting.MockStorageClient{} s.sender = nil s.provider, _ = newProviders(c, azure.ProviderConfig{ Sender: &s.sender, RequestInspector: requestRecorder(&s.requests), NewStorageClient: s.storageClient.NewClient, }) emptyTags := make(map[string]*string) s.tags = map[string]*string{ "juju-machine-name": to.StringPtr("machine-0"), } vmSizes := []compute.VirtualMachineSize{{ Name: to.StringPtr("Standard_D1"), NumberOfCores: to.IntPtr(1), OsDiskSizeInMB: to.IntPtr(1047552), ResourceDiskSizeInMB: to.IntPtr(51200), MemoryInMB: to.IntPtr(3584), MaxDataDiskCount: to.IntPtr(2), }} s.vmSizes = &compute.VirtualMachineSizeListResult{Value: &vmSizes} s.storageNameAvailabilityResult = &storage.CheckNameAvailabilityResult{ NameAvailable: to.BoolPtr(true), } s.storageAccount = &storage.Account{ Name: to.StringPtr("my-storage-account"), Type: to.StringPtr("Standard_LRS"), Properties: &storage.AccountProperties{ PrimaryEndpoints: &storage.Endpoints{ Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", fakeStorageAccount)), }, }, } s.storageAccountKeys = &storage.AccountKeys{ Key1: to.StringPtr("key-1"), } addressPrefixes := make([]string, 256) for i := range addressPrefixes { addressPrefixes[i] = fmt.Sprintf("10.%d.0.0/16", i) } s.vnet = &network.VirtualNetwork{ ID: to.StringPtr("juju-internal"), Name: to.StringPtr("juju-internal"), Location: to.StringPtr("westus"), Tags: &emptyTags, Properties: &network.VirtualNetworkPropertiesFormat{ AddressSpace: &network.AddressSpace{&addressPrefixes}, }, } s.subnet = &network.Subnet{ ID: to.StringPtr("subnet-id"), Name: to.StringPtr("juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d"), Properties: &network.SubnetPropertiesFormat{ AddressPrefix: to.StringPtr("10.0.0.0/16"), }, } s.ubuntuServerSKUs = []compute.VirtualMachineImageResource{ {Name: to.StringPtr("12.04-LTS")}, {Name: to.StringPtr("12.10")}, {Name: to.StringPtr("14.04-LTS")}, {Name: to.StringPtr("15.04")}, {Name: to.StringPtr("15.10")}, } s.publicIPAddress = &network.PublicIPAddress{ ID: to.StringPtr("public-ip-id"), Name: to.StringPtr("machine-0-public-ip"), Location: to.StringPtr("westus"), Tags: &s.tags, Properties: &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Dynamic, IPAddress: to.StringPtr("1.2.3.4"), }, } // Existing IPs/NICs. These are the results of querying NICs so we // can tell which IP to allocate. oldIPConfigurations := []network.InterfaceIPConfiguration{{ ID: to.StringPtr("ip-configuration-0-id"), Name: to.StringPtr("ip-configuration-0"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ PrivateIPAddress: to.StringPtr("10.0.0.4"), PrivateIPAllocationMethod: network.Static, Subnet: &network.SubResource{ID: s.subnet.ID}, }, }} oldNetworkInterfaces := []network.Interface{{ ID: to.StringPtr("network-interface-0-id"), Name: to.StringPtr("network-interface-0"), Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &oldIPConfigurations, Primary: to.BoolPtr(true), }, }} s.oldNetworkInterfaces = &network.InterfaceListResult{ Value: &oldNetworkInterfaces, } // nsgID is the name of the internal network security group. This NSG // is created when the environment is created. nsgID := path.Join( "/subscriptions", fakeSubscriptionId, "resourceGroups", "juju-testenv-model-"+testing.ModelTag.Id(), "providers/Microsoft.Network/networkSecurityGroups/juju-internal", ) // The newly created IP/NIC. newIPConfigurations := []network.InterfaceIPConfiguration{{ ID: to.StringPtr("ip-configuration-1-id"), Name: to.StringPtr("primary"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ PrivateIPAddress: to.StringPtr("10.0.0.5"), PrivateIPAllocationMethod: network.Static, Subnet: &network.SubResource{ID: s.subnet.ID}, PublicIPAddress: &network.SubResource{ID: s.publicIPAddress.ID}, }, }} s.newNetworkInterface = &network.Interface{ ID: to.StringPtr("network-interface-1-id"), Name: to.StringPtr("network-interface-1"), Location: to.StringPtr("westus"), Tags: &s.tags, Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &newIPConfigurations, NetworkSecurityGroup: &network.SubResource{to.StringPtr(nsgID)}, }, } s.jujuAvailabilitySet = &compute.AvailabilitySet{ ID: to.StringPtr("juju-availability-set-id"), Name: to.StringPtr("juju"), Location: to.StringPtr("westus"), Tags: &emptyTags, } sshPublicKeys := []compute.SSHPublicKey{{ Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), KeyData: to.StringPtr(testing.FakeAuthKeys), }} networkInterfaceReferences := []compute.NetworkInterfaceReference{{ ID: s.newNetworkInterface.ID, Properties: &compute.NetworkInterfaceReferenceProperties{ Primary: to.BoolPtr(true), }, }} s.virtualMachine = &compute.VirtualMachine{ ID: to.StringPtr("machine-0-id"), Name: to.StringPtr("machine-0"), Location: to.StringPtr("westus"), Tags: &s.tags, Properties: &compute.VirtualMachineProperties{ HardwareProfile: &compute.HardwareProfile{ VMSize: "Standard_D1", }, StorageProfile: &compute.StorageProfile{ ImageReference: &compute.ImageReference{ Publisher: to.StringPtr("Canonical"), Offer: to.StringPtr("UbuntuServer"), Sku: to.StringPtr("12.10"), Version: to.StringPtr("latest"), }, OsDisk: &compute.OSDisk{ Name: to.StringPtr("machine-0"), CreateOption: compute.FromImage, Caching: compute.ReadWrite, Vhd: &compute.VirtualHardDisk{ URI: to.StringPtr(fmt.Sprintf( "https://%s.blob.storage.azurestack.local/osvhds/machine-0.vhd", fakeStorageAccount, )), }, }, }, OsProfile: &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, }, }, }, NetworkProfile: &compute.NetworkProfile{ NetworkInterfaces: &networkInterfaceReferences, }, AvailabilitySet: &compute.SubResource{ID: s.jujuAvailabilitySet.ID}, ProvisioningState: to.StringPtr("Successful"), }, } }
func (s *environSuite) TestBootstrap(c *gc.C) { defer envtesting.DisableFinishBootstrap()() ctx := envtesting.BootstrapContext(c) env := prepareForBootstrap(c, ctx, s.provider, &s.sender) s.sender = s.initResourceGroupSenders() s.sender = append(s.sender, s.startInstanceSenders(true)...) s.requests = nil result, err := env.Bootstrap( ctx, environs.BootstrapParams{ AvailableTools: makeToolsList("trusty"), }, ) c.Assert(err, jc.ErrorIsNil) c.Assert(result.Arch, gc.Equals, "amd64") c.Assert(result.Series, gc.Equals, "trusty") c.Assert(len(s.requests), gc.Equals, 17) c.Assert(s.requests[0].Method, gc.Equals, "PUT") // resource group c.Assert(s.requests[1].Method, gc.Equals, "PUT") // vnet c.Assert(s.requests[2].Method, gc.Equals, "PUT") // network security group c.Assert(s.requests[3].Method, gc.Equals, "PUT") // subnet c.Assert(s.requests[4].Method, gc.Equals, "POST") // check storage account name c.Assert(s.requests[5].Method, gc.Equals, "PUT") // create storage account c.Assert(s.requests[6].Method, gc.Equals, "POST") // get storage account keys emptyTags := map[string]*string{} assertRequestBody(c, s.requests[0], &resources.Group{ Location: to.StringPtr("westus"), Tags: &emptyTags, }) s.vnet.ID = nil s.vnet.Name = nil assertRequestBody(c, s.requests[1], s.vnet) securityRules := []network.SecurityRule{{ Name: to.StringPtr("SSHInbound"), Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("Allow SSH access to all machines"), Protocol: network.SecurityRuleProtocolTCP, SourceAddressPrefix: to.StringPtr("*"), SourcePortRange: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr("*"), DestinationPortRange: to.StringPtr("22"), Access: network.Allow, Priority: to.IntPtr(100), Direction: network.Inbound, }, }} assertRequestBody(c, s.requests[2], &network.SecurityGroup{ Location: to.StringPtr("westus"), Tags: &emptyTags, Properties: &network.SecurityGroupPropertiesFormat{ SecurityRules: &securityRules, }, }) s.subnet.ID = nil s.subnet.Name = nil assertRequestBody(c, s.requests[3], s.subnet) assertRequestBody(c, s.requests[4], &storage.AccountCheckNameAvailabilityParameters{ Name: to.StringPtr(fakeStorageAccount), Type: to.StringPtr("Microsoft.Storage/storageAccounts"), }) assertRequestBody(c, s.requests[5], &storage.AccountCreateParameters{ Location: to.StringPtr("westus"), Tags: &emptyTags, Properties: &storage.AccountPropertiesCreateParameters{ AccountType: "Standard_LRS", }, }) }
func newNetworkProfile( client network.ManagementClient, vmName string, apiPort *int, internalSubnet *network.Subnet, resourceGroup string, location string, tags map[string]string, ) (*compute.NetworkProfile, error) { logger.Debugf("creating network profile for %q", vmName) // Create a public IP for the NIC. Public IP addresses are dynamic. logger.Debugf("- allocating public IP address") pipClient := network.PublicIPAddressesClient{client} publicIPAddressParams := network.PublicIPAddress{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &network.PublicIPAddressPropertiesFormat{ PublicIPAllocationMethod: network.Dynamic, }, } publicIPAddressName := vmName + "-public-ip" publicIPAddress, err := pipClient.CreateOrUpdate(resourceGroup, publicIPAddressName, publicIPAddressParams) if err != nil { return nil, errors.Annotatef(err, "creating public IP address for %q", vmName) } // Determine the next available private IP address. nicClient := network.InterfacesClient{client} privateIPAddress, err := nextSubnetIPAddress(nicClient, resourceGroup, internalSubnet) if err != nil { return nil, errors.Annotatef(err, "querying private IP addresses") } // Create a primary NIC for the machine. This needs to be static, so // that we can create security rules that don't become invalid. logger.Debugf("- creating primary NIC") ipConfigurations := []network.InterfaceIPConfiguration{{ Name: to.StringPtr("primary"), Properties: &network.InterfaceIPConfigurationPropertiesFormat{ PrivateIPAddress: to.StringPtr(privateIPAddress), PrivateIPAllocationMethod: network.Static, Subnet: &network.SubResource{internalSubnet.ID}, PublicIPAddress: &network.SubResource{publicIPAddress.ID}, }, }} primaryNicName := vmName + "-primary" primaryNicParams := network.Interface{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &network.InterfacePropertiesFormat{ IPConfigurations: &ipConfigurations, }, } primaryNic, err := nicClient.CreateOrUpdate(resourceGroup, primaryNicName, primaryNicParams) if err != nil { return nil, errors.Annotatef(err, "creating network interface for %q", vmName) } // Create a network security rule for the machine if we need to open // the API server port. if apiPort != nil { logger.Debugf("- querying network security group") securityGroupClient := network.SecurityGroupsClient{client} securityGroupName := internalSecurityGroupName securityGroup, err := securityGroupClient.Get(resourceGroup, securityGroupName) if err != nil { return nil, errors.Annotate(err, "querying network security group") } // NOTE(axw) this looks like TOCTTOU race territory, but it's // safe because we only allocate/deallocate rules in this // range during machine (de)provisioning, which is managed by // a single goroutine. Non-internal ports are managed by the // firewaller exclusively. nextPriority, err := nextSecurityRulePriority( securityGroup, securityRuleInternalSSHInbound+1, securityRuleInternalMax, ) if err != nil { return nil, errors.Trace(err) } apiSecurityRuleName := fmt.Sprintf("%s-api", vmName) apiSecurityRule := network.SecurityRule{ Name: to.StringPtr(apiSecurityRuleName), Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("Allow API access to server machines"), Protocol: network.SecurityRuleProtocolTCP, SourceAddressPrefix: to.StringPtr("*"), SourcePortRange: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr(privateIPAddress), DestinationPortRange: to.StringPtr(fmt.Sprint(*apiPort)), Access: network.Allow, Priority: to.IntPtr(nextPriority), Direction: network.Inbound, }, } logger.Debugf("- creating API network security rule") securityRuleClient := network.SecurityRulesClient{client} _, err = securityRuleClient.CreateOrUpdate( resourceGroup, securityGroupName, apiSecurityRuleName, apiSecurityRule, ) if err != nil { return nil, errors.Annotate(err, "creating API network security rule") } } // For now we only attach a single, flat network to each machine. networkInterfaces := []compute.NetworkInterfaceReference{{ ID: primaryNic.ID, Properties: &compute.NetworkInterfaceReferenceProperties{ Primary: to.BoolPtr(true), }, }} return &compute.NetworkProfile{&networkInterfaces}, nil }
// security rule that allows inbound SSH access to all // machines. securityRuleInternalSSHInbound = securityRuleInternalMin + iota ) var sshSecurityRule = network.SecurityRule{ Name: to.StringPtr("SSHInbound"), Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr("Allow SSH access to all machines"), Protocol: network.SecurityRuleProtocolTCP, SourceAddressPrefix: to.StringPtr("*"), SourcePortRange: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr("*"), DestinationPortRange: to.StringPtr("22"), Access: network.Allow, Priority: to.IntPtr(securityRuleInternalSSHInbound), Direction: network.Inbound, }, } func createInternalVirtualNetwork( client network.ManagementClient, resourceGroup string, location string, tags map[string]string, ) (*network.VirtualNetwork, error) { addressPrefixes := []string{"10.0.0.0/16"} virtualNetworkParams := network.VirtualNetwork{ Location: to.StringPtr(location), Tags: toTagsPtr(tags), Properties: &network.VirtualNetworkPropertiesFormat{
// OpenPorts is specified in the Instance interface. func (inst *azureInstance) OpenPorts(machineId string, ports []jujunetwork.PortRange) error { inst.env.mu.Lock() nsgClient := network.SecurityGroupsClient{inst.env.network} securityRuleClient := network.SecurityRulesClient{inst.env.network} inst.env.mu.Unlock() internalNetworkAddress, err := inst.internalNetworkAddress() if err != nil { return errors.Trace(err) } securityGroupName := internalSecurityGroupName nsg, err := nsgClient.Get(inst.env.resourceGroup, securityGroupName) if err != nil { return errors.Annotate(err, "querying network security group") } var securityRules []network.SecurityRule if nsg.Properties.SecurityRules != nil { securityRules = *nsg.Properties.SecurityRules } else { nsg.Properties.SecurityRules = &securityRules } // Create rules one at a time; this is necessary to avoid trampling // on changes made by the provisioner. We still record rules in the // NSG in memory, so we can easily tell which priorities are available. vmName := resourceName(names.NewMachineTag(machineId)) prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) for _, ports := range ports { ruleName := securityRuleName(prefix, ports) // Check if the rule already exists; OpenPorts must be idempotent. var found bool for _, rule := range securityRules { if to.String(rule.Name) == ruleName { found = true break } } if found { logger.Debugf("security rule %q already exists", ruleName) continue } logger.Debugf("creating security rule %q", ruleName) priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax) if err != nil { return errors.Annotatef(err, "getting security rule priority for %s", ports) } var protocol network.SecurityRuleProtocol switch ports.Protocol { case "tcp": protocol = network.SecurityRuleProtocolTCP case "udp": protocol = network.SecurityRuleProtocolUDP default: return errors.Errorf("invalid protocol %q", ports.Protocol) } var portRange string if ports.FromPort != ports.ToPort { portRange = fmt.Sprintf("%d-%d", ports.FromPort, ports.ToPort) } else { portRange = fmt.Sprint(ports.FromPort) } rule := network.SecurityRule{ Properties: &network.SecurityRulePropertiesFormat{ Description: to.StringPtr(ports.String()), Protocol: protocol, SourcePortRange: to.StringPtr("*"), DestinationPortRange: to.StringPtr(portRange), SourceAddressPrefix: to.StringPtr("*"), DestinationAddressPrefix: to.StringPtr(internalNetworkAddress.Value), Access: network.Allow, Priority: to.IntPtr(priority), Direction: network.Inbound, }, } if _, err := securityRuleClient.CreateOrUpdate( inst.env.resourceGroup, securityGroupName, ruleName, rule, ); err != nil { return errors.Annotatef(err, "creating security rule for %s", ports) } securityRules = append(securityRules, rule) } return nil }