github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/azure/environ_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"path"
    12  	"reflect"
    13  	"time"
    14  
    15  	autorestazure "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/azure"
    16  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/mocks"
    17  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to"
    18  	"github.com/Azure/azure-sdk-for-go/arm/compute"
    19  	"github.com/Azure/azure-sdk-for-go/arm/network"
    20  	"github.com/Azure/azure-sdk-for-go/arm/resources"
    21  	"github.com/Azure/azure-sdk-for-go/arm/storage"
    22  	"github.com/juju/names"
    23  	jc "github.com/juju/testing/checkers"
    24  	"github.com/juju/utils/arch"
    25  	gc "gopkg.in/check.v1"
    26  
    27  	"github.com/juju/juju/api"
    28  	"github.com/juju/juju/cloudconfig/instancecfg"
    29  	"github.com/juju/juju/constraints"
    30  	"github.com/juju/juju/environs"
    31  	"github.com/juju/juju/environs/imagemetadata"
    32  	"github.com/juju/juju/environs/simplestreams"
    33  	"github.com/juju/juju/environs/tags"
    34  	envtesting "github.com/juju/juju/environs/testing"
    35  	envtools "github.com/juju/juju/environs/tools"
    36  	"github.com/juju/juju/instance"
    37  	"github.com/juju/juju/mongo"
    38  	"github.com/juju/juju/provider/azure"
    39  	"github.com/juju/juju/provider/azure/internal/azuretesting"
    40  	"github.com/juju/juju/testing"
    41  	"github.com/juju/juju/tools"
    42  	"github.com/juju/version"
    43  )
    44  
    45  type environSuite struct {
    46  	testing.BaseSuite
    47  
    48  	provider      environs.EnvironProvider
    49  	requests      []*http.Request
    50  	storageClient azuretesting.MockStorageClient
    51  	sender        azuretesting.Senders
    52  
    53  	tags                          map[string]*string
    54  	vmSizes                       *compute.VirtualMachineSizeListResult
    55  	storageNameAvailabilityResult *storage.CheckNameAvailabilityResult
    56  	storageAccount                *storage.Account
    57  	storageAccountKeys            *storage.AccountKeys
    58  	vnet                          *network.VirtualNetwork
    59  	subnet                        *network.Subnet
    60  	ubuntuServerSKUs              []compute.VirtualMachineImageResource
    61  	publicIPAddress               *network.PublicIPAddress
    62  	oldNetworkInterfaces          *network.InterfaceListResult
    63  	newNetworkInterface           *network.Interface
    64  	jujuAvailabilitySet           *compute.AvailabilitySet
    65  	virtualMachine                *compute.VirtualMachine
    66  }
    67  
    68  var _ = gc.Suite(&environSuite{})
    69  
    70  func (s *environSuite) SetUpTest(c *gc.C) {
    71  	s.BaseSuite.SetUpTest(c)
    72  	s.storageClient = azuretesting.MockStorageClient{}
    73  	s.sender = nil
    74  	s.provider, _ = newProviders(c, azure.ProviderConfig{
    75  		Sender:           &s.sender,
    76  		RequestInspector: requestRecorder(&s.requests),
    77  		NewStorageClient: s.storageClient.NewClient,
    78  	})
    79  
    80  	emptyTags := make(map[string]*string)
    81  	s.tags = map[string]*string{
    82  		"juju-machine-name": to.StringPtr("machine-0"),
    83  	}
    84  
    85  	vmSizes := []compute.VirtualMachineSize{{
    86  		Name:                 to.StringPtr("Standard_D1"),
    87  		NumberOfCores:        to.IntPtr(1),
    88  		OsDiskSizeInMB:       to.IntPtr(1047552),
    89  		ResourceDiskSizeInMB: to.IntPtr(51200),
    90  		MemoryInMB:           to.IntPtr(3584),
    91  		MaxDataDiskCount:     to.IntPtr(2),
    92  	}}
    93  	s.vmSizes = &compute.VirtualMachineSizeListResult{Value: &vmSizes}
    94  
    95  	s.storageNameAvailabilityResult = &storage.CheckNameAvailabilityResult{
    96  		NameAvailable: to.BoolPtr(true),
    97  	}
    98  
    99  	s.storageAccount = &storage.Account{
   100  		Name: to.StringPtr("my-storage-account"),
   101  		Type: to.StringPtr("Standard_LRS"),
   102  		Properties: &storage.AccountProperties{
   103  			PrimaryEndpoints: &storage.Endpoints{
   104  				Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", fakeStorageAccount)),
   105  			},
   106  		},
   107  	}
   108  
   109  	s.storageAccountKeys = &storage.AccountKeys{
   110  		Key1: to.StringPtr("key-1"),
   111  	}
   112  
   113  	addressPrefixes := make([]string, 256)
   114  	for i := range addressPrefixes {
   115  		addressPrefixes[i] = fmt.Sprintf("10.%d.0.0/16", i)
   116  	}
   117  	s.vnet = &network.VirtualNetwork{
   118  		ID:       to.StringPtr("juju-internal"),
   119  		Name:     to.StringPtr("juju-internal"),
   120  		Location: to.StringPtr("westus"),
   121  		Tags:     &emptyTags,
   122  		Properties: &network.VirtualNetworkPropertiesFormat{
   123  			AddressSpace: &network.AddressSpace{&addressPrefixes},
   124  		},
   125  	}
   126  
   127  	s.subnet = &network.Subnet{
   128  		ID:   to.StringPtr("subnet-id"),
   129  		Name: to.StringPtr("juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d"),
   130  		Properties: &network.SubnetPropertiesFormat{
   131  			AddressPrefix: to.StringPtr("10.0.0.0/16"),
   132  		},
   133  	}
   134  
   135  	s.ubuntuServerSKUs = []compute.VirtualMachineImageResource{
   136  		{Name: to.StringPtr("12.04-LTS")},
   137  		{Name: to.StringPtr("12.10")},
   138  		{Name: to.StringPtr("14.04-LTS")},
   139  		{Name: to.StringPtr("15.04")},
   140  		{Name: to.StringPtr("15.10")},
   141  	}
   142  
   143  	s.publicIPAddress = &network.PublicIPAddress{
   144  		ID:       to.StringPtr("public-ip-id"),
   145  		Name:     to.StringPtr("machine-0-public-ip"),
   146  		Location: to.StringPtr("westus"),
   147  		Tags:     &s.tags,
   148  		Properties: &network.PublicIPAddressPropertiesFormat{
   149  			PublicIPAllocationMethod: network.Dynamic,
   150  			IPAddress:                to.StringPtr("1.2.3.4"),
   151  		},
   152  	}
   153  
   154  	// Existing IPs/NICs. These are the results of querying NICs so we
   155  	// can tell which IP to allocate.
   156  	oldIPConfigurations := []network.InterfaceIPConfiguration{{
   157  		ID:   to.StringPtr("ip-configuration-0-id"),
   158  		Name: to.StringPtr("ip-configuration-0"),
   159  		Properties: &network.InterfaceIPConfigurationPropertiesFormat{
   160  			PrivateIPAddress:          to.StringPtr("10.0.0.4"),
   161  			PrivateIPAllocationMethod: network.Static,
   162  			Subnet: &network.SubResource{ID: s.subnet.ID},
   163  		},
   164  	}}
   165  	oldNetworkInterfaces := []network.Interface{{
   166  		ID:   to.StringPtr("network-interface-0-id"),
   167  		Name: to.StringPtr("network-interface-0"),
   168  		Properties: &network.InterfacePropertiesFormat{
   169  			IPConfigurations: &oldIPConfigurations,
   170  			Primary:          to.BoolPtr(true),
   171  		},
   172  	}}
   173  	s.oldNetworkInterfaces = &network.InterfaceListResult{
   174  		Value: &oldNetworkInterfaces,
   175  	}
   176  
   177  	// nsgID is the name of the internal network security group. This NSG
   178  	// is created when the environment is created.
   179  	nsgID := path.Join(
   180  		"/subscriptions", fakeSubscriptionId,
   181  		"resourceGroups", "juju-testenv-model-"+testing.ModelTag.Id(),
   182  		"providers/Microsoft.Network/networkSecurityGroups/juju-internal",
   183  	)
   184  
   185  	// The newly created IP/NIC.
   186  	newIPConfigurations := []network.InterfaceIPConfiguration{{
   187  		ID:   to.StringPtr("ip-configuration-1-id"),
   188  		Name: to.StringPtr("primary"),
   189  		Properties: &network.InterfaceIPConfigurationPropertiesFormat{
   190  			PrivateIPAddress:          to.StringPtr("10.0.0.5"),
   191  			PrivateIPAllocationMethod: network.Static,
   192  			Subnet:          &network.SubResource{ID: s.subnet.ID},
   193  			PublicIPAddress: &network.SubResource{ID: s.publicIPAddress.ID},
   194  		},
   195  	}}
   196  	s.newNetworkInterface = &network.Interface{
   197  		ID:       to.StringPtr("network-interface-1-id"),
   198  		Name:     to.StringPtr("network-interface-1"),
   199  		Location: to.StringPtr("westus"),
   200  		Tags:     &s.tags,
   201  		Properties: &network.InterfacePropertiesFormat{
   202  			IPConfigurations:     &newIPConfigurations,
   203  			NetworkSecurityGroup: &network.SubResource{to.StringPtr(nsgID)},
   204  		},
   205  	}
   206  
   207  	s.jujuAvailabilitySet = &compute.AvailabilitySet{
   208  		ID:       to.StringPtr("juju-availability-set-id"),
   209  		Name:     to.StringPtr("juju"),
   210  		Location: to.StringPtr("westus"),
   211  		Tags:     &emptyTags,
   212  	}
   213  
   214  	sshPublicKeys := []compute.SSHPublicKey{{
   215  		Path:    to.StringPtr("/home/ubuntu/.ssh/authorized_keys"),
   216  		KeyData: to.StringPtr(testing.FakeAuthKeys),
   217  	}}
   218  	networkInterfaceReferences := []compute.NetworkInterfaceReference{{
   219  		ID: s.newNetworkInterface.ID,
   220  		Properties: &compute.NetworkInterfaceReferenceProperties{
   221  			Primary: to.BoolPtr(true),
   222  		},
   223  	}}
   224  	s.virtualMachine = &compute.VirtualMachine{
   225  		ID:       to.StringPtr("machine-0-id"),
   226  		Name:     to.StringPtr("machine-0"),
   227  		Location: to.StringPtr("westus"),
   228  		Tags:     &s.tags,
   229  		Properties: &compute.VirtualMachineProperties{
   230  			HardwareProfile: &compute.HardwareProfile{
   231  				VMSize: "Standard_D1",
   232  			},
   233  			StorageProfile: &compute.StorageProfile{
   234  				ImageReference: &compute.ImageReference{
   235  					Publisher: to.StringPtr("Canonical"),
   236  					Offer:     to.StringPtr("UbuntuServer"),
   237  					Sku:       to.StringPtr("12.10"),
   238  					Version:   to.StringPtr("latest"),
   239  				},
   240  				OsDisk: &compute.OSDisk{
   241  					Name:         to.StringPtr("machine-0"),
   242  					CreateOption: compute.FromImage,
   243  					Caching:      compute.ReadWrite,
   244  					Vhd: &compute.VirtualHardDisk{
   245  						URI: to.StringPtr(fmt.Sprintf(
   246  							"https://%s.blob.storage.azurestack.local/osvhds/machine-0.vhd",
   247  							fakeStorageAccount,
   248  						)),
   249  					},
   250  				},
   251  			},
   252  			OsProfile: &compute.OSProfile{
   253  				ComputerName:  to.StringPtr("machine-0"),
   254  				CustomData:    to.StringPtr("<juju-goes-here>"),
   255  				AdminUsername: to.StringPtr("ubuntu"),
   256  				LinuxConfiguration: &compute.LinuxConfiguration{
   257  					DisablePasswordAuthentication: to.BoolPtr(true),
   258  					SSH: &compute.SSHConfiguration{
   259  						PublicKeys: &sshPublicKeys,
   260  					},
   261  				},
   262  			},
   263  			NetworkProfile: &compute.NetworkProfile{
   264  				NetworkInterfaces: &networkInterfaceReferences,
   265  			},
   266  			AvailabilitySet:   &compute.SubResource{ID: s.jujuAvailabilitySet.ID},
   267  			ProvisioningState: to.StringPtr("Successful"),
   268  		},
   269  	}
   270  }
   271  
   272  func (s *environSuite) openEnviron(c *gc.C, attrs ...testing.Attrs) environs.Environ {
   273  	attrs = append([]testing.Attrs{{"storage-account": fakeStorageAccount}}, attrs...)
   274  	return openEnviron(c, s.provider, &s.sender, attrs...)
   275  }
   276  
   277  func openEnviron(
   278  	c *gc.C,
   279  	provider environs.EnvironProvider,
   280  	sender *azuretesting.Senders,
   281  	attrs ...testing.Attrs,
   282  ) environs.Environ {
   283  	// Opening the environment should not incur network communication,
   284  	// so we don't set s.sender until after opening.
   285  	cfg := makeTestModelConfig(c, attrs...)
   286  	env, err := provider.Open(cfg)
   287  	c.Assert(err, jc.ErrorIsNil)
   288  
   289  	// Force an explicit refresh of the access token, so it isn't done
   290  	// implicitly during the tests.
   291  	*sender = azuretesting.Senders{tokenRefreshSender()}
   292  	err = azure.ForceTokenRefresh(env)
   293  	c.Assert(err, jc.ErrorIsNil)
   294  	return env
   295  }
   296  
   297  func prepareForBootstrap(
   298  	c *gc.C,
   299  	ctx environs.BootstrapContext,
   300  	provider environs.EnvironProvider,
   301  	sender *azuretesting.Senders,
   302  	attrs ...testing.Attrs,
   303  ) environs.Environ {
   304  	// Opening the environment should not incur network communication,
   305  	// so we don't set s.sender until after opening.
   306  	cfg := makeTestModelConfig(c, attrs...)
   307  	cfg, err := cfg.Remove([]string{"controller-resource-group"})
   308  	c.Assert(err, jc.ErrorIsNil)
   309  	*sender = azuretesting.Senders{tokenRefreshSender()}
   310  	cfg, err = provider.BootstrapConfig(environs.BootstrapConfigParams{
   311  		Config:               cfg,
   312  		CloudRegion:          "westus",
   313  		CloudEndpoint:        "https://management.azure.com",
   314  		CloudStorageEndpoint: "https://core.windows.net",
   315  		Credentials:          fakeUserPassCredential(),
   316  	})
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	env, err := provider.PrepareForBootstrap(ctx, cfg)
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	return env
   321  }
   322  
   323  func tokenRefreshSender() *azuretesting.MockSender {
   324  	tokenRefreshSender := azuretesting.NewSenderWithValue(&autorestazure.Token{
   325  		AccessToken: "access-token",
   326  		ExpiresOn:   fmt.Sprint(time.Now().Add(time.Hour).Unix()),
   327  		Type:        "Bearer",
   328  	})
   329  	tokenRefreshSender.PathPattern = ".*/oauth2/token"
   330  	return tokenRefreshSender
   331  }
   332  
   333  func (s *environSuite) initResourceGroupSenders() azuretesting.Senders {
   334  	resourceGroupName := "juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d"
   335  	return azuretesting.Senders{
   336  		s.makeSender(".*/resourcegroups/"+resourceGroupName, &resources.Group{}),
   337  		s.makeSender(".*/virtualnetworks/juju-internal", s.vnet),
   338  		s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{}),
   339  		s.makeSender(".*/virtualnetworks/juju-internal/subnets/"+resourceGroupName, &s.subnet),
   340  		s.makeSender(".*/checkNameAvailability", s.storageNameAvailabilityResult),
   341  		s.makeSender(".*/storageAccounts/.*", s.storageAccount),
   342  		s.makeSender(".*/storageAccounts/.*/listKeys", s.storageAccountKeys),
   343  	}
   344  }
   345  
   346  func (s *environSuite) startInstanceSenders(controller bool) azuretesting.Senders {
   347  	senders := azuretesting.Senders{
   348  		s.vmSizesSender(),
   349  		s.makeSender(".*/subnets/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d", s.subnet),
   350  		s.makeSender(".*/Canonical/.*/UbuntuServer/skus", s.ubuntuServerSKUs),
   351  		s.makeSender(".*/publicIPAddresses/machine-0-public-ip", s.publicIPAddress),
   352  		s.makeSender(".*/networkInterfaces", s.oldNetworkInterfaces),
   353  		s.makeSender(".*/networkInterfaces/machine-0-primary", s.newNetworkInterface),
   354  	}
   355  	if controller {
   356  		senders = append(senders,
   357  			s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{
   358  				Properties: &network.SecurityGroupPropertiesFormat{},
   359  			}),
   360  			s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{}),
   361  		)
   362  	}
   363  	senders = append(senders,
   364  		s.makeSender(".*/availabilitySets/.*", s.jujuAvailabilitySet),
   365  		s.makeSender(".*/virtualMachines/machine-0", s.virtualMachine),
   366  	)
   367  	return senders
   368  }
   369  
   370  func (s *environSuite) networkInterfacesSender(nics ...network.Interface) *azuretesting.MockSender {
   371  	return s.makeSender(".*/networkInterfaces", network.InterfaceListResult{Value: &nics})
   372  }
   373  
   374  func (s *environSuite) publicIPAddressesSender(pips ...network.PublicIPAddress) *azuretesting.MockSender {
   375  	return s.makeSender(".*/publicIPAddresses", network.PublicIPAddressListResult{Value: &pips})
   376  }
   377  
   378  func (s *environSuite) virtualMachinesSender(vms ...compute.VirtualMachine) *azuretesting.MockSender {
   379  	return s.makeSender(".*/virtualMachines", compute.VirtualMachineListResult{Value: &vms})
   380  }
   381  
   382  func (s *environSuite) vmSizesSender() *azuretesting.MockSender {
   383  	return s.makeSender(".*/vmSizes", s.vmSizes)
   384  }
   385  
   386  func (s *environSuite) makeSender(pattern string, v interface{}) *azuretesting.MockSender {
   387  	sender := azuretesting.NewSenderWithValue(v)
   388  	sender.PathPattern = pattern
   389  	return sender
   390  }
   391  
   392  func makeStartInstanceParams(c *gc.C, series string) environs.StartInstanceParams {
   393  	machineTag := names.NewMachineTag("0")
   394  	stateInfo := &mongo.MongoInfo{
   395  		Info: mongo.Info{
   396  			CACert: testing.CACert,
   397  			Addrs:  []string{"localhost:123"},
   398  		},
   399  		Password: "password",
   400  		Tag:      machineTag,
   401  	}
   402  	apiInfo := &api.Info{
   403  		Addrs:    []string{"localhost:246"},
   404  		CACert:   testing.CACert,
   405  		Password: "admin",
   406  		Tag:      machineTag,
   407  		ModelTag: testing.ModelTag,
   408  	}
   409  
   410  	const secureServerConnections = true
   411  	icfg, err := instancecfg.NewInstanceConfig(
   412  		machineTag.Id(), "yanonce", imagemetadata.ReleasedStream,
   413  		series, "", secureServerConnections, stateInfo, apiInfo,
   414  	)
   415  	c.Assert(err, jc.ErrorIsNil)
   416  
   417  	return environs.StartInstanceParams{
   418  		Tools:          makeToolsList(series),
   419  		InstanceConfig: icfg,
   420  	}
   421  }
   422  
   423  func makeToolsList(series string) tools.List {
   424  	var toolsVersion version.Binary
   425  	toolsVersion.Number = version.MustParse("1.26.0")
   426  	toolsVersion.Arch = arch.AMD64
   427  	toolsVersion.Series = series
   428  	return tools.List{{
   429  		Version: toolsVersion,
   430  		URL:     fmt.Sprintf("http://example.com/tools/juju-%s.tgz", toolsVersion),
   431  		SHA256:  "1234567890abcdef",
   432  		Size:    1024,
   433  	}}
   434  }
   435  
   436  func unmarshalRequestBody(c *gc.C, req *http.Request, out interface{}) {
   437  	bytes, err := ioutil.ReadAll(req.Body)
   438  	c.Assert(err, jc.ErrorIsNil)
   439  	err = json.Unmarshal(bytes, out)
   440  	c.Assert(err, jc.ErrorIsNil)
   441  }
   442  
   443  func assertRequestBody(c *gc.C, req *http.Request, expect interface{}) {
   444  	unmarshalled := reflect.New(reflect.TypeOf(expect).Elem()).Interface()
   445  	unmarshalRequestBody(c, req, unmarshalled)
   446  	c.Assert(unmarshalled, jc.DeepEquals, expect)
   447  }
   448  
   449  func (s *environSuite) TestOpen(c *gc.C) {
   450  	cfg := makeTestModelConfig(c)
   451  	env, err := s.provider.Open(cfg)
   452  	c.Assert(err, jc.ErrorIsNil)
   453  	c.Assert(env, gc.NotNil)
   454  }
   455  
   456  func (s *environSuite) TestCloudEndpointManagementURI(c *gc.C) {
   457  	env := s.openEnviron(c)
   458  
   459  	sender := mocks.NewSender()
   460  	sender.EmitContent("{}")
   461  	s.sender = azuretesting.Senders{sender}
   462  	s.requests = nil
   463  	env.AllInstances() // trigger a query
   464  
   465  	c.Assert(s.requests, gc.HasLen, 1)
   466  	c.Assert(s.requests[0].URL.Host, gc.Equals, "api.azurestack.local")
   467  }
   468  
   469  func (s *environSuite) TestStartInstance(c *gc.C) {
   470  	env := s.openEnviron(c)
   471  	s.sender = s.startInstanceSenders(false)
   472  	s.requests = nil
   473  	result, err := env.StartInstance(makeStartInstanceParams(c, "quantal"))
   474  	c.Assert(err, jc.ErrorIsNil)
   475  	c.Assert(result, gc.NotNil)
   476  	c.Assert(result.Instance, gc.NotNil)
   477  	c.Assert(result.NetworkInfo, gc.HasLen, 0)
   478  	c.Assert(result.Volumes, gc.HasLen, 0)
   479  	c.Assert(result.VolumeAttachments, gc.HasLen, 0)
   480  
   481  	arch := "amd64"
   482  	mem := uint64(3584)
   483  	rootDisk := uint64(29495) // ~30 GB
   484  	cpuCores := uint64(1)
   485  	c.Assert(result.Hardware, jc.DeepEquals, &instance.HardwareCharacteristics{
   486  		Arch:     &arch,
   487  		Mem:      &mem,
   488  		RootDisk: &rootDisk,
   489  		CpuCores: &cpuCores,
   490  	})
   491  	requests := s.assertStartInstanceRequests(c)
   492  	availabilitySetName := path.Base(requests.availabilitySet.URL.Path)
   493  	c.Assert(availabilitySetName, gc.Equals, "juju")
   494  }
   495  
   496  func (s *environSuite) TestStartInstanceDistributionGroup(c *gc.C) {
   497  	c.Skip("TODO: test StartInstance's DistributionGroup behaviour")
   498  }
   499  
   500  func (s *environSuite) TestStartInstanceServiceAvailabilitySet(c *gc.C) {
   501  	env := s.openEnviron(c)
   502  	s.sender = s.startInstanceSenders(false)
   503  	s.requests = nil
   504  	unitsDeployed := "mysql/0 wordpress/0"
   505  	params := makeStartInstanceParams(c, "quantal")
   506  	params.InstanceConfig.Tags[tags.JujuUnitsDeployed] = unitsDeployed
   507  	_, err := env.StartInstance(params)
   508  	c.Assert(err, jc.ErrorIsNil)
   509  	s.tags[tags.JujuUnitsDeployed] = &unitsDeployed
   510  	requests := s.assertStartInstanceRequests(c)
   511  	availabilitySetName := path.Base(requests.availabilitySet.URL.Path)
   512  	c.Assert(availabilitySetName, gc.Equals, "mysql")
   513  }
   514  
   515  func (s *environSuite) assertStartInstanceRequests(c *gc.C) startInstanceRequests {
   516  	// Clear the fields that don't get sent in the request.
   517  	s.publicIPAddress.ID = nil
   518  	s.publicIPAddress.Name = nil
   519  	s.publicIPAddress.Properties.IPAddress = nil
   520  	s.newNetworkInterface.ID = nil
   521  	s.newNetworkInterface.Name = nil
   522  	(*s.newNetworkInterface.Properties.IPConfigurations)[0].ID = nil
   523  	s.jujuAvailabilitySet.ID = nil
   524  	s.jujuAvailabilitySet.Name = nil
   525  	s.virtualMachine.ID = nil
   526  	s.virtualMachine.Name = nil
   527  	s.virtualMachine.Properties.ProvisioningState = nil
   528  
   529  	// Validate HTTP request bodies.
   530  	c.Assert(s.requests, gc.HasLen, 8)
   531  	c.Assert(s.requests[0].Method, gc.Equals, "GET") // vmSizes
   532  	c.Assert(s.requests[1].Method, gc.Equals, "GET") // juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d
   533  	c.Assert(s.requests[2].Method, gc.Equals, "GET") // skus
   534  	c.Assert(s.requests[3].Method, gc.Equals, "PUT")
   535  	assertRequestBody(c, s.requests[3], s.publicIPAddress)
   536  	c.Assert(s.requests[4].Method, gc.Equals, "GET") // NICs
   537  	c.Assert(s.requests[5].Method, gc.Equals, "PUT")
   538  	assertRequestBody(c, s.requests[5], s.newNetworkInterface)
   539  	c.Assert(s.requests[6].Method, gc.Equals, "PUT")
   540  	assertRequestBody(c, s.requests[6], s.jujuAvailabilitySet)
   541  	c.Assert(s.requests[7].Method, gc.Equals, "PUT")
   542  
   543  	// CustomData is non-deterministic, so don't compare it.
   544  	// TODO(axw) shouldn't CustomData be deterministic? Look into this.
   545  	var virtualMachine compute.VirtualMachine
   546  	unmarshalRequestBody(c, s.requests[7], &virtualMachine)
   547  	c.Assert(to.String(virtualMachine.Properties.OsProfile.CustomData), gc.Not(gc.HasLen), 0)
   548  	virtualMachine.Properties.OsProfile.CustomData = to.StringPtr("<juju-goes-here>")
   549  	c.Assert(&virtualMachine, jc.DeepEquals, s.virtualMachine)
   550  
   551  	return startInstanceRequests{
   552  		vmSizes:          s.requests[0],
   553  		subnet:           s.requests[1],
   554  		skus:             s.requests[2],
   555  		publicIPAddress:  s.requests[3],
   556  		nics:             s.requests[4],
   557  		networkInterface: s.requests[5],
   558  		availabilitySet:  s.requests[6],
   559  		virtualMachine:   s.requests[7],
   560  	}
   561  }
   562  
   563  type startInstanceRequests struct {
   564  	vmSizes          *http.Request
   565  	subnet           *http.Request
   566  	skus             *http.Request
   567  	publicIPAddress  *http.Request
   568  	nics             *http.Request
   569  	networkInterface *http.Request
   570  	availabilitySet  *http.Request
   571  	virtualMachine   *http.Request
   572  }
   573  
   574  func (s *environSuite) TestBootstrap(c *gc.C) {
   575  	defer envtesting.DisableFinishBootstrap()()
   576  
   577  	ctx := envtesting.BootstrapContext(c)
   578  	env := prepareForBootstrap(c, ctx, s.provider, &s.sender)
   579  
   580  	s.sender = s.initResourceGroupSenders()
   581  	s.sender = append(s.sender, s.startInstanceSenders(true)...)
   582  	s.requests = nil
   583  	result, err := env.Bootstrap(
   584  		ctx, environs.BootstrapParams{
   585  			AvailableTools: makeToolsList("trusty"),
   586  		},
   587  	)
   588  	c.Assert(err, jc.ErrorIsNil)
   589  	c.Assert(result.Arch, gc.Equals, "amd64")
   590  	c.Assert(result.Series, gc.Equals, "trusty")
   591  
   592  	c.Assert(len(s.requests), gc.Equals, 17)
   593  
   594  	c.Assert(s.requests[0].Method, gc.Equals, "PUT")  // resource group
   595  	c.Assert(s.requests[1].Method, gc.Equals, "PUT")  // vnet
   596  	c.Assert(s.requests[2].Method, gc.Equals, "PUT")  // network security group
   597  	c.Assert(s.requests[3].Method, gc.Equals, "PUT")  // subnet
   598  	c.Assert(s.requests[4].Method, gc.Equals, "POST") // check storage account name
   599  	c.Assert(s.requests[5].Method, gc.Equals, "PUT")  // create storage account
   600  	c.Assert(s.requests[6].Method, gc.Equals, "POST") // get storage account keys
   601  
   602  	emptyTags := map[string]*string{}
   603  	assertRequestBody(c, s.requests[0], &resources.Group{
   604  		Location: to.StringPtr("westus"),
   605  		Tags:     &emptyTags,
   606  	})
   607  
   608  	s.vnet.ID = nil
   609  	s.vnet.Name = nil
   610  	assertRequestBody(c, s.requests[1], s.vnet)
   611  
   612  	securityRules := []network.SecurityRule{{
   613  		Name: to.StringPtr("SSHInbound"),
   614  		Properties: &network.SecurityRulePropertiesFormat{
   615  			Description:              to.StringPtr("Allow SSH access to all machines"),
   616  			Protocol:                 network.SecurityRuleProtocolTCP,
   617  			SourceAddressPrefix:      to.StringPtr("*"),
   618  			SourcePortRange:          to.StringPtr("*"),
   619  			DestinationAddressPrefix: to.StringPtr("*"),
   620  			DestinationPortRange:     to.StringPtr("22"),
   621  			Access:                   network.Allow,
   622  			Priority:                 to.IntPtr(100),
   623  			Direction:                network.Inbound,
   624  		},
   625  	}}
   626  	assertRequestBody(c, s.requests[2], &network.SecurityGroup{
   627  		Location: to.StringPtr("westus"),
   628  		Tags:     &emptyTags,
   629  		Properties: &network.SecurityGroupPropertiesFormat{
   630  			SecurityRules: &securityRules,
   631  		},
   632  	})
   633  
   634  	s.subnet.ID = nil
   635  	s.subnet.Name = nil
   636  	assertRequestBody(c, s.requests[3], s.subnet)
   637  
   638  	assertRequestBody(c, s.requests[4], &storage.AccountCheckNameAvailabilityParameters{
   639  		Name: to.StringPtr(fakeStorageAccount),
   640  		Type: to.StringPtr("Microsoft.Storage/storageAccounts"),
   641  	})
   642  
   643  	assertRequestBody(c, s.requests[5], &storage.AccountCreateParameters{
   644  		Location: to.StringPtr("westus"),
   645  		Tags:     &emptyTags,
   646  		Properties: &storage.AccountPropertiesCreateParameters{
   647  			AccountType: "Standard_LRS",
   648  		},
   649  	})
   650  }
   651  
   652  func (s *environSuite) TestAllInstancesResourceGroupNotFound(c *gc.C) {
   653  	env := s.openEnviron(c)
   654  	sender := mocks.NewSender()
   655  	sender.EmitStatus("resource group not found", http.StatusNotFound)
   656  	s.sender = azuretesting.Senders{sender}
   657  	_, err := env.AllInstances()
   658  	c.Assert(err, jc.ErrorIsNil)
   659  }
   660  
   661  func (s *environSuite) TestStopInstancesNotFound(c *gc.C) {
   662  	env := s.openEnviron(c)
   663  	sender := mocks.NewSender()
   664  	sender.EmitStatus("vm not found", http.StatusNotFound)
   665  	s.sender = azuretesting.Senders{sender, sender, sender}
   666  	err := env.StopInstances("a", "b")
   667  	c.Assert(err, jc.ErrorIsNil)
   668  }
   669  
   670  func (s *environSuite) TestStopInstances(c *gc.C) {
   671  	env := s.openEnviron(c)
   672  
   673  	// Security group has rules for machine-0 but not machine-1, and
   674  	// has a rule that doesn't match either.
   675  	nsg := makeSecurityGroup(
   676  		makeSecurityRule("machine-0-80", "10.0.0.4", "80"),
   677  		makeSecurityRule("machine-0-1000-2000", "10.0.0.4", "1000-2000"),
   678  		makeSecurityRule("machine-42", "10.0.0.5", "*"),
   679  	)
   680  
   681  	// Create an IP configuration with a public IP reference. This will
   682  	// cause an update to the NIC to detach public IPs.
   683  	nic0IPConfiguration := makeIPConfiguration("10.0.0.4")
   684  	nic0IPConfiguration.Properties.PublicIPAddress = &network.SubResource{}
   685  	nic0 := makeNetworkInterface("nic-0", "machine-0", nic0IPConfiguration)
   686  
   687  	s.sender = azuretesting.Senders{
   688  		s.networkInterfacesSender(
   689  			nic0,
   690  			makeNetworkInterface("nic-1", "machine-1"),
   691  			makeNetworkInterface("nic-2", "machine-1"),
   692  		),
   693  		s.virtualMachinesSender(makeVirtualMachine("machine-0")),
   694  		s.publicIPAddressesSender(
   695  			makePublicIPAddress("pip-0", "machine-0", "1.2.3.4"),
   696  		),
   697  		s.makeSender(".*/virtualMachines/machine-0", nil),                                             // DELETE
   698  		s.makeSender(".*/networkSecurityGroups/juju-internal", nsg),                                   // GET
   699  		s.makeSender(".*/networkSecurityGroups/juju-internal/securityRules/machine-0-80", nil),        // DELETE
   700  		s.makeSender(".*/networkSecurityGroups/juju-internal/securityRules/machine-0-1000-2000", nil), // DELETE
   701  		s.makeSender(".*/networkInterfaces/nic-0", nic0),                                              // PUT
   702  		s.makeSender(".*/publicIPAddresses/pip-0", nil),                                               // DELETE
   703  		s.makeSender(".*/networkInterfaces/nic-0", nil),                                               // DELETE
   704  		s.makeSender(".*/virtualMachines/machine-1", nil),                                             // DELETE
   705  		s.makeSender(".*/networkSecurityGroups/juju-internal", nsg),                                   // GET
   706  		s.makeSender(".*/networkInterfaces/nic-1", nil),                                               // DELETE
   707  		s.makeSender(".*/networkInterfaces/nic-2", nil),                                               // DELETE
   708  	}
   709  	err := env.StopInstances("machine-0", "machine-1", "machine-2")
   710  	c.Assert(err, jc.ErrorIsNil)
   711  
   712  	s.storageClient.CheckCallNames(c,
   713  		"NewClient", "DeleteBlobIfExists", "DeleteBlobIfExists",
   714  	)
   715  	s.storageClient.CheckCall(c, 1, "DeleteBlobIfExists", "osvhds", "machine-0")
   716  	s.storageClient.CheckCall(c, 2, "DeleteBlobIfExists", "osvhds", "machine-1")
   717  }
   718  
   719  func (s *environSuite) TestConstraintsValidatorUnsupported(c *gc.C) {
   720  	validator := s.constraintsValidator(c)
   721  	unsupported, err := validator.Validate(constraints.MustParse(
   722  		"arch=amd64 tags=foo cpu-power=100 virt-type=kvm",
   723  	))
   724  	c.Assert(err, jc.ErrorIsNil)
   725  	c.Assert(unsupported, jc.SameContents, []string{"tags", "cpu-power", "virt-type"})
   726  }
   727  
   728  func (s *environSuite) TestConstraintsValidatorVocabulary(c *gc.C) {
   729  	validator := s.constraintsValidator(c)
   730  	_, err := validator.Validate(constraints.MustParse("arch=armhf"))
   731  	c.Assert(err, gc.ErrorMatches,
   732  		"invalid constraint value: arch=armhf\nvalid values are: \\[amd64\\]",
   733  	)
   734  	_, err = validator.Validate(constraints.MustParse("instance-type=t1.micro"))
   735  	c.Assert(err, gc.ErrorMatches,
   736  		"invalid constraint value: instance-type=t1.micro\nvalid values are: \\[D1 Standard_D1\\]",
   737  	)
   738  }
   739  
   740  func (s *environSuite) TestConstraintsValidatorMerge(c *gc.C) {
   741  	validator := s.constraintsValidator(c)
   742  	cons, err := validator.Merge(
   743  		constraints.MustParse("mem=3G arch=amd64"),
   744  		constraints.MustParse("instance-type=D1"),
   745  	)
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	c.Assert(cons.String(), gc.Equals, "instance-type=D1")
   748  }
   749  
   750  func (s *environSuite) constraintsValidator(c *gc.C) constraints.Validator {
   751  	env := s.openEnviron(c)
   752  	s.sender = azuretesting.Senders{s.vmSizesSender()}
   753  	validator, err := env.ConstraintsValidator()
   754  	c.Assert(err, jc.ErrorIsNil)
   755  	return validator
   756  }
   757  
   758  func (s *environSuite) TestAgentMirror(c *gc.C) {
   759  	env := s.openEnviron(c)
   760  	c.Assert(env, gc.Implements, new(envtools.HasAgentMirror))
   761  	cloudSpec, err := env.(envtools.HasAgentMirror).AgentMirror()
   762  	c.Assert(err, jc.ErrorIsNil)
   763  	c.Assert(cloudSpec, gc.Equals, simplestreams.CloudSpec{
   764  		Region:   "westus",
   765  		Endpoint: "https://storage.azurestack.local/",
   766  	})
   767  }