github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/maas/maas2_environ_whitebox_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "fmt" 8 "net/http" 9 10 "github.com/juju/errors" 11 "github.com/juju/gomaasapi" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils/arch" 14 "github.com/juju/utils/set" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/constraints" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/bootstrap" 20 "github.com/juju/juju/environs/config" 21 envtesting "github.com/juju/juju/environs/testing" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/juju/testing" 24 "github.com/juju/juju/network" 25 coretesting "github.com/juju/juju/testing" 26 ) 27 28 type maas2EnvironSuite struct { 29 maas2Suite 30 } 31 32 var _ = gc.Suite(&maas2EnvironSuite{}) 33 34 func (suite *maas2EnvironSuite) getEnvWithServer(c *gc.C) (*maasEnviron, error) { 35 testServer := gomaasapi.NewSimpleServer() 36 testServer.AddGetResponse("/api/2.0/version/", http.StatusOK, maas2VersionResponse) 37 testServer.AddGetResponse("/api/2.0/users/?op=whoami", http.StatusOK, "{}") 38 // Weirdly, rather than returning a 404 when the version is 39 // unknown, MAAS2 returns some HTML (the login page). 40 testServer.AddGetResponse("/api/1.0/version/", http.StatusOK, "<html></html>") 41 testServer.Start() 42 suite.AddCleanup(func(*gc.C) { testServer.Close() }) 43 testAttrs := coretesting.Attrs{} 44 for k, v := range maasEnvAttrs { 45 testAttrs[k] = v 46 } 47 testAttrs["maas-server"] = testServer.Server.URL 48 attrs := coretesting.FakeConfig().Merge(testAttrs) 49 cfg, err := config.New(config.NoDefaults, attrs) 50 c.Assert(err, jc.ErrorIsNil) 51 return NewEnviron(cfg) 52 } 53 54 func (suite *maas2EnvironSuite) TestNewEnvironWithoutFeatureFlag(c *gc.C) { 55 suite.SetFeatureFlags() 56 _, err := suite.getEnvWithServer(c) 57 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 58 } 59 60 func (suite *maas2EnvironSuite) TestNewEnvironWithController(c *gc.C) { 61 env, err := suite.getEnvWithServer(c) 62 c.Assert(err, jc.ErrorIsNil) 63 c.Assert(env, gc.NotNil) 64 } 65 66 func (suite *maas2EnvironSuite) TestSupportedArchitectures(c *gc.C) { 67 controller := &fakeController{ 68 bootResources: []gomaasapi.BootResource{ 69 &fakeBootResource{name: "wily", architecture: "amd64/blah"}, 70 &fakeBootResource{name: "wily", architecture: "amd64/something"}, 71 &fakeBootResource{name: "xenial", architecture: "arm/somethingelse"}, 72 }, 73 } 74 env := suite.makeEnviron(c, controller) 75 result, err := env.SupportedArchitectures() 76 c.Assert(err, jc.ErrorIsNil) 77 c.Assert(result, gc.DeepEquals, []string{"amd64", "arm"}) 78 } 79 80 func (suite *maas2EnvironSuite) TestSupportedArchitecturesError(c *gc.C) { 81 env := suite.makeEnviron(c, &fakeController{bootResourcesError: errors.New("Something terrible!")}) 82 _, err := env.SupportedArchitectures() 83 c.Assert(err, gc.ErrorMatches, "Something terrible!") 84 } 85 86 func (suite *maas2EnvironSuite) injectControllerWithSpacesAndCheck(c *gc.C, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) *maasEnviron { 87 var env *maasEnviron 88 check := func(args gomaasapi.AllocateMachineArgs) { 89 expected.AgentName = env.ecfg().maasAgentName() 90 c.Assert(args, gc.DeepEquals, expected) 91 } 92 controller := &fakeController{ 93 allocateMachineArgsCheck: check, 94 allocateMachine: &fakeMachine{ 95 systemID: "Bruce Sterling", 96 architecture: arch.HostArch(), 97 }, 98 allocateMachineMatches: gomaasapi.ConstraintMatches{ 99 Storage: map[string]gomaasapi.BlockDevice{}, 100 }, 101 spaces: spaces, 102 } 103 suite.injectController(controller) 104 suite.setupFakeTools(c) 105 env = suite.makeEnviron(c, nil) 106 return env 107 } 108 109 func (suite *maas2EnvironSuite) makeEnvironWithMachines(c *gc.C, expectedSystemIDs []string, returnSystemIDs []string) *maasEnviron { 110 var env *maasEnviron 111 checkArgs := func(args gomaasapi.MachinesArgs) { 112 c.Check(args.SystemIDs, gc.DeepEquals, expectedSystemIDs) 113 c.Check(args.AgentName, gc.Equals, env.ecfg().maasAgentName()) 114 } 115 machines := make([]gomaasapi.Machine, len(returnSystemIDs)) 116 for index, id := range returnSystemIDs { 117 machines[index] = &fakeMachine{systemID: id} 118 } 119 controller := &fakeController{ 120 machines: machines, 121 machinesArgsCheck: checkArgs, 122 } 123 env = suite.makeEnviron(c, controller) 124 return env 125 } 126 127 func (suite *maas2EnvironSuite) TestAllInstances(c *gc.C) { 128 env := suite.makeEnvironWithMachines( 129 c, []string{}, []string{"tuco", "tio", "gus"}, 130 ) 131 result, err := env.AllInstances() 132 c.Assert(err, jc.ErrorIsNil) 133 expectedMachines := set.NewStrings("tuco", "tio", "gus") 134 actualMachines := set.NewStrings() 135 for _, instance := range result { 136 actualMachines.Add(string(instance.Id())) 137 } 138 c.Assert(actualMachines, gc.DeepEquals, expectedMachines) 139 } 140 141 func (suite *maas2EnvironSuite) TestAllInstancesError(c *gc.C) { 142 controller := &fakeController{machinesError: errors.New("Something terrible!")} 143 env := suite.makeEnviron(c, controller) 144 _, err := env.AllInstances() 145 c.Assert(err, gc.ErrorMatches, "Something terrible!") 146 } 147 148 func (suite *maas2EnvironSuite) TestInstances(c *gc.C) { 149 env := suite.makeEnvironWithMachines( 150 c, []string{"jake", "bonnibel"}, []string{"jake", "bonnibel"}, 151 ) 152 result, err := env.Instances([]instance.Id{"jake", "bonnibel"}) 153 c.Assert(err, jc.ErrorIsNil) 154 expectedMachines := set.NewStrings("jake", "bonnibel") 155 actualMachines := set.NewStrings() 156 for _, machine := range result { 157 actualMachines.Add(string(machine.Id())) 158 } 159 c.Assert(actualMachines, gc.DeepEquals, expectedMachines) 160 } 161 162 func (suite *maas2EnvironSuite) TestInstancesPartialResult(c *gc.C) { 163 env := suite.makeEnvironWithMachines( 164 c, []string{"jake", "bonnibel"}, []string{"tuco", "bonnibel"}, 165 ) 166 result, err := env.Instances([]instance.Id{"jake", "bonnibel"}) 167 c.Check(err, gc.Equals, environs.ErrPartialInstances) 168 c.Assert(result, gc.HasLen, 2) 169 c.Assert(result[0], gc.IsNil) 170 c.Assert(result[1].Id(), gc.Equals, instance.Id("bonnibel")) 171 } 172 173 func (suite *maas2EnvironSuite) TestAvailabilityZones(c *gc.C) { 174 controller := &fakeController{ 175 zones: []gomaasapi.Zone{ 176 &fakeZone{name: "mossack"}, 177 &fakeZone{name: "fonseca"}, 178 }, 179 } 180 env := suite.makeEnviron(c, controller) 181 result, err := env.AvailabilityZones() 182 c.Assert(err, jc.ErrorIsNil) 183 expectedZones := set.NewStrings("mossack", "fonseca") 184 actualZones := set.NewStrings() 185 for _, zone := range result { 186 actualZones.Add(zone.Name()) 187 } 188 c.Assert(actualZones, gc.DeepEquals, expectedZones) 189 } 190 191 func (suite *maas2EnvironSuite) TestAvailabilityZonesError(c *gc.C) { 192 controller := &fakeController{ 193 zonesError: errors.New("a bad thing"), 194 } 195 env := suite.makeEnviron(c, controller) 196 _, err := env.AvailabilityZones() 197 c.Assert(err, gc.ErrorMatches, "a bad thing") 198 } 199 200 func (suite *maas2EnvironSuite) TestSpaces(c *gc.C) { 201 controller := &fakeController{ 202 spaces: []gomaasapi.Space{ 203 fakeSpace{ 204 name: "pepper", 205 id: 1234, 206 }, 207 fakeSpace{ 208 name: "freckles", 209 id: 4567, 210 subnets: []gomaasapi.Subnet{ 211 fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}, 212 fakeSubnet{id: 98, vlan: fakeVLAN{vid: 67}, cidr: "192.168.11.0/24"}, 213 }, 214 }, 215 }, 216 } 217 env := suite.makeEnviron(c, controller) 218 result, err := env.Spaces() 219 c.Assert(err, jc.ErrorIsNil) 220 c.Assert(result, gc.HasLen, 1) 221 c.Assert(result[0].Name, gc.Equals, "freckles") 222 c.Assert(result[0].ProviderId, gc.Equals, network.Id("4567")) 223 subnets := result[0].Subnets 224 c.Assert(subnets, gc.HasLen, 2) 225 c.Assert(subnets[0].ProviderId, gc.Equals, network.Id("99")) 226 c.Assert(subnets[0].VLANTag, gc.Equals, 66) 227 c.Assert(subnets[0].CIDR, gc.Equals, "192.168.10.0/24") 228 c.Assert(subnets[0].SpaceProviderId, gc.Equals, network.Id("4567")) 229 c.Assert(subnets[1].ProviderId, gc.Equals, network.Id("98")) 230 c.Assert(subnets[1].VLANTag, gc.Equals, 67) 231 c.Assert(subnets[1].CIDR, gc.Equals, "192.168.11.0/24") 232 c.Assert(subnets[1].SpaceProviderId, gc.Equals, network.Id("4567")) 233 } 234 235 func (suite *maas2EnvironSuite) TestSpacesError(c *gc.C) { 236 controller := &fakeController{ 237 spacesError: errors.New("Joe Manginiello"), 238 } 239 env := suite.makeEnviron(c, controller) 240 _, err := env.Spaces() 241 c.Assert(err, gc.ErrorMatches, "Joe Manginiello") 242 } 243 244 func collectReleaseArgs(controller *fakeController) []gomaasapi.ReleaseMachinesArgs { 245 args := []gomaasapi.ReleaseMachinesArgs{} 246 for _, call := range controller.Stub.Calls() { 247 if call.FuncName == "ReleaseMachines" { 248 args = append(args, call.Args[0].(gomaasapi.ReleaseMachinesArgs)) 249 } 250 } 251 return args 252 } 253 254 func (suite *maas2EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 255 controller := newFakeController() 256 err := suite.makeEnviron(c, controller).StopInstances() 257 c.Check(err, jc.ErrorIsNil) 258 c.Assert(collectReleaseArgs(controller), gc.HasLen, 0) 259 } 260 261 func (suite *maas2EnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 262 // Return a cannot complete indicating that test1 is in the wrong state. 263 // The release operation will still release the others and succeed. 264 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 265 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 266 c.Check(err, jc.ErrorIsNil) 267 args := collectReleaseArgs(controller) 268 c.Assert(args, gc.HasLen, 1) 269 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 270 } 271 272 func (suite *maas2EnvironSuite) TestStopInstancesIgnoresConflict(c *gc.C) { 273 // Return a cannot complete indicating that test1 is in the wrong state. 274 // The release operation will still release the others and succeed. 275 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 276 controller.SetErrors(gomaasapi.NewCannotCompleteError("test1 not allocated")) 277 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 278 c.Check(err, jc.ErrorIsNil) 279 280 args := collectReleaseArgs(controller) 281 c.Assert(args, gc.HasLen, 1) 282 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 283 } 284 285 func (suite *maas2EnvironSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) { 286 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 287 controller.SetErrors( 288 gomaasapi.NewBadRequestError("no such machine: test1"), 289 gomaasapi.NewBadRequestError("no such machine: test1"), 290 ) 291 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 292 c.Check(err, jc.ErrorIsNil) 293 args := collectReleaseArgs(controller) 294 c.Assert(args, gc.HasLen, 4) 295 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 296 c.Assert(args[1].SystemIDs, gc.DeepEquals, []string{"test1"}) 297 c.Assert(args[2].SystemIDs, gc.DeepEquals, []string{"test2"}) 298 c.Assert(args[3].SystemIDs, gc.DeepEquals, []string{"test3"}) 299 } 300 301 func (suite *maas2EnvironSuite) checkStopInstancesFails(c *gc.C, withError error) { 302 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 303 controller.SetErrors(withError) 304 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 305 c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot release nodes: %s", withError)) 306 // Only tries once. 307 c.Assert(collectReleaseArgs(controller), gc.HasLen, 1) 308 } 309 310 func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) { 311 suite.checkStopInstancesFails(c, gomaasapi.NewNoMatchError("Something else bad!")) 312 } 313 314 func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) { 315 suite.checkStopInstancesFails(c, errors.New("Something completely unexpected!")) 316 } 317 318 func (suite *maas2EnvironSuite) TestStartInstanceError(c *gc.C) { 319 suite.injectController(&fakeController{ 320 allocateMachineError: errors.New("Charles Babbage"), 321 }) 322 env := suite.makeEnviron(c, nil) 323 _, err := env.StartInstance(environs.StartInstanceParams{}) 324 c.Assert(err, gc.ErrorMatches, ".* cannot run instance: Charles Babbage") 325 } 326 327 func (suite *maas2EnvironSuite) TestStartInstance(c *gc.C) { 328 env := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{}) 329 330 params := environs.StartInstanceParams{} 331 result, err := testing.StartInstanceWithParams(env, "1", params) 332 c.Assert(err, jc.ErrorIsNil) 333 c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling")) 334 } 335 336 func (suite *maas2EnvironSuite) TestStartInstanceParams(c *gc.C) { 337 var env *maasEnviron 338 suite.injectController(&fakeController{ 339 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 340 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 341 AgentName: env.ecfg().maasAgentName(), 342 Zone: "foo", 343 MinMemory: 8192, 344 }) 345 }, 346 allocateMachine: &fakeMachine{ 347 systemID: "Bruce Sterling", 348 architecture: arch.HostArch(), 349 }, 350 allocateMachineMatches: gomaasapi.ConstraintMatches{ 351 Storage: map[string]gomaasapi.BlockDevice{}, 352 }, 353 zones: []gomaasapi.Zone{&fakeZone{name: "foo"}}, 354 }) 355 suite.setupFakeTools(c) 356 env = suite.makeEnviron(c, nil) 357 params := environs.StartInstanceParams{ 358 Placement: "zone=foo", 359 Constraints: constraints.MustParse("mem=8G"), 360 } 361 result, err := testing.StartInstanceWithParams(env, "1", params) 362 c.Assert(err, jc.ErrorIsNil) 363 c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling")) 364 } 365 366 func (suite *maas2EnvironSuite) TestAcquireNodePassedAgentName(c *gc.C) { 367 var env *maasEnviron 368 suite.injectController(&fakeController{ 369 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 370 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 371 AgentName: env.ecfg().maasAgentName()}) 372 }, 373 allocateMachine: &fakeMachine{ 374 systemID: "Bruce Sterling", 375 architecture: arch.HostArch(), 376 }, 377 }) 378 suite.setupFakeTools(c) 379 env = suite.makeEnviron(c, nil) 380 381 _, err := env.acquireNode2("", "", constraints.Value{}, nil, nil) 382 383 c.Check(err, jc.ErrorIsNil) 384 } 385 386 func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) { 387 var env *maasEnviron 388 expected := gomaasapi.AllocateMachineArgs{ 389 Tags: []string{"tag1", "tag3"}, 390 NotTags: []string{"tag2", "tag4"}, 391 } 392 env = suite.injectControllerWithSpacesAndCheck(c, nil, expected) 393 _, err := env.acquireNode2( 394 "", "", 395 constraints.Value{Tags: stringslicep("tag1", "^tag2", "tag3", "^tag4")}, 396 nil, nil, 397 ) 398 c.Check(err, jc.ErrorIsNil) 399 } 400 401 func getFourSpaces() []gomaasapi.Space { 402 return []gomaasapi.Space{ 403 fakeSpace{ 404 name: "space-1", 405 subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}}, 406 id: 5, 407 }, 408 fakeSpace{ 409 name: "space-2", 410 subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}}, 411 id: 6, 412 }, 413 fakeSpace{ 414 name: "space-3", 415 subnets: []gomaasapi.Subnet{fakeSubnet{id: 101, vlan: fakeVLAN{vid: 66}, cidr: "192.168.12.0/24"}}, 416 id: 7, 417 }, 418 fakeSpace{ 419 name: "space-4", 420 subnets: []gomaasapi.Subnet{fakeSubnet{id: 102, vlan: fakeVLAN{vid: 66}, cidr: "192.168.13.0/24"}}, 421 id: 8, 422 }, 423 } 424 425 } 426 427 func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeSpaces(c *gc.C) { 428 expected := gomaasapi.AllocateMachineArgs{ 429 NotSpace: []string{"6", "8"}, 430 Interfaces: []gomaasapi.InterfaceSpec{ 431 {Label: "0", Space: "5"}, 432 {Label: "1", Space: "7"}, 433 }, 434 } 435 env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), expected) 436 437 _, err := env.acquireNode2( 438 "", "", 439 constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")}, 440 nil, nil, 441 ) 442 c.Check(err, jc.ErrorIsNil) 443 } 444 445 func (suite *maas2EnvironSuite) TestAcquireNodeDisambiguatesNamedLabelsFromIndexedUpToALimit(c *gc.C) { 446 env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), gomaasapi.AllocateMachineArgs{}) 447 var shortLimit uint = 0 448 suite.PatchValue(&numericLabelLimit, shortLimit) 449 450 _, err := env.acquireNode2( 451 "", "", 452 constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")}, 453 []interfaceBinding{{"0", "first-clash"}, {"1", "final-clash"}}, 454 nil, 455 ) 456 c.Assert(err, gc.ErrorMatches, `too many conflicting numeric labels, giving up.`) 457 } 458 459 func (suite *maas2EnvironSuite) TestAcquireNodeStorage(c *gc.C) { 460 var env *maasEnviron 461 var getStorage func() []gomaasapi.StorageSpec 462 suite.injectController(&fakeController{ 463 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 464 c.Assert(args, jc.DeepEquals, gomaasapi.AllocateMachineArgs{ 465 AgentName: env.ecfg().maasAgentName(), 466 Storage: getStorage(), 467 }) 468 }, 469 allocateMachine: &fakeMachine{ 470 systemID: "Bruce Sterling", 471 architecture: arch.HostArch(), 472 }, 473 }) 474 suite.setupFakeTools(c) 475 for i, test := range []struct { 476 volumes []volumeInfo 477 expected []gomaasapi.StorageSpec 478 }{{ 479 volumes: nil, 480 expected: []gomaasapi.StorageSpec{}, 481 }, { 482 volumes: []volumeInfo{{"volume-1", 1234, nil}}, 483 expected: []gomaasapi.StorageSpec{{"volume-1", 1234, nil}}, 484 }, { 485 volumes: []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}}, 486 expected: []gomaasapi.StorageSpec{{"", 1234, []string{"tag1", "tag2"}}}, 487 }, { 488 volumes: []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}}, 489 expected: []gomaasapi.StorageSpec{{"volume-1", 1234, []string{"tag1", "tag2"}}}, 490 }, { 491 volumes: []volumeInfo{ 492 {"volume-1", 1234, []string{"tag1", "tag2"}}, 493 {"volume-2", 4567, []string{"tag1", "tag3"}}, 494 }, 495 expected: []gomaasapi.StorageSpec{ 496 {"volume-1", 1234, []string{"tag1", "tag2"}}, 497 {"volume-2", 4567, []string{"tag1", "tag3"}}, 498 }, 499 }} { 500 c.Logf("test #%d: volumes=%v", i, test.volumes) 501 getStorage = func() []gomaasapi.StorageSpec { 502 return test.expected 503 } 504 env = suite.makeEnviron(c, nil) 505 _, err := env.acquireNode2("", "", constraints.Value{}, nil, test.volumes) 506 c.Check(err, jc.ErrorIsNil) 507 } 508 } 509 510 func (suite *maas2EnvironSuite) TestAcquireNodeInterfaces(c *gc.C) { 511 var env *maasEnviron 512 var getNegatives func() []string 513 var getPositives func() []gomaasapi.InterfaceSpec 514 suite.injectController(&fakeController{ 515 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 516 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 517 AgentName: env.ecfg().maasAgentName(), 518 Interfaces: getPositives(), 519 NotSpace: getNegatives(), 520 }) 521 }, 522 allocateMachine: &fakeMachine{ 523 systemID: "Bruce Sterling", 524 architecture: arch.HostArch(), 525 }, 526 spaces: getTwoSpaces(), 527 }) 528 suite.setupFakeTools(c) 529 // Add some constraints, including spaces to verify specified bindings 530 // always override any spaces constraints. 531 cons := constraints.Value{ 532 Spaces: stringslicep("foo", "^bar"), 533 } 534 // In the tests below "space:5" means foo, "space:6" means bar. 535 for i, test := range []struct { 536 interfaces []interfaceBinding 537 expectedPositives []gomaasapi.InterfaceSpec 538 expectedNegatives []string 539 expectedError string 540 }{{ // without specified bindings, spaces constraints are used instead. 541 interfaces: nil, 542 expectedPositives: []gomaasapi.InterfaceSpec{{"0", "2"}}, 543 expectedNegatives: []string{"3"}, 544 expectedError: "", 545 }, { 546 interfaces: []interfaceBinding{{"name-1", "space-1"}}, 547 expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "space-1"}, {"0", "2"}}, 548 expectedNegatives: []string{"3"}, 549 }, { 550 interfaces: []interfaceBinding{ 551 {"name-1", "7"}, 552 {"name-2", "8"}, 553 {"name-3", "9"}, 554 }, 555 expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "7"}, {"name-2", "8"}, {"name-3", "9"}, {"0", "2"}}, 556 expectedNegatives: []string{"3"}, 557 }, { 558 interfaces: []interfaceBinding{{"", "anything"}}, 559 expectedError: "interface bindings cannot have empty names", 560 }, { 561 interfaces: []interfaceBinding{{"shared-db", "3"}}, 562 expectedError: `negative space "bar" from constraints clashes with interface bindings`, 563 }, { 564 interfaces: []interfaceBinding{ 565 {"shared-db", "1"}, 566 {"db", "1"}, 567 }, 568 expectedPositives: []gomaasapi.InterfaceSpec{{"shared-db", "1"}, {"db", "1"}, {"0", "2"}}, 569 expectedNegatives: []string{"3"}, 570 }, { 571 interfaces: []interfaceBinding{{"", ""}}, 572 expectedError: "interface bindings cannot have empty names", 573 }, { 574 interfaces: []interfaceBinding{ 575 {"valid", "ok"}, 576 {"", "valid-but-ignored-space"}, 577 {"valid-name-empty-space", ""}, 578 {"", ""}, 579 }, 580 expectedError: "interface bindings cannot have empty names", 581 }, { 582 interfaces: []interfaceBinding{{"foo", ""}}, 583 expectedError: `invalid interface binding "foo": space provider ID is required`, 584 }, { 585 interfaces: []interfaceBinding{ 586 {"bar", ""}, 587 {"valid", "ok"}, 588 {"", "valid-but-ignored-space"}, 589 {"", ""}, 590 }, 591 expectedError: `invalid interface binding "bar": space provider ID is required`, 592 }, { 593 interfaces: []interfaceBinding{ 594 {"dup-name", "1"}, 595 {"dup-name", "2"}, 596 }, 597 expectedError: `duplicated interface binding "dup-name"`, 598 }, { 599 interfaces: []interfaceBinding{ 600 {"valid-1", "0"}, 601 {"dup-name", "1"}, 602 {"dup-name", "2"}, 603 {"valid-2", "3"}, 604 }, 605 expectedError: `duplicated interface binding "dup-name"`, 606 }} { 607 c.Logf("test #%d: interfaces=%v", i, test.interfaces) 608 env = suite.makeEnviron(c, nil) 609 getNegatives = func() []string { 610 return test.expectedNegatives 611 } 612 getPositives = func() []gomaasapi.InterfaceSpec { 613 return test.expectedPositives 614 } 615 _, err := env.acquireNode2("", "", cons, test.interfaces, nil) 616 if test.expectedError != "" { 617 c.Check(err, gc.ErrorMatches, test.expectedError) 618 c.Check(err, jc.Satisfies, errors.IsNotValid) 619 continue 620 } 621 c.Check(err, jc.ErrorIsNil) 622 } 623 } 624 625 func getTwoSpaces() []gomaasapi.Space { 626 return []gomaasapi.Space{ 627 fakeSpace{ 628 name: "foo", 629 subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}}, 630 id: 2, 631 }, 632 fakeSpace{ 633 name: "bar", 634 subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}}, 635 id: 3, 636 }, 637 } 638 } 639 640 func (suite *maas2EnvironSuite) TestAcquireNodeConvertsSpaceNames(c *gc.C) { 641 expected := gomaasapi.AllocateMachineArgs{ 642 NotSpace: []string{"3"}, 643 Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}}, 644 } 645 env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected) 646 cons := constraints.Value{ 647 Spaces: stringslicep("foo", "^bar"), 648 } 649 _, err := env.acquireNode2("", "", cons, nil, nil) 650 c.Assert(err, jc.ErrorIsNil) 651 } 652 653 func (suite *maas2EnvironSuite) TestAcquireNodeTranslatesSpaceNames(c *gc.C) { 654 expected := gomaasapi.AllocateMachineArgs{ 655 NotSpace: []string{"3"}, 656 Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}}, 657 } 658 env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected) 659 cons := constraints.Value{ 660 Spaces: stringslicep("foo-1", "^bar-3"), 661 } 662 _, err := env.acquireNode2("", "", cons, nil, nil) 663 c.Assert(err, jc.ErrorIsNil) 664 } 665 666 func (suite *maas2EnvironSuite) TestAcquireNodeUnrecognisedSpace(c *gc.C) { 667 suite.injectController(&fakeController{}) 668 env := suite.makeEnviron(c, nil) 669 cons := constraints.Value{ 670 Spaces: stringslicep("baz"), 671 } 672 _, err := env.acquireNode2("", "", cons, nil, nil) 673 c.Assert(err, gc.ErrorMatches, `unrecognised space in constraint "baz"`) 674 } 675 676 func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentError(c *gc.C) { 677 machine := &fakeMachine{ 678 systemID: "Bruce Sterling", 679 architecture: arch.HostArch(), 680 } 681 controller := newFakeController() 682 controller.allocateMachine = machine 683 controller.allocateMachineMatches = gomaasapi.ConstraintMatches{ 684 Storage: map[string]gomaasapi.BlockDevice{}, 685 } 686 controller.machines = []gomaasapi.Machine{machine} 687 suite.injectController(controller) 688 suite.setupFakeTools(c) 689 env := suite.makeEnviron(c, nil) 690 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 691 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*") 692 } 693 694 func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentSucceeds(c *gc.C) { 695 machine := &fakeMachine{ 696 systemID: "Bruce Sterling", 697 architecture: arch.HostArch(), 698 statusName: "Deployed", 699 } 700 701 controller := newFakeController() 702 controller.allocateMachine = machine 703 controller.allocateMachineMatches = gomaasapi.ConstraintMatches{ 704 Storage: map[string]gomaasapi.BlockDevice{}, 705 } 706 controller.machines = []gomaasapi.Machine{machine} 707 suite.injectController(controller) 708 suite.setupFakeTools(c) 709 env := suite.makeEnviron(c, nil) 710 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 711 c.Assert(err, jc.ErrorIsNil) 712 } 713 714 func (suite *maas2EnvironSuite) TestSubnetsNoFilters(c *gc.C) { 715 suite.injectController(&fakeController{ 716 spaces: getFourSpaces(), 717 }) 718 env := suite.makeEnviron(c, nil) 719 subnets, err := env.Subnets("", nil) 720 c.Assert(err, jc.ErrorIsNil) 721 expected := []network.SubnetInfo{ 722 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 723 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"}, 724 {CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 66, SpaceProviderId: "7"}, 725 {CIDR: "192.168.13.0/24", ProviderId: "102", VLANTag: 66, SpaceProviderId: "8"}, 726 } 727 c.Assert(subnets, jc.DeepEquals, expected) 728 } 729 730 func (suite *maas2EnvironSuite) TestSubnetsNoFiltersError(c *gc.C) { 731 suite.injectController(&fakeController{ 732 spacesError: errors.New("bang"), 733 }) 734 env := suite.makeEnviron(c, nil) 735 _, err := env.Subnets("", nil) 736 c.Assert(err, gc.ErrorMatches, "bang") 737 } 738 739 func (suite *maas2EnvironSuite) TestSubnetsSubnetIds(c *gc.C) { 740 suite.injectController(&fakeController{ 741 spaces: getFourSpaces(), 742 }) 743 env := suite.makeEnviron(c, nil) 744 subnets, err := env.Subnets("", []network.Id{"99", "100"}) 745 c.Assert(err, jc.ErrorIsNil) 746 expected := []network.SubnetInfo{ 747 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 748 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"}, 749 } 750 c.Assert(subnets, jc.DeepEquals, expected) 751 } 752 753 func (suite *maas2EnvironSuite) TestSubnetsSubnetIdsMissing(c *gc.C) { 754 suite.injectController(&fakeController{ 755 spaces: getFourSpaces(), 756 }) 757 env := suite.makeEnviron(c, nil) 758 _, err := env.Subnets("", []network.Id{"99", "missing"}) 759 msg := "failed to find the following subnets: missing" 760 c.Assert(err, gc.ErrorMatches, msg) 761 } 762 763 func (suite *maas2EnvironSuite) TestSubnetsInstIdNotFound(c *gc.C) { 764 suite.injectController(&fakeController{}) 765 env := suite.makeEnviron(c, nil) 766 _, err := env.Subnets("foo", nil) 767 c.Assert(err, jc.Satisfies, errors.IsNotFound) 768 } 769 770 func (suite *maas2EnvironSuite) TestSubnetsInstId(c *gc.C) { 771 interfaces := []gomaasapi.Interface{ 772 &fakeInterface{ 773 links: []gomaasapi.Link{ 774 &fakeLink{subnet: fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24", space: "space-1"}}, 775 &fakeLink{subnet: fakeSubnet{id: 100, vlan: fakeVLAN{vid: 0}, cidr: "192.168.11.0/24", space: "space-2"}}, 776 }, 777 }, 778 &fakeInterface{ 779 links: []gomaasapi.Link{ 780 &fakeLink{subnet: fakeSubnet{id: 101, vlan: fakeVLAN{vid: 2}, cidr: "192.168.12.0/24", space: "space-3"}}, 781 }, 782 }, 783 } 784 machine := &fakeMachine{ 785 systemID: "William Gibson", 786 interfaceSet: interfaces, 787 } 788 machine2 := &fakeMachine{systemID: "Bruce Sterling"} 789 suite.injectController(&fakeController{ 790 machines: []gomaasapi.Machine{machine, machine2}, 791 spaces: getFourSpaces(), 792 }) 793 env := suite.makeEnviron(c, nil) 794 subnets, err := env.Subnets("William Gibson", nil) 795 c.Assert(err, jc.ErrorIsNil) 796 expected := []network.SubnetInfo{ 797 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 798 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 0, SpaceProviderId: "6"}, 799 {CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 2, SpaceProviderId: "7"}, 800 } 801 c.Assert(subnets, jc.DeepEquals, expected) 802 } 803 804 func (suite *maas2EnvironSuite) TestStartInstanceNetworkInterfaces(c *gc.C) { 805 vlan0 := fakeVLAN{ 806 id: 5001, 807 vid: 0, 808 mtu: 1500, 809 } 810 811 vlan50 := fakeVLAN{ 812 id: 5004, 813 vid: 50, 814 mtu: 1500, 815 } 816 817 subnetPXE := fakeSubnet{ 818 id: 3, 819 space: "default", 820 vlan: vlan0, 821 gateway: "10.20.19.2", 822 cidr: "10.20.19.0/24", 823 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 824 } 825 826 exampleInterfaces := []gomaasapi.Interface{ 827 &fakeInterface{ 828 id: 91, 829 name: "eth0", 830 type_: "physical", 831 enabled: true, 832 macAddress: "52:54:00:70:9b:fe", 833 vlan: vlan0, 834 links: []gomaasapi.Link{ 835 &fakeLink{ 836 id: 436, 837 subnet: &subnetPXE, 838 ipAddress: "10.20.19.103", 839 mode: "static", 840 }, 841 &fakeLink{ 842 id: 437, 843 subnet: &subnetPXE, 844 ipAddress: "10.20.19.104", 845 mode: "static", 846 }, 847 }, 848 parents: []string{}, 849 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 850 }, 851 &fakeInterface{ 852 id: 150, 853 name: "eth0.50", 854 type_: "vlan", 855 enabled: true, 856 macAddress: "52:54:00:70:9b:fe", 857 vlan: vlan50, 858 links: []gomaasapi.Link{ 859 &fakeLink{ 860 id: 517, 861 subnet: &fakeSubnet{ 862 id: 5, 863 space: "admin", 864 vlan: vlan50, 865 gateway: "10.50.19.2", 866 cidr: "10.50.19.0/24", 867 dnsServers: []string{}, 868 }, 869 ipAddress: "10.50.19.103", 870 mode: "static", 871 }, 872 }, 873 parents: []string{"eth0"}, 874 children: []string{}, 875 }, 876 } 877 var env *maasEnviron 878 controller := &fakeController{ 879 allocateMachine: &fakeMachine{ 880 systemID: "Bruce Sterling", 881 architecture: arch.HostArch(), 882 interfaceSet: exampleInterfaces, 883 }, 884 allocateMachineMatches: gomaasapi.ConstraintMatches{ 885 Storage: map[string]gomaasapi.BlockDevice{}, 886 }, 887 } 888 suite.injectController(controller) 889 suite.setupFakeTools(c) 890 env = suite.makeEnviron(c, nil) 891 892 params := environs.StartInstanceParams{} 893 result, err := testing.StartInstanceWithParams(env, "1", params) 894 c.Assert(err, jc.ErrorIsNil) 895 expected := []network.InterfaceInfo{{ 896 DeviceIndex: 0, 897 MACAddress: "52:54:00:70:9b:fe", 898 CIDR: "10.20.19.0/24", 899 ProviderId: "91", 900 ProviderSubnetId: "3", 901 AvailabilityZones: nil, 902 VLANTag: 0, 903 ProviderVLANId: "5001", 904 ProviderAddressId: "436", 905 InterfaceName: "eth0", 906 InterfaceType: "ethernet", 907 Disabled: false, 908 NoAutoStart: false, 909 ConfigType: "static", 910 Address: network.NewAddressOnSpace("default", "10.20.19.103"), 911 DNSServers: network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"), 912 DNSSearchDomains: nil, 913 MTU: 1500, 914 GatewayAddress: network.NewAddressOnSpace("default", "10.20.19.2"), 915 }, { 916 DeviceIndex: 0, 917 MACAddress: "52:54:00:70:9b:fe", 918 CIDR: "10.20.19.0/24", 919 ProviderId: "91", 920 ProviderSubnetId: "3", 921 AvailabilityZones: nil, 922 VLANTag: 0, 923 ProviderVLANId: "5001", 924 ProviderAddressId: "437", 925 InterfaceName: "eth0", 926 InterfaceType: "ethernet", 927 Disabled: false, 928 NoAutoStart: false, 929 ConfigType: "static", 930 Address: network.NewAddressOnSpace("default", "10.20.19.104"), 931 DNSServers: network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"), 932 DNSSearchDomains: nil, 933 MTU: 1500, 934 GatewayAddress: network.NewAddressOnSpace("default", "10.20.19.2"), 935 }, { 936 DeviceIndex: 1, 937 MACAddress: "52:54:00:70:9b:fe", 938 CIDR: "10.50.19.0/24", 939 ProviderId: "150", 940 ProviderSubnetId: "5", 941 AvailabilityZones: nil, 942 VLANTag: 50, 943 ProviderVLANId: "5004", 944 ProviderAddressId: "517", 945 InterfaceName: "eth0.50", 946 ParentInterfaceName: "eth0", 947 InterfaceType: "802.1q", 948 Disabled: false, 949 NoAutoStart: false, 950 ConfigType: "static", 951 Address: network.NewAddressOnSpace("admin", "10.50.19.103"), 952 DNSServers: nil, 953 DNSSearchDomains: nil, 954 MTU: 1500, 955 GatewayAddress: network.NewAddressOnSpace("admin", "10.50.19.2"), 956 }, 957 } 958 c.Assert(result.NetworkInfo, jc.DeepEquals, expected) 959 }