github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "bytes" 8 "fmt" 9 "net/http" 10 11 "github.com/juju/errors" 12 "github.com/juju/gomaasapi" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils/arch" 16 "github.com/juju/utils/set" 17 gc "gopkg.in/check.v1" 18 goyaml "gopkg.in/yaml.v2" 19 20 "github.com/juju/juju/cloudconfig/cloudinit" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/environs" 23 "github.com/juju/juju/environs/bootstrap" 24 "github.com/juju/juju/environs/config" 25 envjujutesting "github.com/juju/juju/environs/testing" 26 envtools "github.com/juju/juju/environs/tools" 27 "github.com/juju/juju/instance" 28 jujutesting "github.com/juju/juju/juju/testing" 29 "github.com/juju/juju/network" 30 "github.com/juju/juju/provider/common" 31 corejujutesting "github.com/juju/juju/testing" 32 jujuversion "github.com/juju/juju/version" 33 ) 34 35 type maas2EnvironSuite struct { 36 maas2Suite 37 } 38 39 var _ = gc.Suite(&maas2EnvironSuite{}) 40 41 func (suite *maas2EnvironSuite) getEnvWithServer(c *gc.C) (*maasEnviron, error) { 42 testServer := gomaasapi.NewSimpleServer() 43 testServer.AddGetResponse("/api/2.0/version/", http.StatusOK, maas2VersionResponse) 44 testServer.AddGetResponse("/api/2.0/users/?op=whoami", http.StatusOK, "{}") 45 // Weirdly, rather than returning a 404 when the version is 46 // unknown, MAAS2 returns some HTML (the login page). 47 testServer.AddGetResponse("/api/1.0/version/", http.StatusOK, "<html></html>") 48 testServer.Start() 49 suite.AddCleanup(func(*gc.C) { testServer.Close() }) 50 testAttrs := corejujutesting.Attrs{} 51 for k, v := range maasEnvAttrs { 52 testAttrs[k] = v 53 } 54 testAttrs["maas-server"] = testServer.Server.URL 55 attrs := corejujutesting.FakeConfig().Merge(testAttrs) 56 cfg, err := config.New(config.NoDefaults, attrs) 57 c.Assert(err, jc.ErrorIsNil) 58 return NewEnviron(cfg) 59 } 60 61 func (suite *maas2EnvironSuite) TestNewEnvironWithController(c *gc.C) { 62 env, err := suite.getEnvWithServer(c) 63 c.Assert(err, jc.ErrorIsNil) 64 c.Assert(env, gc.NotNil) 65 } 66 67 func (suite *maas2EnvironSuite) TestSupportedArchitectures(c *gc.C) { 68 controller := &fakeController{ 69 bootResources: []gomaasapi.BootResource{ 70 &fakeBootResource{name: "wily", architecture: "amd64/blah"}, 71 &fakeBootResource{name: "wily", architecture: "amd64/something"}, 72 &fakeBootResource{name: "xenial", architecture: "arm/somethingelse"}, 73 }, 74 } 75 env := suite.makeEnviron(c, controller) 76 result, err := env.SupportedArchitectures() 77 c.Assert(err, jc.ErrorIsNil) 78 c.Assert(result, gc.DeepEquals, []string{"amd64", "arm"}) 79 } 80 81 func (suite *maas2EnvironSuite) TestSupportedArchitecturesError(c *gc.C) { 82 env := suite.makeEnviron(c, &fakeController{bootResourcesError: errors.New("Something terrible!")}) 83 _, err := env.SupportedArchitectures() 84 c.Assert(err, gc.ErrorMatches, "Something terrible!") 85 } 86 87 func (suite *maas2EnvironSuite) injectControllerWithSpacesAndCheck(c *gc.C, spaces []gomaasapi.Space, expected gomaasapi.AllocateMachineArgs) *maasEnviron { 88 var env *maasEnviron 89 check := func(args gomaasapi.AllocateMachineArgs) { 90 expected.AgentName = env.ecfg().maasAgentName() 91 c.Assert(args, gc.DeepEquals, expected) 92 } 93 controller := &fakeController{ 94 allocateMachineArgsCheck: check, 95 allocateMachine: newFakeMachine("Bruce Sterling", arch.HostArch(), ""), 96 allocateMachineMatches: gomaasapi.ConstraintMatches{ 97 Storage: map[string][]gomaasapi.BlockDevice{}, 98 }, 99 spaces: spaces, 100 } 101 suite.injectController(controller) 102 suite.setupFakeTools(c) 103 env = suite.makeEnviron(c, nil) 104 return env 105 } 106 107 func (suite *maas2EnvironSuite) makeEnvironWithMachines(c *gc.C, expectedSystemIDs []string, returnSystemIDs []string) *maasEnviron { 108 var env *maasEnviron 109 checkArgs := func(args gomaasapi.MachinesArgs) { 110 c.Check(args.SystemIDs, gc.DeepEquals, expectedSystemIDs) 111 c.Check(args.AgentName, gc.Equals, env.ecfg().maasAgentName()) 112 } 113 machines := make([]gomaasapi.Machine, len(returnSystemIDs)) 114 for index, id := range returnSystemIDs { 115 machines[index] = &fakeMachine{systemID: id} 116 } 117 controller := &fakeController{ 118 machines: machines, 119 machinesArgsCheck: checkArgs, 120 } 121 env = suite.makeEnviron(c, controller) 122 return env 123 } 124 125 func (suite *maas2EnvironSuite) TestAllInstances(c *gc.C) { 126 env := suite.makeEnvironWithMachines( 127 c, []string{}, []string{"tuco", "tio", "gus"}, 128 ) 129 result, err := env.AllInstances() 130 c.Assert(err, jc.ErrorIsNil) 131 expectedMachines := set.NewStrings("tuco", "tio", "gus") 132 actualMachines := set.NewStrings() 133 for _, instance := range result { 134 actualMachines.Add(string(instance.Id())) 135 } 136 c.Assert(actualMachines, gc.DeepEquals, expectedMachines) 137 } 138 139 func (suite *maas2EnvironSuite) TestAllInstancesError(c *gc.C) { 140 controller := &fakeController{machinesError: errors.New("Something terrible!")} 141 env := suite.makeEnviron(c, controller) 142 _, err := env.AllInstances() 143 c.Assert(err, gc.ErrorMatches, "Something terrible!") 144 } 145 146 func (suite *maas2EnvironSuite) TestInstances(c *gc.C) { 147 env := suite.makeEnvironWithMachines( 148 c, []string{"jake", "bonnibel"}, []string{"jake", "bonnibel"}, 149 ) 150 result, err := env.Instances([]instance.Id{"jake", "bonnibel"}) 151 c.Assert(err, jc.ErrorIsNil) 152 expectedMachines := set.NewStrings("jake", "bonnibel") 153 actualMachines := set.NewStrings() 154 for _, machine := range result { 155 actualMachines.Add(string(machine.Id())) 156 } 157 c.Assert(actualMachines, gc.DeepEquals, expectedMachines) 158 } 159 160 func (suite *maas2EnvironSuite) TestInstancesPartialResult(c *gc.C) { 161 env := suite.makeEnvironWithMachines( 162 c, []string{"jake", "bonnibel"}, []string{"tuco", "bonnibel"}, 163 ) 164 result, err := env.Instances([]instance.Id{"jake", "bonnibel"}) 165 c.Check(err, gc.Equals, environs.ErrPartialInstances) 166 c.Assert(result, gc.HasLen, 2) 167 c.Assert(result[0], gc.IsNil) 168 c.Assert(result[1].Id(), gc.Equals, instance.Id("bonnibel")) 169 } 170 171 func (suite *maas2EnvironSuite) TestAvailabilityZones(c *gc.C) { 172 controller := &fakeController{ 173 zones: []gomaasapi.Zone{ 174 &fakeZone{name: "mossack"}, 175 &fakeZone{name: "fonseca"}, 176 }, 177 } 178 env := suite.makeEnviron(c, controller) 179 result, err := env.AvailabilityZones() 180 c.Assert(err, jc.ErrorIsNil) 181 expectedZones := set.NewStrings("mossack", "fonseca") 182 actualZones := set.NewStrings() 183 for _, zone := range result { 184 actualZones.Add(zone.Name()) 185 } 186 c.Assert(actualZones, gc.DeepEquals, expectedZones) 187 } 188 189 func (suite *maas2EnvironSuite) TestAvailabilityZonesError(c *gc.C) { 190 controller := &fakeController{ 191 zonesError: errors.New("a bad thing"), 192 } 193 env := suite.makeEnviron(c, controller) 194 _, err := env.AvailabilityZones() 195 c.Assert(err, gc.ErrorMatches, "a bad thing") 196 } 197 198 func (suite *maas2EnvironSuite) TestSpaces(c *gc.C) { 199 controller := &fakeController{ 200 spaces: []gomaasapi.Space{ 201 fakeSpace{ 202 name: "pepper", 203 id: 1234, 204 }, 205 fakeSpace{ 206 name: "freckles", 207 id: 4567, 208 subnets: []gomaasapi.Subnet{ 209 fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}, 210 fakeSubnet{id: 98, vlan: fakeVLAN{vid: 67}, cidr: "192.168.11.0/24"}, 211 }, 212 }, 213 }, 214 } 215 env := suite.makeEnviron(c, controller) 216 result, err := env.Spaces() 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(result, gc.HasLen, 1) 219 c.Assert(result[0].Name, gc.Equals, "freckles") 220 c.Assert(result[0].ProviderId, gc.Equals, network.Id("4567")) 221 subnets := result[0].Subnets 222 c.Assert(subnets, gc.HasLen, 2) 223 c.Assert(subnets[0].ProviderId, gc.Equals, network.Id("99")) 224 c.Assert(subnets[0].VLANTag, gc.Equals, 66) 225 c.Assert(subnets[0].CIDR, gc.Equals, "192.168.10.0/24") 226 c.Assert(subnets[0].SpaceProviderId, gc.Equals, network.Id("4567")) 227 c.Assert(subnets[1].ProviderId, gc.Equals, network.Id("98")) 228 c.Assert(subnets[1].VLANTag, gc.Equals, 67) 229 c.Assert(subnets[1].CIDR, gc.Equals, "192.168.11.0/24") 230 c.Assert(subnets[1].SpaceProviderId, gc.Equals, network.Id("4567")) 231 } 232 233 func (suite *maas2EnvironSuite) TestSpacesError(c *gc.C) { 234 controller := &fakeController{ 235 spacesError: errors.New("Joe Manginiello"), 236 } 237 env := suite.makeEnviron(c, controller) 238 _, err := env.Spaces() 239 c.Assert(err, gc.ErrorMatches, "Joe Manginiello") 240 } 241 242 func collectReleaseArgs(controller *fakeController) []gomaasapi.ReleaseMachinesArgs { 243 args := []gomaasapi.ReleaseMachinesArgs{} 244 for _, call := range controller.Stub.Calls() { 245 if call.FuncName == "ReleaseMachines" { 246 args = append(args, call.Args[0].(gomaasapi.ReleaseMachinesArgs)) 247 } 248 } 249 return args 250 } 251 252 func (suite *maas2EnvironSuite) TestStopInstancesReturnsIfParameterEmpty(c *gc.C) { 253 controller := newFakeController() 254 err := suite.makeEnviron(c, controller).StopInstances() 255 c.Check(err, jc.ErrorIsNil) 256 c.Assert(collectReleaseArgs(controller), gc.HasLen, 0) 257 } 258 259 func (suite *maas2EnvironSuite) TestStopInstancesStopsAndReleasesInstances(c *gc.C) { 260 // Return a cannot complete indicating that test1 is in the wrong state. 261 // The release operation will still release the others and succeed. 262 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 263 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 264 c.Check(err, jc.ErrorIsNil) 265 args := collectReleaseArgs(controller) 266 c.Assert(args, gc.HasLen, 1) 267 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 268 } 269 270 func (suite *maas2EnvironSuite) TestStopInstancesIgnoresConflict(c *gc.C) { 271 // Return a cannot complete indicating that test1 is in the wrong state. 272 // The release operation will still release the others and succeed. 273 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 274 controller.SetErrors(gomaasapi.NewCannotCompleteError("test1 not allocated")) 275 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 276 c.Check(err, jc.ErrorIsNil) 277 278 args := collectReleaseArgs(controller) 279 c.Assert(args, gc.HasLen, 1) 280 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 281 } 282 283 func (suite *maas2EnvironSuite) TestStopInstancesIgnoresMissingNodeAndRecurses(c *gc.C) { 284 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 285 controller.SetErrors( 286 gomaasapi.NewBadRequestError("no such machine: test1"), 287 gomaasapi.NewBadRequestError("no such machine: test1"), 288 ) 289 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 290 c.Check(err, jc.ErrorIsNil) 291 args := collectReleaseArgs(controller) 292 c.Assert(args, gc.HasLen, 4) 293 c.Assert(args[0].SystemIDs, gc.DeepEquals, []string{"test1", "test2", "test3"}) 294 c.Assert(args[1].SystemIDs, gc.DeepEquals, []string{"test1"}) 295 c.Assert(args[2].SystemIDs, gc.DeepEquals, []string{"test2"}) 296 c.Assert(args[3].SystemIDs, gc.DeepEquals, []string{"test3"}) 297 } 298 299 func (suite *maas2EnvironSuite) checkStopInstancesFails(c *gc.C, withError error) { 300 controller := newFakeControllerWithFiles(&fakeFile{name: "agent-prefix-provider-state"}) 301 controller.SetErrors(withError) 302 err := suite.makeEnviron(c, controller).StopInstances("test1", "test2", "test3") 303 c.Check(err, gc.ErrorMatches, fmt.Sprintf("cannot release nodes: %s", withError)) 304 // Only tries once. 305 c.Assert(collectReleaseArgs(controller), gc.HasLen, 1) 306 } 307 308 func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedMAASError(c *gc.C) { 309 suite.checkStopInstancesFails(c, gomaasapi.NewNoMatchError("Something else bad!")) 310 } 311 312 func (suite *maas2EnvironSuite) TestStopInstancesReturnsUnexpectedError(c *gc.C) { 313 suite.checkStopInstancesFails(c, errors.New("Something completely unexpected!")) 314 } 315 316 func (suite *maas2EnvironSuite) TestStartInstanceError(c *gc.C) { 317 suite.injectController(&fakeController{ 318 allocateMachineError: errors.New("Charles Babbage"), 319 }) 320 env := suite.makeEnviron(c, nil) 321 _, err := env.StartInstance(environs.StartInstanceParams{}) 322 c.Assert(err, gc.ErrorMatches, ".* cannot run instance: Charles Babbage") 323 } 324 325 func (suite *maas2EnvironSuite) TestStartInstance(c *gc.C) { 326 env := suite.injectControllerWithSpacesAndCheck(c, nil, gomaasapi.AllocateMachineArgs{}) 327 328 params := environs.StartInstanceParams{} 329 result, err := jujutesting.StartInstanceWithParams(env, "1", params) 330 c.Assert(err, jc.ErrorIsNil) 331 c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling")) 332 } 333 334 func (suite *maas2EnvironSuite) TestStartInstanceParams(c *gc.C) { 335 var env *maasEnviron 336 suite.injectController(&fakeController{ 337 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 338 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 339 AgentName: env.ecfg().maasAgentName(), 340 Zone: "foo", 341 MinMemory: 8192, 342 }) 343 }, 344 allocateMachine: newFakeMachine("Bruce Sterling", arch.HostArch(), ""), 345 allocateMachineMatches: gomaasapi.ConstraintMatches{ 346 Storage: map[string][]gomaasapi.BlockDevice{}, 347 }, 348 zones: []gomaasapi.Zone{&fakeZone{name: "foo"}}, 349 }) 350 suite.setupFakeTools(c) 351 env = suite.makeEnviron(c, nil) 352 params := environs.StartInstanceParams{ 353 Placement: "zone=foo", 354 Constraints: constraints.MustParse("mem=8G"), 355 } 356 result, err := jujutesting.StartInstanceWithParams(env, "1", params) 357 c.Assert(err, jc.ErrorIsNil) 358 c.Assert(result.Instance.Id(), gc.Equals, instance.Id("Bruce Sterling")) 359 } 360 361 func (suite *maas2EnvironSuite) TestAcquireNodePassedAgentName(c *gc.C) { 362 var env *maasEnviron 363 suite.injectController(&fakeController{ 364 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 365 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 366 AgentName: env.ecfg().maasAgentName()}) 367 }, 368 allocateMachine: &fakeMachine{ 369 systemID: "Bruce Sterling", 370 architecture: arch.HostArch(), 371 }, 372 }) 373 suite.setupFakeTools(c) 374 env = suite.makeEnviron(c, nil) 375 376 _, err := env.acquireNode2("", "", constraints.Value{}, nil, nil) 377 378 c.Check(err, jc.ErrorIsNil) 379 } 380 381 func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeTags(c *gc.C) { 382 var env *maasEnviron 383 expected := gomaasapi.AllocateMachineArgs{ 384 Tags: []string{"tag1", "tag3"}, 385 NotTags: []string{"tag2", "tag4"}, 386 } 387 env = suite.injectControllerWithSpacesAndCheck(c, nil, expected) 388 _, err := env.acquireNode2( 389 "", "", 390 constraints.Value{Tags: stringslicep("tag1", "^tag2", "tag3", "^tag4")}, 391 nil, nil, 392 ) 393 c.Check(err, jc.ErrorIsNil) 394 } 395 396 func getFourSpaces() []gomaasapi.Space { 397 return []gomaasapi.Space{ 398 fakeSpace{ 399 name: "space-1", 400 subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}}, 401 id: 5, 402 }, 403 fakeSpace{ 404 name: "space-2", 405 subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}}, 406 id: 6, 407 }, 408 fakeSpace{ 409 name: "space-3", 410 subnets: []gomaasapi.Subnet{fakeSubnet{id: 101, vlan: fakeVLAN{vid: 66}, cidr: "192.168.12.0/24"}}, 411 id: 7, 412 }, 413 fakeSpace{ 414 name: "space-4", 415 subnets: []gomaasapi.Subnet{fakeSubnet{id: 102, vlan: fakeVLAN{vid: 66}, cidr: "192.168.13.0/24"}}, 416 id: 8, 417 }, 418 } 419 420 } 421 422 func (suite *maas2EnvironSuite) TestAcquireNodePassesPositiveAndNegativeSpaces(c *gc.C) { 423 expected := gomaasapi.AllocateMachineArgs{ 424 NotSpace: []string{"6", "8"}, 425 Interfaces: []gomaasapi.InterfaceSpec{ 426 {Label: "0", Space: "5"}, 427 {Label: "1", Space: "7"}, 428 }, 429 } 430 env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), expected) 431 432 _, err := env.acquireNode2( 433 "", "", 434 constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")}, 435 nil, nil, 436 ) 437 c.Check(err, jc.ErrorIsNil) 438 } 439 440 func (suite *maas2EnvironSuite) TestAcquireNodeDisambiguatesNamedLabelsFromIndexedUpToALimit(c *gc.C) { 441 env := suite.injectControllerWithSpacesAndCheck(c, getFourSpaces(), gomaasapi.AllocateMachineArgs{}) 442 var shortLimit uint = 0 443 suite.PatchValue(&numericLabelLimit, shortLimit) 444 445 _, err := env.acquireNode2( 446 "", "", 447 constraints.Value{Spaces: stringslicep("space-1", "^space-2", "space-3", "^space-4")}, 448 []interfaceBinding{{"0", "first-clash"}, {"1", "final-clash"}}, 449 nil, 450 ) 451 c.Assert(err, gc.ErrorMatches, `too many conflicting numeric labels, giving up.`) 452 } 453 454 func (suite *maas2EnvironSuite) TestAcquireNodeStorage(c *gc.C) { 455 var env *maasEnviron 456 var getStorage func() []gomaasapi.StorageSpec 457 suite.injectController(&fakeController{ 458 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 459 c.Assert(args, jc.DeepEquals, gomaasapi.AllocateMachineArgs{ 460 AgentName: env.ecfg().maasAgentName(), 461 Storage: getStorage(), 462 }) 463 }, 464 allocateMachine: &fakeMachine{ 465 systemID: "Bruce Sterling", 466 architecture: arch.HostArch(), 467 }, 468 }) 469 suite.setupFakeTools(c) 470 for i, test := range []struct { 471 volumes []volumeInfo 472 expected []gomaasapi.StorageSpec 473 }{{ 474 volumes: nil, 475 expected: []gomaasapi.StorageSpec{}, 476 }, { 477 volumes: []volumeInfo{{"volume-1", 1234, nil}}, 478 expected: []gomaasapi.StorageSpec{{"volume-1", 1234, nil}}, 479 }, { 480 volumes: []volumeInfo{{"", 1234, []string{"tag1", "tag2"}}}, 481 expected: []gomaasapi.StorageSpec{{"", 1234, []string{"tag1", "tag2"}}}, 482 }, { 483 volumes: []volumeInfo{{"volume-1", 1234, []string{"tag1", "tag2"}}}, 484 expected: []gomaasapi.StorageSpec{{"volume-1", 1234, []string{"tag1", "tag2"}}}, 485 }, { 486 volumes: []volumeInfo{ 487 {"volume-1", 1234, []string{"tag1", "tag2"}}, 488 {"volume-2", 4567, []string{"tag1", "tag3"}}, 489 }, 490 expected: []gomaasapi.StorageSpec{ 491 {"volume-1", 1234, []string{"tag1", "tag2"}}, 492 {"volume-2", 4567, []string{"tag1", "tag3"}}, 493 }, 494 }} { 495 c.Logf("test #%d: volumes=%v", i, test.volumes) 496 getStorage = func() []gomaasapi.StorageSpec { 497 return test.expected 498 } 499 env = suite.makeEnviron(c, nil) 500 _, err := env.acquireNode2("", "", constraints.Value{}, nil, test.volumes) 501 c.Check(err, jc.ErrorIsNil) 502 } 503 } 504 505 func (suite *maas2EnvironSuite) TestAcquireNodeInterfaces(c *gc.C) { 506 var env *maasEnviron 507 var getNegatives func() []string 508 var getPositives func() []gomaasapi.InterfaceSpec 509 suite.injectController(&fakeController{ 510 allocateMachineArgsCheck: func(args gomaasapi.AllocateMachineArgs) { 511 c.Assert(args, gc.DeepEquals, gomaasapi.AllocateMachineArgs{ 512 AgentName: env.ecfg().maasAgentName(), 513 Interfaces: getPositives(), 514 NotSpace: getNegatives(), 515 }) 516 }, 517 allocateMachine: &fakeMachine{ 518 systemID: "Bruce Sterling", 519 architecture: arch.HostArch(), 520 }, 521 spaces: getTwoSpaces(), 522 }) 523 suite.setupFakeTools(c) 524 // Add some constraints, including spaces to verify specified bindings 525 // always override any spaces constraints. 526 cons := constraints.Value{ 527 Spaces: stringslicep("foo", "^bar"), 528 } 529 // In the tests below "space:5" means foo, "space:6" means bar. 530 for i, test := range []struct { 531 interfaces []interfaceBinding 532 expectedPositives []gomaasapi.InterfaceSpec 533 expectedNegatives []string 534 expectedError string 535 }{{ // without specified bindings, spaces constraints are used instead. 536 interfaces: nil, 537 expectedPositives: []gomaasapi.InterfaceSpec{{"0", "2"}}, 538 expectedNegatives: []string{"3"}, 539 expectedError: "", 540 }, { 541 interfaces: []interfaceBinding{{"name-1", "space-1"}}, 542 expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "space-1"}, {"0", "2"}}, 543 expectedNegatives: []string{"3"}, 544 }, { 545 interfaces: []interfaceBinding{ 546 {"name-1", "7"}, 547 {"name-2", "8"}, 548 {"name-3", "9"}, 549 }, 550 expectedPositives: []gomaasapi.InterfaceSpec{{"name-1", "7"}, {"name-2", "8"}, {"name-3", "9"}, {"0", "2"}}, 551 expectedNegatives: []string{"3"}, 552 }, { 553 interfaces: []interfaceBinding{{"", "anything"}}, 554 expectedError: "interface bindings cannot have empty names", 555 }, { 556 interfaces: []interfaceBinding{{"shared-db", "3"}}, 557 expectedError: `negative space "bar" from constraints clashes with interface bindings`, 558 }, { 559 interfaces: []interfaceBinding{ 560 {"shared-db", "1"}, 561 {"db", "1"}, 562 }, 563 expectedPositives: []gomaasapi.InterfaceSpec{{"shared-db", "1"}, {"db", "1"}, {"0", "2"}}, 564 expectedNegatives: []string{"3"}, 565 }, { 566 interfaces: []interfaceBinding{{"", ""}}, 567 expectedError: "interface bindings cannot have empty names", 568 }, { 569 interfaces: []interfaceBinding{ 570 {"valid", "ok"}, 571 {"", "valid-but-ignored-space"}, 572 {"valid-name-empty-space", ""}, 573 {"", ""}, 574 }, 575 expectedError: "interface bindings cannot have empty names", 576 }, { 577 interfaces: []interfaceBinding{{"foo", ""}}, 578 expectedError: `invalid interface binding "foo": space provider ID is required`, 579 }, { 580 interfaces: []interfaceBinding{ 581 {"bar", ""}, 582 {"valid", "ok"}, 583 {"", "valid-but-ignored-space"}, 584 {"", ""}, 585 }, 586 expectedError: `invalid interface binding "bar": space provider ID is required`, 587 }, { 588 interfaces: []interfaceBinding{ 589 {"dup-name", "1"}, 590 {"dup-name", "2"}, 591 }, 592 expectedError: `duplicated interface binding "dup-name"`, 593 }, { 594 interfaces: []interfaceBinding{ 595 {"valid-1", "0"}, 596 {"dup-name", "1"}, 597 {"dup-name", "2"}, 598 {"valid-2", "3"}, 599 }, 600 expectedError: `duplicated interface binding "dup-name"`, 601 }} { 602 c.Logf("test #%d: interfaces=%v", i, test.interfaces) 603 env = suite.makeEnviron(c, nil) 604 getNegatives = func() []string { 605 return test.expectedNegatives 606 } 607 getPositives = func() []gomaasapi.InterfaceSpec { 608 return test.expectedPositives 609 } 610 _, err := env.acquireNode2("", "", cons, test.interfaces, nil) 611 if test.expectedError != "" { 612 c.Check(err, gc.ErrorMatches, test.expectedError) 613 c.Check(err, jc.Satisfies, errors.IsNotValid) 614 continue 615 } 616 c.Check(err, jc.ErrorIsNil) 617 } 618 } 619 620 func getTwoSpaces() []gomaasapi.Space { 621 return []gomaasapi.Space{ 622 fakeSpace{ 623 name: "foo", 624 subnets: []gomaasapi.Subnet{fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24"}}, 625 id: 2, 626 }, 627 fakeSpace{ 628 name: "bar", 629 subnets: []gomaasapi.Subnet{fakeSubnet{id: 100, vlan: fakeVLAN{vid: 66}, cidr: "192.168.11.0/24"}}, 630 id: 3, 631 }, 632 } 633 } 634 635 func (suite *maas2EnvironSuite) TestAcquireNodeConvertsSpaceNames(c *gc.C) { 636 expected := gomaasapi.AllocateMachineArgs{ 637 NotSpace: []string{"3"}, 638 Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}}, 639 } 640 env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected) 641 cons := constraints.Value{ 642 Spaces: stringslicep("foo", "^bar"), 643 } 644 _, err := env.acquireNode2("", "", cons, nil, nil) 645 c.Assert(err, jc.ErrorIsNil) 646 } 647 648 func (suite *maas2EnvironSuite) TestAcquireNodeTranslatesSpaceNames(c *gc.C) { 649 expected := gomaasapi.AllocateMachineArgs{ 650 NotSpace: []string{"3"}, 651 Interfaces: []gomaasapi.InterfaceSpec{{Label: "0", Space: "2"}}, 652 } 653 env := suite.injectControllerWithSpacesAndCheck(c, getTwoSpaces(), expected) 654 cons := constraints.Value{ 655 Spaces: stringslicep("foo-1", "^bar-3"), 656 } 657 _, err := env.acquireNode2("", "", cons, nil, nil) 658 c.Assert(err, jc.ErrorIsNil) 659 } 660 661 func (suite *maas2EnvironSuite) TestAcquireNodeUnrecognisedSpace(c *gc.C) { 662 suite.injectController(&fakeController{}) 663 env := suite.makeEnviron(c, nil) 664 cons := constraints.Value{ 665 Spaces: stringslicep("baz"), 666 } 667 _, err := env.acquireNode2("", "", cons, nil, nil) 668 c.Assert(err, gc.ErrorMatches, `unrecognised space in constraint "baz"`) 669 } 670 671 func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentError(c *gc.C) { 672 machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "") 673 controller := newFakeController() 674 controller.allocateMachine = machine 675 controller.allocateMachineMatches = gomaasapi.ConstraintMatches{ 676 Storage: map[string][]gomaasapi.BlockDevice{}, 677 } 678 controller.machines = []gomaasapi.Machine{machine} 679 suite.injectController(controller) 680 suite.setupFakeTools(c) 681 env := suite.makeEnviron(c, nil) 682 err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 683 c.Assert(err, gc.ErrorMatches, "bootstrap instance started but did not change to Deployed state.*") 684 } 685 686 func (suite *maas2EnvironSuite) TestWaitForNodeDeploymentSucceeds(c *gc.C) { 687 machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "Deployed") 688 controller := newFakeController() 689 controller.allocateMachine = machine 690 controller.allocateMachineMatches = gomaasapi.ConstraintMatches{ 691 Storage: map[string][]gomaasapi.BlockDevice{}, 692 } 693 controller.machines = []gomaasapi.Machine{machine} 694 suite.injectController(controller) 695 suite.setupFakeTools(c) 696 env := suite.makeEnviron(c, nil) 697 err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 698 c.Assert(err, jc.ErrorIsNil) 699 } 700 701 func (suite *maas2EnvironSuite) TestSubnetsNoFilters(c *gc.C) { 702 suite.injectController(&fakeController{ 703 spaces: getFourSpaces(), 704 }) 705 env := suite.makeEnviron(c, nil) 706 subnets, err := env.Subnets("", nil) 707 c.Assert(err, jc.ErrorIsNil) 708 expected := []network.SubnetInfo{ 709 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 710 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"}, 711 {CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 66, SpaceProviderId: "7"}, 712 {CIDR: "192.168.13.0/24", ProviderId: "102", VLANTag: 66, SpaceProviderId: "8"}, 713 } 714 c.Assert(subnets, jc.DeepEquals, expected) 715 } 716 717 func (suite *maas2EnvironSuite) TestSubnetsNoFiltersError(c *gc.C) { 718 suite.injectController(&fakeController{ 719 spacesError: errors.New("bang"), 720 }) 721 env := suite.makeEnviron(c, nil) 722 _, err := env.Subnets("", nil) 723 c.Assert(err, gc.ErrorMatches, "bang") 724 } 725 726 func (suite *maas2EnvironSuite) TestSubnetsSubnetIds(c *gc.C) { 727 suite.injectController(&fakeController{ 728 spaces: getFourSpaces(), 729 }) 730 env := suite.makeEnviron(c, nil) 731 subnets, err := env.Subnets("", []network.Id{"99", "100"}) 732 c.Assert(err, jc.ErrorIsNil) 733 expected := []network.SubnetInfo{ 734 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 735 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 66, SpaceProviderId: "6"}, 736 } 737 c.Assert(subnets, jc.DeepEquals, expected) 738 } 739 740 func (suite *maas2EnvironSuite) TestSubnetsSubnetIdsMissing(c *gc.C) { 741 suite.injectController(&fakeController{ 742 spaces: getFourSpaces(), 743 }) 744 env := suite.makeEnviron(c, nil) 745 _, err := env.Subnets("", []network.Id{"99", "missing"}) 746 msg := "failed to find the following subnets: missing" 747 c.Assert(err, gc.ErrorMatches, msg) 748 } 749 750 func (suite *maas2EnvironSuite) TestSubnetsInstIdNotFound(c *gc.C) { 751 suite.injectController(&fakeController{}) 752 env := suite.makeEnviron(c, nil) 753 _, err := env.Subnets("foo", nil) 754 c.Assert(err, jc.Satisfies, errors.IsNotFound) 755 } 756 757 func (suite *maas2EnvironSuite) TestSubnetsInstId(c *gc.C) { 758 interfaces := []gomaasapi.Interface{ 759 &fakeInterface{ 760 links: []gomaasapi.Link{ 761 &fakeLink{subnet: fakeSubnet{id: 99, vlan: fakeVLAN{vid: 66}, cidr: "192.168.10.0/24", space: "space-1"}}, 762 &fakeLink{subnet: fakeSubnet{id: 100, vlan: fakeVLAN{vid: 0}, cidr: "192.168.11.0/24", space: "space-2"}}, 763 }, 764 }, 765 &fakeInterface{ 766 links: []gomaasapi.Link{ 767 &fakeLink{subnet: fakeSubnet{id: 101, vlan: fakeVLAN{vid: 2}, cidr: "192.168.12.0/24", space: "space-3"}}, 768 }, 769 }, 770 } 771 machine := &fakeMachine{ 772 systemID: "William Gibson", 773 interfaceSet: interfaces, 774 } 775 machine2 := &fakeMachine{systemID: "Bruce Sterling"} 776 suite.injectController(&fakeController{ 777 machines: []gomaasapi.Machine{machine, machine2}, 778 spaces: getFourSpaces(), 779 }) 780 env := suite.makeEnviron(c, nil) 781 subnets, err := env.Subnets("William Gibson", nil) 782 c.Assert(err, jc.ErrorIsNil) 783 expected := []network.SubnetInfo{ 784 {CIDR: "192.168.10.0/24", ProviderId: "99", VLANTag: 66, SpaceProviderId: "5"}, 785 {CIDR: "192.168.11.0/24", ProviderId: "100", VLANTag: 0, SpaceProviderId: "6"}, 786 {CIDR: "192.168.12.0/24", ProviderId: "101", VLANTag: 2, SpaceProviderId: "7"}, 787 } 788 c.Assert(subnets, jc.DeepEquals, expected) 789 } 790 791 func (suite *maas2EnvironSuite) TestStartInstanceNetworkInterfaces(c *gc.C) { 792 vlan0 := fakeVLAN{ 793 id: 5001, 794 vid: 0, 795 mtu: 1500, 796 } 797 798 vlan50 := fakeVLAN{ 799 id: 5004, 800 vid: 50, 801 mtu: 1500, 802 } 803 804 subnetPXE := fakeSubnet{ 805 id: 3, 806 space: "default", 807 vlan: vlan0, 808 gateway: "10.20.19.2", 809 cidr: "10.20.19.0/24", 810 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 811 } 812 813 exampleInterfaces := []gomaasapi.Interface{ 814 &fakeInterface{ 815 id: 91, 816 name: "eth0", 817 type_: "physical", 818 enabled: true, 819 macAddress: "52:54:00:70:9b:fe", 820 vlan: vlan0, 821 links: []gomaasapi.Link{ 822 &fakeLink{ 823 id: 436, 824 subnet: &subnetPXE, 825 ipAddress: "10.20.19.103", 826 mode: "static", 827 }, 828 &fakeLink{ 829 id: 437, 830 subnet: &subnetPXE, 831 ipAddress: "10.20.19.104", 832 mode: "static", 833 }, 834 }, 835 parents: []string{}, 836 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 837 }, 838 &fakeInterface{ 839 id: 150, 840 name: "eth0.50", 841 type_: "vlan", 842 enabled: true, 843 macAddress: "52:54:00:70:9b:fe", 844 vlan: vlan50, 845 links: []gomaasapi.Link{ 846 &fakeLink{ 847 id: 517, 848 subnet: &fakeSubnet{ 849 id: 5, 850 space: "admin", 851 vlan: vlan50, 852 gateway: "10.50.19.2", 853 cidr: "10.50.19.0/24", 854 dnsServers: []string{}, 855 }, 856 ipAddress: "10.50.19.103", 857 mode: "static", 858 }, 859 }, 860 parents: []string{"eth0"}, 861 children: []string{}, 862 }, 863 } 864 var env *maasEnviron 865 machine := newFakeMachine("Bruce Sterling", arch.HostArch(), "") 866 machine.interfaceSet = exampleInterfaces 867 controller := &fakeController{ 868 allocateMachine: machine, 869 allocateMachineMatches: gomaasapi.ConstraintMatches{ 870 Storage: map[string][]gomaasapi.BlockDevice{}, 871 }, 872 } 873 suite.injectController(controller) 874 suite.setupFakeTools(c) 875 env = suite.makeEnviron(c, nil) 876 877 params := environs.StartInstanceParams{} 878 result, err := jujutesting.StartInstanceWithParams(env, "1", params) 879 c.Assert(err, jc.ErrorIsNil) 880 expected := []network.InterfaceInfo{{ 881 DeviceIndex: 0, 882 MACAddress: "52:54:00:70:9b:fe", 883 CIDR: "10.20.19.0/24", 884 ProviderId: "91", 885 ProviderSubnetId: "3", 886 AvailabilityZones: nil, 887 VLANTag: 0, 888 ProviderVLANId: "5001", 889 ProviderAddressId: "436", 890 InterfaceName: "eth0", 891 InterfaceType: "ethernet", 892 Disabled: false, 893 NoAutoStart: false, 894 ConfigType: "static", 895 Address: network.NewAddressOnSpace("default", "10.20.19.103"), 896 DNSServers: network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"), 897 DNSSearchDomains: nil, 898 MTU: 1500, 899 GatewayAddress: network.NewAddressOnSpace("default", "10.20.19.2"), 900 }, { 901 DeviceIndex: 0, 902 MACAddress: "52:54:00:70:9b:fe", 903 CIDR: "10.20.19.0/24", 904 ProviderId: "91", 905 ProviderSubnetId: "3", 906 AvailabilityZones: nil, 907 VLANTag: 0, 908 ProviderVLANId: "5001", 909 ProviderAddressId: "437", 910 InterfaceName: "eth0", 911 InterfaceType: "ethernet", 912 Disabled: false, 913 NoAutoStart: false, 914 ConfigType: "static", 915 Address: network.NewAddressOnSpace("default", "10.20.19.104"), 916 DNSServers: network.NewAddressesOnSpace("default", "10.20.19.2", "10.20.19.3"), 917 DNSSearchDomains: nil, 918 MTU: 1500, 919 GatewayAddress: network.NewAddressOnSpace("default", "10.20.19.2"), 920 }, { 921 DeviceIndex: 1, 922 MACAddress: "52:54:00:70:9b:fe", 923 CIDR: "10.50.19.0/24", 924 ProviderId: "150", 925 ProviderSubnetId: "5", 926 AvailabilityZones: nil, 927 VLANTag: 50, 928 ProviderVLANId: "5004", 929 ProviderAddressId: "517", 930 InterfaceName: "eth0.50", 931 ParentInterfaceName: "eth0", 932 InterfaceType: "802.1q", 933 Disabled: false, 934 NoAutoStart: false, 935 ConfigType: "static", 936 Address: network.NewAddressOnSpace("admin", "10.50.19.103"), 937 DNSServers: nil, 938 DNSSearchDomains: nil, 939 MTU: 1500, 940 GatewayAddress: network.NewAddressOnSpace("admin", "10.50.19.2"), 941 }, 942 } 943 c.Assert(result.NetworkInfo, jc.DeepEquals, expected) 944 } 945 946 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSingleNic(c *gc.C) { 947 vlan1 := fakeVLAN{ 948 id: 5001, 949 mtu: 1500, 950 } 951 vlan2 := fakeVLAN{ 952 id: 5002, 953 mtu: 1500, 954 } 955 subnet1 := fakeSubnet{ 956 id: 3, 957 space: "default", 958 vlan: vlan1, 959 gateway: "10.20.19.2", 960 cidr: "10.20.19.0/24", 961 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 962 } 963 subnet2 := fakeSubnet{ 964 id: 4, 965 space: "freckles", 966 vlan: vlan2, 967 gateway: "192.168.1.1", 968 cidr: "192.168.1.0/24", 969 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 970 } 971 972 interfaces := []gomaasapi.Interface{ 973 &fakeInterface{ 974 id: 91, 975 name: "eth0", 976 type_: "physical", 977 enabled: true, 978 macAddress: "52:54:00:70:9b:fe", 979 vlan: vlan1, 980 links: []gomaasapi.Link{ 981 &fakeLink{ 982 id: 436, 983 subnet: &subnet1, 984 ipAddress: "10.20.19.103", 985 mode: "static", 986 }, 987 }, 988 parents: []string{}, 989 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 990 }, 991 } 992 deviceInterfaces := []gomaasapi.Interface{ 993 &fakeInterface{ 994 id: 93, 995 name: "eth1", 996 type_: "physical", 997 enabled: true, 998 macAddress: "53:54:00:70:9b:ff", 999 vlan: vlan2, 1000 links: []gomaasapi.Link{ 1001 &fakeLink{ 1002 id: 480, 1003 subnet: &subnet2, 1004 ipAddress: "192.168.1.127", 1005 mode: "static", 1006 }, 1007 }, 1008 parents: []string{}, 1009 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 1010 }, 1011 } 1012 var env *maasEnviron 1013 device := &fakeDevice{ 1014 interfaceSet: deviceInterfaces, 1015 systemID: "foo", 1016 } 1017 controller := &fakeController{ 1018 machines: []gomaasapi.Machine{&fakeMachine{ 1019 Stub: &testing.Stub{}, 1020 systemID: "1", 1021 architecture: arch.HostArch(), 1022 interfaceSet: interfaces, 1023 createDevice: device, 1024 }}, 1025 spaces: []gomaasapi.Space{ 1026 fakeSpace{ 1027 name: "freckles", 1028 id: 4567, 1029 subnets: []gomaasapi.Subnet{subnet1, subnet2}, 1030 }, 1031 }, 1032 devices: []gomaasapi.Device{device}, 1033 } 1034 suite.injectController(controller) 1035 suite.setupFakeTools(c) 1036 env = suite.makeEnviron(c, nil) 1037 1038 prepared := []network.InterfaceInfo{{ 1039 MACAddress: "52:54:00:70:9b:fe", 1040 CIDR: "10.20.19.0/24", 1041 InterfaceName: "eth0", 1042 }} 1043 result, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1044 c.Assert(err, jc.ErrorIsNil) 1045 expected := []network.InterfaceInfo{{ 1046 DeviceIndex: 0, 1047 MACAddress: "53:54:00:70:9b:ff", 1048 CIDR: "192.168.1.0/24", 1049 ProviderId: "93", 1050 ProviderSubnetId: "4", 1051 VLANTag: 0, 1052 ProviderVLANId: "5002", 1053 ProviderAddressId: "480", 1054 InterfaceName: "eth1", 1055 InterfaceType: "ethernet", 1056 ConfigType: "static", 1057 Address: network.NewAddressOnSpace("freckles", "192.168.1.127"), 1058 DNSServers: network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"), 1059 MTU: 1500, 1060 GatewayAddress: network.NewAddressOnSpace("freckles", "192.168.1.1"), 1061 }} 1062 c.Assert(result, jc.DeepEquals, expected) 1063 } 1064 1065 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesDualNic(c *gc.C) { 1066 vlan1 := fakeVLAN{ 1067 id: 5001, 1068 mtu: 1500, 1069 } 1070 vlan2 := fakeVLAN{ 1071 id: 5002, 1072 mtu: 1500, 1073 } 1074 subnet1 := fakeSubnet{ 1075 id: 3, 1076 space: "freckles", 1077 vlan: vlan1, 1078 gateway: "10.20.19.2", 1079 cidr: "10.20.19.0/24", 1080 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 1081 } 1082 subnet2 := fakeSubnet{ 1083 id: 4, 1084 space: "freckles", 1085 vlan: vlan2, 1086 gateway: "192.168.1.1", 1087 cidr: "192.168.1.0/24", 1088 dnsServers: []string{"10.20.19.2", "10.20.19.3"}, 1089 } 1090 1091 interfaces := []gomaasapi.Interface{ 1092 &fakeInterface{ 1093 id: 91, 1094 name: "eth0", 1095 type_: "physical", 1096 enabled: true, 1097 macAddress: "52:54:00:70:9b:fe", 1098 vlan: vlan1, 1099 links: []gomaasapi.Link{ 1100 &fakeLink{ 1101 id: 436, 1102 subnet: &subnet1, 1103 ipAddress: "10.20.19.103", 1104 mode: "static", 1105 }, 1106 }, 1107 parents: []string{}, 1108 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 1109 }, 1110 &fakeInterface{ 1111 id: 92, 1112 name: "eth1", 1113 type_: "physical", 1114 enabled: true, 1115 macAddress: "52:54:00:70:9b:ff", 1116 vlan: vlan2, 1117 links: []gomaasapi.Link{ 1118 &fakeLink{ 1119 id: 437, 1120 subnet: &subnet2, 1121 ipAddress: "192.168.1.4", 1122 mode: "static", 1123 }, 1124 }, 1125 }, 1126 } 1127 deviceInterfaces := []gomaasapi.Interface{ 1128 &fakeInterface{ 1129 id: 93, 1130 name: "eth0", 1131 type_: "physical", 1132 enabled: true, 1133 macAddress: "53:54:00:70:9b:ff", 1134 vlan: vlan1, 1135 links: []gomaasapi.Link{ 1136 &fakeLink{ 1137 id: 480, 1138 subnet: &subnet1, 1139 ipAddress: "10.20.19.127", 1140 mode: "static", 1141 }, 1142 }, 1143 parents: []string{}, 1144 children: []string{"eth0.100", "eth0.250", "eth0.50"}, 1145 Stub: &testing.Stub{}, 1146 }, 1147 } 1148 newInterface := &fakeInterface{ 1149 id: 94, 1150 name: "eth1", 1151 type_: "physical", 1152 enabled: true, 1153 macAddress: "52:54:00:70:9b:f4", 1154 vlan: vlan2, 1155 links: []gomaasapi.Link{ 1156 &fakeLink{ 1157 id: 481, 1158 subnet: &subnet2, 1159 ipAddress: "192.168.1.127", 1160 mode: "static", 1161 }, 1162 }, 1163 Stub: &testing.Stub{}, 1164 } 1165 device := &fakeDevice{ 1166 interfaceSet: deviceInterfaces, 1167 systemID: "foo", 1168 interface_: newInterface, 1169 Stub: &testing.Stub{}, 1170 } 1171 controller := &fakeController{ 1172 machines: []gomaasapi.Machine{&fakeMachine{ 1173 Stub: &testing.Stub{}, 1174 systemID: "1", 1175 architecture: arch.HostArch(), 1176 interfaceSet: interfaces, 1177 createDevice: device, 1178 }}, 1179 spaces: []gomaasapi.Space{ 1180 fakeSpace{ 1181 name: "freckles", 1182 id: 4567, 1183 subnets: []gomaasapi.Subnet{subnet1, subnet2}, 1184 }, 1185 }, 1186 devices: []gomaasapi.Device{device}, 1187 } 1188 suite.injectController(controller) 1189 env := suite.makeEnviron(c, nil) 1190 1191 prepared := []network.InterfaceInfo{{ 1192 MACAddress: "53:54:00:70:9b:ff", 1193 CIDR: "10.20.19.0/24", 1194 InterfaceName: "eth0", 1195 }, { 1196 MACAddress: "52:54:00:70:9b:f4", 1197 CIDR: "192.168.1.0/24", 1198 InterfaceName: "eth1", 1199 }} 1200 expected := []network.InterfaceInfo{{ 1201 DeviceIndex: 0, 1202 MACAddress: "53:54:00:70:9b:ff", 1203 CIDR: "10.20.19.0/24", 1204 ProviderId: "93", 1205 ProviderSubnetId: "3", 1206 ProviderVLANId: "5001", 1207 ProviderAddressId: "480", 1208 InterfaceName: "eth0", 1209 InterfaceType: "ethernet", 1210 ConfigType: "static", 1211 Address: network.NewAddressOnSpace("freckles", "10.20.19.127"), 1212 DNSServers: network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"), 1213 MTU: 1500, 1214 GatewayAddress: network.NewAddressOnSpace("freckles", "10.20.19.2"), 1215 }, { 1216 DeviceIndex: 0, 1217 MACAddress: "52:54:00:70:9b:f4", 1218 CIDR: "192.168.1.0/24", 1219 ProviderId: "94", 1220 ProviderSubnetId: "4", 1221 ProviderVLANId: "5002", 1222 ProviderAddressId: "481", 1223 InterfaceName: "eth1", 1224 InterfaceType: "ethernet", 1225 ConfigType: "static", 1226 Address: network.NewAddressOnSpace("freckles", "192.168.1.127"), 1227 DNSServers: network.NewAddressesOnSpace("freckles", "10.20.19.2", "10.20.19.3"), 1228 MTU: 1500, 1229 GatewayAddress: network.NewAddressOnSpace("freckles", "192.168.1.1"), 1230 }} 1231 result, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1232 c.Assert(err, jc.ErrorIsNil) 1233 c.Assert(result, jc.DeepEquals, expected) 1234 } 1235 1236 func (suite *maas2EnvironSuite) assertAllocateContainerAddressesFails(c *gc.C, controller *fakeController, prepared []network.InterfaceInfo, errorMatches string) { 1237 if prepared == nil { 1238 prepared = []network.InterfaceInfo{{}} 1239 } 1240 suite.injectController(controller) 1241 env := suite.makeEnviron(c, nil) 1242 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1243 c.Assert(err, gc.ErrorMatches, errorMatches) 1244 } 1245 1246 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSpacesError(c *gc.C) { 1247 controller := &fakeController{spacesError: errors.New("boom")} 1248 suite.assertAllocateContainerAddressesFails(c, controller, nil, "boom") 1249 } 1250 1251 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesPrimaryInterfaceMissing(c *gc.C) { 1252 controller := &fakeController{} 1253 suite.assertAllocateContainerAddressesFails(c, controller, nil, "cannot find primary interface for container") 1254 } 1255 1256 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesPrimaryInterfaceSubnetMissing(c *gc.C) { 1257 controller := &fakeController{} 1258 prepared := []network.InterfaceInfo{{InterfaceName: "eth0"}} 1259 errorMatches := "primary NIC subnet not found" 1260 suite.assertAllocateContainerAddressesFails(c, controller, prepared, errorMatches) 1261 } 1262 1263 func makeFakeSubnet(id int) fakeSubnet { 1264 return fakeSubnet{ 1265 id: id, 1266 space: "freckles", 1267 gateway: fmt.Sprintf("10.20.%d.2", 16+id), 1268 cidr: fmt.Sprintf("10.20.%d.0/24", 16+id), 1269 } 1270 } 1271 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesMachinesError(c *gc.C) { 1272 var env *maasEnviron 1273 subnet := makeFakeSubnet(3) 1274 checkMachinesArgs := func(args gomaasapi.MachinesArgs) { 1275 expected := gomaasapi.MachinesArgs{ 1276 AgentName: env.ecfg().maasAgentName(), 1277 SystemIDs: []string{"1"}, 1278 } 1279 c.Assert(args, jc.DeepEquals, expected) 1280 } 1281 controller := &fakeController{ 1282 machinesError: errors.New("boom"), 1283 machinesArgsCheck: checkMachinesArgs, 1284 spaces: []gomaasapi.Space{ 1285 fakeSpace{ 1286 name: "freckles", 1287 id: 4567, 1288 subnets: []gomaasapi.Subnet{subnet}, 1289 }, 1290 }, 1291 } 1292 suite.injectController(controller) 1293 env = suite.makeEnviron(c, nil) 1294 prepared := []network.InterfaceInfo{ 1295 {InterfaceName: "eth0", CIDR: "10.20.19.0/24"}, 1296 } 1297 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1298 c.Assert(err, gc.ErrorMatches, "boom") 1299 } 1300 1301 func getArgs(c *gc.C, calls []testing.StubCall) interface{} { 1302 c.Assert(calls, gc.HasLen, 1) 1303 args := calls[0].Args 1304 c.Assert(args, gc.HasLen, 1) 1305 return args[0] 1306 } 1307 1308 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesCreateDevicerror(c *gc.C) { 1309 subnet := makeFakeSubnet(3) 1310 var env *maasEnviron 1311 machine := &fakeMachine{ 1312 Stub: &testing.Stub{}, 1313 systemID: "1", 1314 } 1315 machine.SetErrors(errors.New("boom")) 1316 controller := &fakeController{ 1317 machines: []gomaasapi.Machine{machine}, 1318 spaces: []gomaasapi.Space{ 1319 fakeSpace{ 1320 name: "freckles", 1321 id: 4567, 1322 subnets: []gomaasapi.Subnet{subnet}, 1323 }, 1324 }, 1325 } 1326 suite.injectController(controller) 1327 env = suite.makeEnviron(c, nil) 1328 prepared := []network.InterfaceInfo{ 1329 {InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"}, 1330 } 1331 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1332 c.Assert(err, gc.ErrorMatches, "boom") 1333 args := getArgs(c, machine.Calls()) 1334 maasArgs, ok := args.(gomaasapi.CreateMachineDeviceArgs) 1335 c.Assert(ok, jc.IsTrue) 1336 expected := gomaasapi.CreateMachineDeviceArgs{ 1337 Subnet: subnet, 1338 MACAddress: "DEADBEEF", 1339 InterfaceName: "eth0", 1340 } 1341 c.Assert(maasArgs, jc.DeepEquals, expected) 1342 } 1343 1344 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesSecondNICSubnetMissing(c *gc.C) { 1345 subnet := makeFakeSubnet(3) 1346 var env *maasEnviron 1347 device := &fakeDevice{ 1348 interfaceSet: []gomaasapi.Interface{&fakeInterface{}}, 1349 systemID: "foo", 1350 } 1351 machine := &fakeMachine{ 1352 Stub: &testing.Stub{}, 1353 systemID: "1", 1354 createDevice: device, 1355 } 1356 controller := &fakeController{ 1357 machines: []gomaasapi.Machine{machine}, 1358 spaces: []gomaasapi.Space{ 1359 fakeSpace{ 1360 name: "freckles", 1361 id: 4567, 1362 subnets: []gomaasapi.Subnet{subnet}, 1363 }, 1364 }, 1365 } 1366 suite.injectController(controller) 1367 env = suite.makeEnviron(c, nil) 1368 prepared := []network.InterfaceInfo{ 1369 {InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"}, 1370 {InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"}, 1371 } 1372 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1373 c.Assert(err, gc.ErrorMatches, "NIC eth1 subnet 10.20.20.0/24 not found") 1374 } 1375 1376 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesCreateInterfaceError(c *gc.C) { 1377 subnet := makeFakeSubnet(3) 1378 subnet2 := makeFakeSubnet(4) 1379 subnet2.vlan = fakeVLAN{vid: 66} 1380 var env *maasEnviron 1381 device := &fakeDevice{ 1382 Stub: &testing.Stub{}, 1383 interfaceSet: []gomaasapi.Interface{&fakeInterface{}}, 1384 systemID: "foo", 1385 } 1386 device.SetErrors(errors.New("boom")) 1387 machine := &fakeMachine{ 1388 Stub: &testing.Stub{}, 1389 systemID: "1", 1390 createDevice: device, 1391 } 1392 controller := &fakeController{ 1393 machines: []gomaasapi.Machine{machine}, 1394 spaces: []gomaasapi.Space{ 1395 fakeSpace{ 1396 name: "freckles", 1397 id: 4567, 1398 subnets: []gomaasapi.Subnet{subnet, subnet2}, 1399 }, 1400 }, 1401 } 1402 suite.injectController(controller) 1403 env = suite.makeEnviron(c, nil) 1404 prepared := []network.InterfaceInfo{ 1405 {InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"}, 1406 {InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"}, 1407 } 1408 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1409 c.Assert(err, gc.ErrorMatches, "creating device interface: boom") 1410 args := getArgs(c, device.Calls()) 1411 maasArgs, ok := args.(gomaasapi.CreateInterfaceArgs) 1412 c.Assert(ok, jc.IsTrue) 1413 expected := gomaasapi.CreateInterfaceArgs{ 1414 MACAddress: "DEADBEEE", 1415 Name: "eth1", 1416 VLAN: subnet2.VLAN(), 1417 } 1418 c.Assert(maasArgs, jc.DeepEquals, expected) 1419 } 1420 1421 func (suite *maas2EnvironSuite) TestAllocateContainerAddressesLinkSubnetError(c *gc.C) { 1422 subnet := makeFakeSubnet(3) 1423 subnet2 := makeFakeSubnet(4) 1424 subnet2.vlan = fakeVLAN{vid: 66} 1425 var env *maasEnviron 1426 interface_ := &fakeInterface{Stub: &testing.Stub{}} 1427 interface_.SetErrors(errors.New("boom")) 1428 device := &fakeDevice{ 1429 Stub: &testing.Stub{}, 1430 interfaceSet: []gomaasapi.Interface{&fakeInterface{}}, 1431 interface_: interface_, 1432 systemID: "foo", 1433 } 1434 machine := &fakeMachine{ 1435 Stub: &testing.Stub{}, 1436 systemID: "1", 1437 createDevice: device, 1438 } 1439 controller := &fakeController{ 1440 machines: []gomaasapi.Machine{machine}, 1441 spaces: []gomaasapi.Space{ 1442 fakeSpace{ 1443 name: "freckles", 1444 id: 4567, 1445 subnets: []gomaasapi.Subnet{subnet, subnet2}, 1446 }, 1447 }, 1448 } 1449 suite.injectController(controller) 1450 env = suite.makeEnviron(c, nil) 1451 prepared := []network.InterfaceInfo{ 1452 {InterfaceName: "eth0", CIDR: "10.20.19.0/24", MACAddress: "DEADBEEF"}, 1453 {InterfaceName: "eth1", CIDR: "10.20.20.0/24", MACAddress: "DEADBEEE"}, 1454 } 1455 _, err := env.AllocateContainerAddresses(instance.Id("1"), prepared) 1456 c.Assert(err, gc.ErrorMatches, "cannot link device interface to subnet: boom") 1457 args := getArgs(c, interface_.Calls()) 1458 maasArgs, ok := args.(gomaasapi.LinkSubnetArgs) 1459 c.Assert(ok, jc.IsTrue) 1460 expected := gomaasapi.LinkSubnetArgs{ 1461 Mode: gomaasapi.LinkModeStatic, 1462 Subnet: subnet2, 1463 } 1464 c.Assert(maasArgs, jc.DeepEquals, expected) 1465 } 1466 func (suite *maas2EnvironSuite) TestStorageReturnsStorage(c *gc.C) { 1467 controller := newFakeController() 1468 env := suite.makeEnviron(c, controller) 1469 stor := env.Storage() 1470 c.Check(stor, gc.NotNil) 1471 1472 // The Storage object is really a maas2Storage. 1473 specificStorage := stor.(*maas2Storage) 1474 1475 // Its environment pointer refers back to its environment. 1476 c.Check(specificStorage.environ, gc.Equals, env) 1477 c.Check(specificStorage.maasController, gc.Equals, controller) 1478 } 1479 1480 func (suite *maas2EnvironSuite) TestStartInstanceEndToEnd(c *gc.C) { 1481 suite.setupFakeTools(c) 1482 machine := newFakeMachine("gus", arch.HostArch(), "Deployed") 1483 file := &fakeFile{name: "agent-prefix-provider-state"} 1484 controller := newFakeControllerWithFiles(file) 1485 controller.machines = []gomaasapi.Machine{machine} 1486 controller.allocateMachine = machine 1487 controller.allocateMachineMatches = gomaasapi.ConstraintMatches{ 1488 Storage: make(map[string][]gomaasapi.BlockDevice), 1489 } 1490 1491 env := suite.makeEnviron(c, controller) 1492 err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1493 c.Assert(err, jc.ErrorIsNil) 1494 1495 machine.Stub.CheckCallNames(c, "Start") 1496 controller.Stub.CheckCallNames(c, "GetFile", "AddFile") 1497 addFileArgs, ok := controller.Stub.Calls()[1].Args[0].(gomaasapi.AddFileArgs) 1498 c.Assert(ok, jc.IsTrue) 1499 1500 // Make it look like the right state was written to the file. 1501 buffer := new(bytes.Buffer) 1502 buffer.ReadFrom(addFileArgs.Reader) 1503 file.contents = buffer.Bytes() 1504 c.Check(string(buffer.Bytes()), gc.Equals, "state-instances:\n- gus\n") 1505 1506 // Test the instance id is correctly recorded for the bootstrap node. 1507 // Check that ControllerInstances returns the id of the bootstrap machine. 1508 instanceIds, err := env.ControllerInstances() 1509 c.Assert(err, jc.ErrorIsNil) 1510 c.Assert(instanceIds, gc.HasLen, 1) 1511 insts, err := env.AllInstances() 1512 c.Assert(err, jc.ErrorIsNil) 1513 c.Assert(insts, gc.HasLen, 1) 1514 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 1515 1516 node1 := newFakeMachine("victor", arch.HostArch(), "Deployed") 1517 node1.hostname = "host1" 1518 node1.cpuCount = 1 1519 node1.memory = 1024 1520 node1.zoneName = "test_zone" 1521 controller.allocateMachine = node1 1522 1523 instance, hc := jujutesting.AssertStartInstance(c, env, "1") 1524 c.Check(instance, gc.NotNil) 1525 c.Assert(hc, gc.NotNil) 1526 c.Check(hc.String(), gc.Equals, fmt.Sprintf("arch=%s cpu-cores=1 mem=1024M availability-zone=test_zone", arch.HostArch())) 1527 1528 node1.Stub.CheckCallNames(c, "Start") 1529 startArgs, ok := node1.Stub.Calls()[0].Args[0].(gomaasapi.StartArgs) 1530 c.Assert(ok, jc.IsTrue) 1531 1532 decodedUserData, err := decodeUserData(startArgs.UserData) 1533 c.Assert(err, jc.ErrorIsNil) 1534 info := machineInfo{"host1"} 1535 cloudcfg, err := cloudinit.New("precise") 1536 c.Assert(err, jc.ErrorIsNil) 1537 cloudinitRunCmd, err := info.cloudinitRunCmd(cloudcfg) 1538 c.Assert(err, jc.ErrorIsNil) 1539 data, err := goyaml.Marshal(cloudinitRunCmd) 1540 c.Assert(err, jc.ErrorIsNil) 1541 c.Check(string(decodedUserData), jc.Contains, string(data)) 1542 1543 // Trash the tools and try to start another instance. 1544 suite.PatchValue(&envtools.DefaultBaseURL, "") 1545 instance, _, _, err = jujutesting.StartInstance(env, "2") 1546 c.Check(instance, gc.IsNil) 1547 c.Check(err, jc.Satisfies, errors.IsNotFound) 1548 } 1549 1550 func (suite *maas2EnvironSuite) TestControllerInstances(c *gc.C) { 1551 controller := newFakeControllerWithErrors(gomaasapi.NewNoMatchError("state")) 1552 env := suite.makeEnviron(c, controller) 1553 _, err := env.ControllerInstances() 1554 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 1555 1556 tests := [][]instance.Id{{}, {"inst-0"}, {"inst-0", "inst-1"}} 1557 for _, expected := range tests { 1558 state, err := goyaml.Marshal(&common.BootstrapState{StateInstances: expected}) 1559 c.Assert(err, jc.ErrorIsNil) 1560 1561 controller.files = []gomaasapi.File{&fakeFile{ 1562 name: "agent-prefix-provider-state", 1563 contents: state, 1564 }} 1565 controllerInstances, err := env.ControllerInstances() 1566 c.Assert(err, jc.ErrorIsNil) 1567 c.Assert(controllerInstances, jc.SameContents, expected) 1568 } 1569 } 1570 1571 func (suite *maas2EnvironSuite) TestControllerInstancesFailsIfNoStateInstances(c *gc.C) { 1572 env := suite.makeEnviron(c, 1573 newFakeControllerWithErrors(gomaasapi.NewNoMatchError("state"))) 1574 _, err := env.ControllerInstances() 1575 c.Check(err, gc.Equals, environs.ErrNotBootstrapped) 1576 } 1577 1578 func (suite *maas2EnvironSuite) TestDestroy(c *gc.C) { 1579 file1 := &fakeFile{name: "agent-prefix-provider-state"} 1580 file2 := &fakeFile{name: "agent-prefix-horace"} 1581 controller := newFakeControllerWithFiles(file1, file2) 1582 controller.machines = []gomaasapi.Machine{&fakeMachine{systemID: "pete"}} 1583 env := suite.makeEnviron(c, controller) 1584 err := env.Destroy() 1585 c.Check(err, jc.ErrorIsNil) 1586 1587 controller.Stub.CheckCallNames(c, "ReleaseMachines", "GetFile", "Files", "GetFile", "GetFile") 1588 // Instances have been stopped. 1589 controller.Stub.CheckCall(c, 0, "ReleaseMachines", gomaasapi.ReleaseMachinesArgs{ 1590 SystemIDs: []string{"pete"}, 1591 Comment: "Released by Juju MAAS provider", 1592 }) 1593 1594 // Files have been cleaned up. 1595 c.Check(file1.deleted, jc.IsTrue) 1596 c.Check(file2.deleted, jc.IsTrue) 1597 } 1598 1599 func (suite *maas2EnvironSuite) TestBootstrapFailsIfNoTools(c *gc.C) { 1600 env := suite.makeEnviron(c, newFakeController()) 1601 // Disable auto-uploading by setting the agent version. 1602 cfg, err := env.Config().Apply(map[string]interface{}{ 1603 "agent-version": jujuversion.Current.String(), 1604 }) 1605 c.Assert(err, jc.ErrorIsNil) 1606 err = env.SetConfig(cfg) 1607 c.Assert(err, jc.ErrorIsNil) 1608 err = bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1609 c.Check(err, gc.ErrorMatches, "Juju cannot bootstrap because no tools are available for your model(.|\n)*") 1610 } 1611 1612 func (suite *maas2EnvironSuite) TestBootstrapFailsIfNoNodes(c *gc.C) { 1613 suite.setupFakeTools(c) 1614 controller := newFakeController() 1615 controller.allocateMachineError = gomaasapi.NewNoMatchError("oops") 1616 env := suite.makeEnviron(c, controller) 1617 err := bootstrap.Bootstrap(envjujutesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1618 // Since there are no nodes, the attempt to allocate one returns a 1619 // 409: Conflict. 1620 c.Check(err, gc.ErrorMatches, ".*cannot run instances.*") 1621 } 1622 1623 func (suite *maas2EnvironSuite) TestGetToolsMetadataSources(c *gc.C) { 1624 // Add a dummy file to storage so we can use that to check the 1625 // obtained source later. 1626 env := suite.makeEnviron(c, newFakeControllerWithFiles( 1627 &fakeFile{name: "agent-prefix-tools/filename", contents: makeRandomBytes(10)}, 1628 )) 1629 sources, err := envtools.GetMetadataSources(env) 1630 c.Assert(err, jc.ErrorIsNil) 1631 c.Assert(sources, gc.HasLen, 0) 1632 } 1633 1634 func (suite *maas2EnvironSuite) TestConstraintsValidator(c *gc.C) { 1635 controller := newFakeController() 1636 controller.bootResources = []gomaasapi.BootResource{&fakeBootResource{name: "trusty", architecture: "amd64"}} 1637 env := suite.makeEnviron(c, controller) 1638 validator, err := env.ConstraintsValidator() 1639 c.Assert(err, jc.ErrorIsNil) 1640 cons := constraints.MustParse("arch=amd64 cpu-power=10 instance-type=foo virt-type=kvm") 1641 unsupported, err := validator.Validate(cons) 1642 c.Assert(err, jc.ErrorIsNil) 1643 c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "instance-type", "virt-type"}) 1644 } 1645 1646 func (suite *maas2EnvironSuite) TestConstraintsValidatorVocab(c *gc.C) { 1647 controller := newFakeController() 1648 controller.bootResources = []gomaasapi.BootResource{ 1649 &fakeBootResource{name: "trusty", architecture: "amd64"}, 1650 &fakeBootResource{name: "precise", architecture: "armhf"}, 1651 } 1652 env := suite.makeEnviron(c, controller) 1653 validator, err := env.ConstraintsValidator() 1654 c.Assert(err, jc.ErrorIsNil) 1655 cons := constraints.MustParse("arch=ppc64el") 1656 _, err = validator.Validate(cons) 1657 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are: \\[amd64 armhf\\]") 1658 }