github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/provisioner/provisioninginfo_test.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provisioner_test
     5  
     6  import (
     7  	jc "github.com/juju/testing/checkers"
     8  	gc "gopkg.in/check.v1"
     9  
    10  	"github.com/juju/juju/apiserver/params"
    11  	"github.com/juju/juju/apiserver/provisioner"
    12  	apiservertesting "github.com/juju/juju/apiserver/testing"
    13  	"github.com/juju/juju/constraints"
    14  	"github.com/juju/juju/environs/tags"
    15  	"github.com/juju/juju/juju/testing"
    16  	"github.com/juju/juju/state"
    17  	"github.com/juju/juju/state/multiwatcher"
    18  	"github.com/juju/juju/storage"
    19  	"github.com/juju/juju/storage/poolmanager"
    20  	storagedummy "github.com/juju/juju/storage/provider/dummy"
    21  	"github.com/juju/juju/storage/provider/registry"
    22  	coretesting "github.com/juju/juju/testing"
    23  )
    24  
    25  func (s *withoutControllerSuite) TestProvisioningInfoWithStorage(c *gc.C) {
    26  	s.registerStorageProviders(c, "static")
    27  
    28  	pm := poolmanager.New(state.NewStateSettings(s.State))
    29  	_, err := pm.Create("static-pool", "static", map[string]interface{}{"foo": "bar"})
    30  	c.Assert(err, jc.ErrorIsNil)
    31  
    32  	cons := constraints.MustParse("cpu-cores=123 mem=8G")
    33  	template := state.MachineTemplate{
    34  		Series:      "quantal",
    35  		Jobs:        []state.MachineJob{state.JobHostUnits},
    36  		Constraints: cons,
    37  		Placement:   "valid",
    38  		Volumes: []state.MachineVolumeParams{
    39  			{Volume: state.VolumeParams{Size: 1000, Pool: "static-pool"}},
    40  			{Volume: state.VolumeParams{Size: 2000, Pool: "static-pool"}},
    41  		},
    42  	}
    43  	placementMachine, err := s.State.AddOneMachine(template)
    44  	c.Assert(err, jc.ErrorIsNil)
    45  
    46  	args := params.Entities{Entities: []params.Entity{
    47  		{Tag: s.machines[0].Tag().String()},
    48  		{Tag: placementMachine.Tag().String()},
    49  	}}
    50  	result, err := s.provisioner.ProvisioningInfo(args)
    51  	c.Assert(err, jc.ErrorIsNil)
    52  
    53  	expected := params.ProvisioningInfoResults{
    54  		Results: []params.ProvisioningInfoResult{
    55  			{Result: &params.ProvisioningInfo{
    56  				Series: "quantal",
    57  				Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    58  				Tags: map[string]string{
    59  					tags.JujuController: coretesting.ModelTag.Id(),
    60  					tags.JujuModel:      coretesting.ModelTag.Id(),
    61  				},
    62  			}},
    63  			{Result: &params.ProvisioningInfo{
    64  				Series:      "quantal",
    65  				Constraints: template.Constraints,
    66  				Placement:   template.Placement,
    67  				Jobs:        []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    68  				Tags: map[string]string{
    69  					tags.JujuController: coretesting.ModelTag.Id(),
    70  					tags.JujuModel:      coretesting.ModelTag.Id(),
    71  				},
    72  				Volumes: []params.VolumeParams{{
    73  					VolumeTag:  "volume-0",
    74  					Size:       1000,
    75  					Provider:   "static",
    76  					Attributes: map[string]interface{}{"foo": "bar"},
    77  					Tags: map[string]string{
    78  						tags.JujuController: coretesting.ModelTag.Id(),
    79  						tags.JujuModel:      coretesting.ModelTag.Id(),
    80  					},
    81  					Attachment: &params.VolumeAttachmentParams{
    82  						MachineTag: placementMachine.Tag().String(),
    83  						VolumeTag:  "volume-0",
    84  						Provider:   "static",
    85  					},
    86  				}, {
    87  					VolumeTag:  "volume-1",
    88  					Size:       2000,
    89  					Provider:   "static",
    90  					Attributes: map[string]interface{}{"foo": "bar"},
    91  					Tags: map[string]string{
    92  						tags.JujuController: coretesting.ModelTag.Id(),
    93  						tags.JujuModel:      coretesting.ModelTag.Id(),
    94  					},
    95  					Attachment: &params.VolumeAttachmentParams{
    96  						MachineTag: placementMachine.Tag().String(),
    97  						VolumeTag:  "volume-1",
    98  						Provider:   "static",
    99  					},
   100  				}},
   101  			}},
   102  		},
   103  	}
   104  	// The order of volumes is not predictable, so we make sure we
   105  	// compare the right ones. This only applies to Results[1] since
   106  	// it is the only result to contain volumes.
   107  	if expected.Results[1].Result.Volumes[0].VolumeTag != result.Results[1].Result.Volumes[0].VolumeTag {
   108  		vols := expected.Results[1].Result.Volumes
   109  		vols[0], vols[1] = vols[1], vols[0]
   110  	}
   111  	c.Assert(result, jc.DeepEquals, expected)
   112  }
   113  
   114  func (s *withoutControllerSuite) registerStorageProviders(c *gc.C, names ...string) {
   115  	types := make([]storage.ProviderType, len(names))
   116  	for i, name := range names {
   117  		types[i] = storage.ProviderType(name)
   118  		if name == "dynamic" {
   119  			s.registerDynamicStorageProvider(c)
   120  		} else if name == "static" {
   121  			s.registerStaticStorageProvider(c)
   122  		} else {
   123  			c.Fatalf("unknown storage provider type: %q, expected static or dynamic", name)
   124  		}
   125  	}
   126  	registry.RegisterEnvironStorageProviders("dummy", types...)
   127  }
   128  
   129  func (s *withoutControllerSuite) registerDynamicStorageProvider(c *gc.C) {
   130  	registry.RegisterProvider("dynamic", &storagedummy.StorageProvider{IsDynamic: true})
   131  	s.AddCleanup(func(*gc.C) {
   132  		registry.RegisterProvider("dynamic", nil)
   133  	})
   134  }
   135  
   136  func (s *withoutControllerSuite) registerStaticStorageProvider(c *gc.C) {
   137  	registry.RegisterProvider("static", &storagedummy.StorageProvider{IsDynamic: false})
   138  	s.AddCleanup(func(*gc.C) {
   139  		registry.RegisterProvider("static", nil)
   140  	})
   141  }
   142  
   143  func (s *withoutControllerSuite) TestProvisioningInfoWithSingleNegativeAndPositiveSpaceInConstraints(c *gc.C) {
   144  	s.addSpacesAndSubnets(c)
   145  
   146  	cons := constraints.MustParse("cpu-cores=123 mem=8G spaces=^space1,space2")
   147  	template := state.MachineTemplate{
   148  		Series:      "quantal",
   149  		Jobs:        []state.MachineJob{state.JobHostUnits},
   150  		Constraints: cons,
   151  		Placement:   "valid",
   152  	}
   153  	placementMachine, err := s.State.AddOneMachine(template)
   154  	c.Assert(err, jc.ErrorIsNil)
   155  
   156  	args := params.Entities{Entities: []params.Entity{
   157  		{Tag: placementMachine.Tag().String()},
   158  	}}
   159  	result, err := s.provisioner.ProvisioningInfo(args)
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	expected := params.ProvisioningInfoResults{
   163  		Results: []params.ProvisioningInfoResult{{
   164  			Result: &params.ProvisioningInfo{
   165  				Series:      "quantal",
   166  				Constraints: template.Constraints,
   167  				Placement:   template.Placement,
   168  				Jobs:        []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   169  				Tags: map[string]string{
   170  					tags.JujuController: coretesting.ModelTag.Id(),
   171  					tags.JujuModel:      coretesting.ModelTag.Id(),
   172  				},
   173  				SubnetsToZones: map[string][]string{
   174  					"subnet-1": []string{"zone1"},
   175  					"subnet-2": []string{"zone2"},
   176  				},
   177  			},
   178  		}}}
   179  	c.Assert(result, jc.DeepEquals, expected)
   180  }
   181  
   182  func (s *withoutControllerSuite) addSpacesAndSubnets(c *gc.C) {
   183  	// Add a couple of spaces.
   184  	_, err := s.State.AddSpace("space1", "first space id", nil, true)
   185  	c.Assert(err, jc.ErrorIsNil)
   186  	_, err = s.State.AddSpace("space2", "", nil, false) // no provider ID
   187  	c.Assert(err, jc.ErrorIsNil)
   188  	// Add 1 subnet into space1, and 2 into space2.
   189  	// Only the first subnet of space2 has AllocatableIPLow|High set.
   190  	// Each subnet is in a matching zone (e.g "subnet-#" in "zone#").
   191  	testing.AddSubnetsWithTemplate(c, s.State, 3, state.SubnetInfo{
   192  		CIDR:              "10.10.{{.}}.0/24",
   193  		ProviderId:        "subnet-{{.}}",
   194  		AllocatableIPLow:  "{{if (eq . 1)}}10.10.{{.}}.5{{end}}",
   195  		AllocatableIPHigh: "{{if (eq . 1)}}10.10.{{.}}.254{{end}}",
   196  		AvailabilityZone:  "zone{{.}}",
   197  		SpaceName:         "{{if (eq . 0)}}space1{{else}}space2{{end}}",
   198  		VLANTag:           42,
   199  	})
   200  }
   201  
   202  func (s *withoutControllerSuite) TestProvisioningInfoWithEndpointBindings(c *gc.C) {
   203  	s.addSpacesAndSubnets(c)
   204  
   205  	wordpressMachine, err := s.State.AddOneMachine(state.MachineTemplate{
   206  		Series: "quantal",
   207  		Jobs:   []state.MachineJob{state.JobHostUnits},
   208  	})
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	// Use juju names for spaces in bindings, simulating ''juju deploy
   212  	// --bind...' was called.
   213  	bindings := map[string]string{
   214  		"url": "space1", // has both name and provider ID
   215  		"db":  "space2", // has only name, no provider ID
   216  	}
   217  	wordpressCharm := s.AddTestingCharm(c, "wordpress")
   218  	wordpressService := s.AddTestingServiceWithBindings(c, "wordpress", wordpressCharm, bindings)
   219  	wordpressUnit, err := wordpressService.AddUnit()
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	err = wordpressUnit.AssignToMachine(wordpressMachine)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  
   224  	args := params.Entities{Entities: []params.Entity{
   225  		{Tag: wordpressMachine.Tag().String()},
   226  	}}
   227  	result, err := s.provisioner.ProvisioningInfo(args)
   228  	c.Assert(err, jc.ErrorIsNil)
   229  
   230  	expected := params.ProvisioningInfoResults{
   231  		Results: []params.ProvisioningInfoResult{{
   232  			Result: &params.ProvisioningInfo{
   233  				Series: "quantal",
   234  				Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   235  				Tags: map[string]string{
   236  					tags.JujuController:    coretesting.ModelTag.Id(),
   237  					tags.JujuModel:         coretesting.ModelTag.Id(),
   238  					tags.JujuUnitsDeployed: wordpressUnit.Name(),
   239  				},
   240  				// Ensure space names are translated to provider IDs, where
   241  				// possible.
   242  				EndpointBindings: map[string]string{
   243  					"db":  "space2",         // just name, no provider ID
   244  					"url": "first space id", // has provider ID
   245  					// We expect none of the unspecified bindings in the result.
   246  				},
   247  			},
   248  		}}}
   249  	c.Assert(result, jc.DeepEquals, expected)
   250  }
   251  
   252  func (s *withoutControllerSuite) TestProvisioningInfoWithUnsuitableSpacesConstraints(c *gc.C) {
   253  	// Add an empty space.
   254  	_, err := s.State.AddSpace("empty", "", nil, true)
   255  	c.Assert(err, jc.ErrorIsNil)
   256  
   257  	consEmptySpace := constraints.MustParse("cpu-cores=123 mem=8G spaces=empty")
   258  	consMissingSpace := constraints.MustParse("cpu-cores=123 mem=8G spaces=missing")
   259  	templates := []state.MachineTemplate{{
   260  		Series:      "quantal",
   261  		Jobs:        []state.MachineJob{state.JobHostUnits},
   262  		Constraints: consEmptySpace,
   263  		Placement:   "valid",
   264  	}, {
   265  		Series:      "quantal",
   266  		Jobs:        []state.MachineJob{state.JobHostUnits},
   267  		Constraints: consMissingSpace,
   268  		Placement:   "valid",
   269  	}}
   270  	placementMachines, err := s.State.AddMachines(templates...)
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Assert(placementMachines, gc.HasLen, 2)
   273  
   274  	args := params.Entities{Entities: []params.Entity{
   275  		{Tag: placementMachines[0].Tag().String()},
   276  		{Tag: placementMachines[1].Tag().String()},
   277  	}}
   278  	result, err := s.provisioner.ProvisioningInfo(args)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	expectedErrorEmptySpace := `cannot match subnets to zones: ` +
   282  		`cannot use space "empty" as deployment target: no subnets`
   283  	expectedErrorMissingSpace := `cannot match subnets to zones: ` +
   284  		`space "missing"` // " not found" will be appended by NotFoundError helper below.
   285  	expected := params.ProvisioningInfoResults{Results: []params.ProvisioningInfoResult{
   286  		{Error: apiservertesting.ServerError(expectedErrorEmptySpace)},
   287  		{Error: apiservertesting.NotFoundError(expectedErrorMissingSpace)},
   288  	}}
   289  	c.Assert(result, jc.DeepEquals, expected)
   290  }
   291  
   292  func (s *withoutControllerSuite) TestStorageProviderFallbackToType(c *gc.C) {
   293  	s.registerStorageProviders(c, "dynamic", "static")
   294  
   295  	template := state.MachineTemplate{
   296  		Series:    "quantal",
   297  		Jobs:      []state.MachineJob{state.JobHostUnits},
   298  		Placement: "valid",
   299  		Volumes: []state.MachineVolumeParams{
   300  			{Volume: state.VolumeParams{Size: 1000, Pool: "dynamic"}},
   301  			{Volume: state.VolumeParams{Size: 1000, Pool: "static"}},
   302  		},
   303  	}
   304  	placementMachine, err := s.State.AddOneMachine(template)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  
   307  	args := params.Entities{Entities: []params.Entity{
   308  		{Tag: placementMachine.Tag().String()},
   309  	}}
   310  	result, err := s.provisioner.ProvisioningInfo(args)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	c.Assert(result, jc.DeepEquals, params.ProvisioningInfoResults{
   314  		Results: []params.ProvisioningInfoResult{
   315  			{Result: &params.ProvisioningInfo{
   316  				Series:      "quantal",
   317  				Constraints: template.Constraints,
   318  				Placement:   template.Placement,
   319  				Jobs:        []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   320  				Tags: map[string]string{
   321  					tags.JujuController: coretesting.ModelTag.Id(),
   322  					tags.JujuModel:      coretesting.ModelTag.Id(),
   323  				},
   324  				Volumes: []params.VolumeParams{{
   325  					VolumeTag:  "volume-1",
   326  					Size:       1000,
   327  					Provider:   "static",
   328  					Attributes: nil,
   329  					Tags: map[string]string{
   330  						tags.JujuController: coretesting.ModelTag.Id(),
   331  						tags.JujuModel:      coretesting.ModelTag.Id(),
   332  					},
   333  					Attachment: &params.VolumeAttachmentParams{
   334  						MachineTag: placementMachine.Tag().String(),
   335  						VolumeTag:  "volume-1",
   336  						Provider:   "static",
   337  					},
   338  				}},
   339  			}},
   340  		},
   341  	})
   342  }
   343  
   344  func (s *withoutControllerSuite) TestProvisioningInfoPermissions(c *gc.C) {
   345  	// Login as a machine agent for machine 0.
   346  	anAuthorizer := s.authorizer
   347  	anAuthorizer.EnvironManager = false
   348  	anAuthorizer.Tag = s.machines[0].Tag()
   349  	aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
   350  	c.Assert(err, jc.ErrorIsNil)
   351  	c.Assert(aProvisioner, gc.NotNil)
   352  
   353  	args := params.Entities{Entities: []params.Entity{
   354  		{Tag: s.machines[0].Tag().String()},
   355  		{Tag: s.machines[0].Tag().String() + "-lxc-0"},
   356  		{Tag: "machine-42"},
   357  		{Tag: s.machines[1].Tag().String()},
   358  		{Tag: "service-bar"},
   359  	}}
   360  
   361  	// Only machine 0 and containers therein can be accessed.
   362  	results, err := aProvisioner.ProvisioningInfo(args)
   363  	c.Assert(results, jc.DeepEquals, params.ProvisioningInfoResults{
   364  		Results: []params.ProvisioningInfoResult{
   365  			{Result: &params.ProvisioningInfo{
   366  				Series: "quantal",
   367  				Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   368  				Tags: map[string]string{
   369  					tags.JujuController: coretesting.ModelTag.Id(),
   370  					tags.JujuModel:      coretesting.ModelTag.Id(),
   371  				},
   372  			}},
   373  			{Error: apiservertesting.NotFoundError("machine 0/lxc/0")},
   374  			{Error: apiservertesting.ErrUnauthorized},
   375  			{Error: apiservertesting.ErrUnauthorized},
   376  			{Error: apiservertesting.ErrUnauthorized},
   377  		},
   378  	})
   379  }