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