github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/instancepoller/instancepoller_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package instancepoller_test 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/clock/testclock" 11 "github.com/juju/errors" 12 "github.com/juju/mgo/v3/txn" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 "go.uber.org/mock/gomock" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/apiserver/common" 19 "github.com/juju/juju/apiserver/common/networkingcommon" 20 "github.com/juju/juju/apiserver/common/networkingcommon/mocks" 21 "github.com/juju/juju/apiserver/facades/controller/instancepoller" 22 apiservertesting "github.com/juju/juju/apiserver/testing" 23 "github.com/juju/juju/core/life" 24 "github.com/juju/juju/core/network" 25 "github.com/juju/juju/core/status" 26 "github.com/juju/juju/rpc/params" 27 "github.com/juju/juju/state" 28 statetesting "github.com/juju/juju/state/testing" 29 jujutesting "github.com/juju/juju/testing" 30 ) 31 32 type InstancePollerSuite struct { 33 testing.IsolationSuite 34 35 st *mockState 36 api *instancepoller.InstancePollerAPI 37 authoriser apiservertesting.FakeAuthorizer 38 resources *common.Resources 39 40 machineEntities params.Entities 41 machineErrorResults params.ErrorResults 42 43 mixedEntities params.Entities 44 mixedErrorResults params.ErrorResults 45 46 clock clock.Clock 47 } 48 49 var _ = gc.Suite(&InstancePollerSuite{}) 50 51 func (s *InstancePollerSuite) SetUpTest(c *gc.C) { 52 s.IsolationSuite.SetUpTest(c) 53 54 s.authoriser = apiservertesting.FakeAuthorizer{ 55 Controller: true, 56 } 57 s.resources = common.NewResources() 58 s.AddCleanup(func(*gc.C) { s.resources.StopAll() }) 59 60 s.st = NewMockState() 61 instancepoller.PatchState(s, s.st) 62 63 var err error 64 s.clock = testclock.NewClock(time.Now()) 65 s.api, err = instancepoller.NewInstancePollerAPI(nil, nil, s.resources, s.authoriser, s.clock) 66 c.Assert(err, jc.ErrorIsNil) 67 68 s.machineEntities = params.Entities{ 69 Entities: []params.Entity{ 70 {Tag: "machine-1"}, 71 {Tag: "machine-2"}, 72 {Tag: "machine-3"}, 73 }} 74 s.machineErrorResults = params.ErrorResults{ 75 Results: []params.ErrorResult{ 76 {Error: apiservertesting.ServerError("pow!")}, 77 {Error: apiservertesting.ServerError("FAIL")}, 78 {Error: apiservertesting.NotProvisionedError("42")}, 79 }} 80 81 s.mixedEntities = params.Entities{ 82 Entities: []params.Entity{ 83 {Tag: "machine-1"}, 84 {Tag: "machine-2"}, 85 {Tag: "machine-42"}, 86 {Tag: "application-unknown"}, 87 {Tag: "invalid-tag"}, 88 {Tag: "unit-missing-1"}, 89 {Tag: ""}, 90 {Tag: "42"}, 91 }} 92 s.mixedErrorResults = params.ErrorResults{ 93 Results: []params.ErrorResult{ 94 {Error: nil}, 95 {Error: nil}, 96 {Error: apiservertesting.NotFoundError("machine 42")}, 97 {Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)}, 98 {Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)}, 99 {Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)}, 100 {Error: apiservertesting.ServerError(`"" is not a valid tag`)}, 101 {Error: apiservertesting.ServerError(`"42" is not a valid tag`)}, 102 }} 103 } 104 105 func (s *InstancePollerSuite) TestNewInstancePollerAPIRequiresController(c *gc.C) { 106 anAuthoriser := s.authoriser 107 anAuthoriser.Controller = false 108 api, err := instancepoller.NewInstancePollerAPI(nil, nil, s.resources, anAuthoriser, s.clock) 109 c.Assert(api, gc.IsNil) 110 c.Assert(err, gc.ErrorMatches, "permission denied") 111 } 112 113 func (s *InstancePollerSuite) TestModelConfigFailure(c *gc.C) { 114 s.st.SetErrors(errors.New("boom")) 115 116 result, err := s.api.ModelConfig() 117 c.Assert(err, gc.ErrorMatches, "boom") 118 c.Assert(result, jc.DeepEquals, params.ModelConfigResult{}) 119 120 s.st.CheckCallNames(c, "ModelConfig") 121 } 122 123 func (s *InstancePollerSuite) TestModelConfigSuccess(c *gc.C) { 124 modelConfig := jujutesting.ModelConfig(c) 125 s.st.SetConfig(c, modelConfig) 126 127 result, err := s.api.ModelConfig() 128 c.Assert(err, jc.ErrorIsNil) 129 c.Assert(result, jc.DeepEquals, params.ModelConfigResult{ 130 Config: modelConfig.AllAttrs(), 131 }) 132 133 s.st.CheckCallNames(c, "ModelConfig") 134 } 135 136 func (s *InstancePollerSuite) TestWatchForModelConfigChangesFailure(c *gc.C) { 137 // Force the Changes() method of the mock watcher to return a 138 // closed channel by setting an error. 139 s.st.SetErrors(errors.New("boom")) 140 141 result, err := s.api.WatchForModelConfigChanges() 142 c.Assert(err, gc.ErrorMatches, "boom") 143 c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{}) 144 145 c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered 146 s.st.CheckCallNames(c, "WatchForModelConfigChanges") 147 } 148 149 func (s *InstancePollerSuite) TestWatchForModelConfigChangesSuccess(c *gc.C) { 150 result, err := s.api.WatchForModelConfigChanges() 151 c.Assert(err, jc.ErrorIsNil) 152 c.Assert(result, jc.DeepEquals, params.NotifyWatchResult{ 153 Error: nil, NotifyWatcherId: "1", 154 }) 155 156 // Verify the watcher resource was registered. 157 c.Assert(s.resources.Count(), gc.Equals, 1) 158 resource := s.resources.Get("1") 159 defer statetesting.AssertStop(c, resource) 160 161 // Check that the watcher has consumed the initial event 162 wc := statetesting.NewNotifyWatcherC(c, resource.(state.NotifyWatcher)) 163 wc.AssertNoChange() 164 165 s.st.CheckCallNames(c, "WatchForModelConfigChanges") 166 167 // Try changing the config to verify an event is reported. 168 modelConfig := jujutesting.ModelConfig(c) 169 s.st.SetConfig(c, modelConfig) 170 wc.AssertOneChange() 171 } 172 173 func (s *InstancePollerSuite) TestWatchModelMachinesFailure(c *gc.C) { 174 s.assertMachineWatcherFails(c, "WatchModelMachines", s.api.WatchModelMachines) 175 } 176 177 func (s *InstancePollerSuite) TestWatchModelMachinesSuccess(c *gc.C) { 178 s.assertMachineWatcherSucceeds(c, "WatchModelMachines", s.api.WatchModelMachines) 179 } 180 181 func (s *InstancePollerSuite) TestWatchModelMachineStartTimesFailure(c *gc.C) { 182 s.assertMachineWatcherFails(c, "WatchModelMachineStartTimes", s.api.WatchModelMachineStartTimes) 183 } 184 185 func (s *InstancePollerSuite) TestWatchModelMachineStartTimesSuccess(c *gc.C) { 186 s.assertMachineWatcherFails(c, "WatchModelMachineStartTimes", s.api.WatchModelMachineStartTimes) 187 } 188 189 func (s *InstancePollerSuite) assertMachineWatcherFails(c *gc.C, watchFacadeName string, getWatcherFn func() (params.StringsWatchResult, error)) { 190 // Force the Changes() method of the mock watcher to return a 191 // closed channel by setting an error. 192 s.st.SetErrors(errors.Errorf("boom")) 193 194 result, err := getWatcherFn() 195 c.Assert(err, gc.ErrorMatches, "cannot obtain initial model machines: boom") 196 c.Assert(result, jc.DeepEquals, params.StringsWatchResult{}) 197 198 c.Assert(s.resources.Count(), gc.Equals, 0) // no watcher registered 199 s.st.CheckCallNames(c, watchFacadeName) 200 } 201 202 func (s *InstancePollerSuite) assertMachineWatcherSucceeds(c *gc.C, watchFacadeName string, getWatcherFn func() (params.StringsWatchResult, error)) { 203 // Add a couple of machines. 204 s.st.SetMachineInfo(c, machineInfo{id: "2"}) 205 s.st.SetMachineInfo(c, machineInfo{id: "1"}) 206 207 expectedResult := params.StringsWatchResult{ 208 Error: nil, 209 StringsWatcherId: "1", 210 Changes: []string{"1", "2"}, // initial event (sorted ids) 211 } 212 result, err := getWatcherFn() 213 c.Assert(err, jc.ErrorIsNil) 214 c.Assert(result, jc.DeepEquals, expectedResult) 215 216 // Verify the watcher resource was registered. 217 c.Assert(s.resources.Count(), gc.Equals, 1) 218 resource1 := s.resources.Get("1") 219 defer func() { 220 if resource1 != nil { 221 statetesting.AssertStop(c, resource1) 222 } 223 }() 224 225 // Check that the watcher has consumed the initial event 226 wc1 := statetesting.NewStringsWatcherC(c, resource1.(state.StringsWatcher)) 227 wc1.AssertNoChange() 228 229 s.st.CheckCallNames(c, watchFacadeName) 230 231 // Add another watcher to verify events coalescence. 232 result, err = getWatcherFn() 233 c.Assert(err, jc.ErrorIsNil) 234 expectedResult.StringsWatcherId = "2" 235 c.Assert(result, jc.DeepEquals, expectedResult) 236 s.st.CheckCallNames(c, watchFacadeName, watchFacadeName) 237 c.Assert(s.resources.Count(), gc.Equals, 2) 238 resource2 := s.resources.Get("2") 239 defer statetesting.AssertStop(c, resource2) 240 wc2 := statetesting.NewStringsWatcherC(c, resource2.(state.StringsWatcher)) 241 wc2.AssertNoChange() 242 243 // Remove machine 1, check it's reported. 244 s.st.RemoveMachine(c, "1") 245 wc1.AssertChangeInSingleEvent("1") 246 247 // Make separate changes, check they're combined. 248 s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying}) 249 s.st.SetMachineInfo(c, machineInfo{id: "3"}) 250 s.st.RemoveMachine(c, "42") // ignored 251 wc1.AssertChangeInSingleEvent("2", "3") 252 wc2.AssertChangeInSingleEvent("1", "2", "3") 253 254 // Stop the first watcher and assert its changes chan is closed. 255 c.Assert(resource1.Stop(), jc.ErrorIsNil) 256 wc1.AssertClosed() 257 resource1 = nil 258 } 259 260 func (s *InstancePollerSuite) TestLifeSuccess(c *gc.C) { 261 s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive}) 262 s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dying}) 263 264 result, err := s.api.Life(s.mixedEntities) 265 c.Assert(err, jc.ErrorIsNil) 266 c.Assert(result, jc.DeepEquals, params.LifeResults{ 267 Results: []params.LifeResult{ 268 {Life: life.Alive}, 269 {Life: life.Dying}, 270 {Error: apiservertesting.NotFoundError("machine 42")}, 271 {Error: apiservertesting.ErrUnauthorized}, 272 {Error: apiservertesting.ErrUnauthorized}, 273 {Error: apiservertesting.ErrUnauthorized}, 274 {Error: apiservertesting.ErrUnauthorized}, 275 {Error: apiservertesting.ErrUnauthorized}, 276 }}, 277 ) 278 279 s.st.CheckFindEntityCall(c, 0, "1") 280 s.st.CheckCall(c, 1, "Life") 281 s.st.CheckFindEntityCall(c, 2, "2") 282 s.st.CheckCall(c, 3, "Life") 283 s.st.CheckFindEntityCall(c, 4, "42") 284 } 285 286 func (s *InstancePollerSuite) TestLifeFailure(c *gc.C) { 287 s.st.SetErrors( 288 errors.New("pow!"), // m1 := FindEntity("1"); Life not called 289 nil, // m2 := FindEntity("2") 290 errors.New("FAIL"), // m2.Life() - unused 291 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 292 ) 293 s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Alive}) 294 s.st.SetMachineInfo(c, machineInfo{id: "2", life: state.Dead}) 295 s.st.SetMachineInfo(c, machineInfo{id: "3", life: state.Dying}) 296 297 result, err := s.api.Life(s.machineEntities) 298 c.Assert(err, jc.ErrorIsNil) 299 c.Assert(result, jc.DeepEquals, params.LifeResults{ 300 Results: []params.LifeResult{ 301 {Error: apiservertesting.ServerError("pow!")}, 302 {Life: life.Dead}, 303 {Error: apiservertesting.NotProvisionedError("42")}, 304 }}, 305 ) 306 307 s.st.CheckFindEntityCall(c, 0, "1") 308 s.st.CheckFindEntityCall(c, 1, "2") 309 s.st.CheckCall(c, 2, "Life") 310 s.st.CheckFindEntityCall(c, 3, "3") 311 } 312 313 func (s *InstancePollerSuite) TestInstanceIdSuccess(c *gc.C) { 314 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: "i-foo"}) 315 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: ""}) 316 317 result, err := s.api.InstanceId(s.mixedEntities) 318 c.Assert(err, jc.ErrorIsNil) 319 c.Assert(result, jc.DeepEquals, params.StringResults{ 320 Results: []params.StringResult{ 321 {Result: "i-foo"}, 322 {Result: ""}, 323 {Error: apiservertesting.NotFoundError("machine 42")}, 324 {Error: apiservertesting.ErrUnauthorized}, 325 {Error: apiservertesting.ErrUnauthorized}, 326 {Error: apiservertesting.ErrUnauthorized}, 327 {Error: apiservertesting.ErrUnauthorized}, 328 {Error: apiservertesting.ErrUnauthorized}, 329 }}, 330 ) 331 332 s.st.CheckFindEntityCall(c, 0, "1") 333 s.st.CheckCall(c, 1, "InstanceId") 334 s.st.CheckFindEntityCall(c, 2, "2") 335 s.st.CheckCall(c, 3, "InstanceId") 336 s.st.CheckFindEntityCall(c, 4, "42") 337 } 338 339 func (s *InstancePollerSuite) TestInstanceIdFailure(c *gc.C) { 340 s.st.SetErrors( 341 errors.New("pow!"), // m1 := FindEntity("1"); InstanceId not called 342 nil, // m2 := FindEntity("2") 343 errors.New("FAIL"), // m2.InstanceId() 344 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 345 ) 346 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceId: ""}) 347 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceId: "i-bar"}) 348 349 result, err := s.api.InstanceId(s.machineEntities) 350 c.Assert(err, jc.ErrorIsNil) 351 c.Assert(result, jc.DeepEquals, params.StringResults{ 352 Results: []params.StringResult{ 353 {Error: apiservertesting.ServerError("pow!")}, 354 {Error: apiservertesting.ServerError("FAIL")}, 355 {Error: apiservertesting.NotProvisionedError("42")}, 356 }}, 357 ) 358 359 s.st.CheckFindEntityCall(c, 0, "1") 360 s.st.CheckFindEntityCall(c, 1, "2") 361 s.st.CheckCall(c, 2, "InstanceId") 362 s.st.CheckFindEntityCall(c, 3, "3") 363 } 364 365 func (s *InstancePollerSuite) TestStatusSuccess(c *gc.C) { 366 now := time.Now() 367 s1 := status.StatusInfo{ 368 Status: status.Error, 369 Message: "not really", 370 Data: map[string]interface{}{ 371 "price": 4.2, 372 "bool": false, 373 "bar": []string{"a", "b"}, 374 }, 375 Since: &now, 376 } 377 s2 := status.StatusInfo{} 378 s.st.SetMachineInfo(c, machineInfo{id: "1", status: s1}) 379 s.st.SetMachineInfo(c, machineInfo{id: "2", status: s2}) 380 381 result, err := s.api.Status(s.mixedEntities) 382 c.Assert(err, jc.ErrorIsNil) 383 c.Assert(result, jc.DeepEquals, params.StatusResults{ 384 Results: []params.StatusResult{ 385 { 386 Status: status.Error.String(), 387 Info: s1.Message, 388 Data: s1.Data, 389 Since: s1.Since, 390 }, 391 {Status: "", Info: "", Data: nil, Since: nil}, 392 {Error: apiservertesting.NotFoundError("machine 42")}, 393 {Error: apiservertesting.ErrUnauthorized}, 394 {Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)}, 395 {Error: apiservertesting.ErrUnauthorized}, 396 {Error: apiservertesting.ServerError(`"" is not a valid tag`)}, 397 {Error: apiservertesting.ServerError(`"42" is not a valid tag`)}, 398 }}, 399 ) 400 401 s.st.CheckFindEntityCall(c, 0, "1") 402 s.st.CheckCall(c, 1, "Status") 403 s.st.CheckFindEntityCall(c, 2, "2") 404 s.st.CheckCall(c, 3, "Status") 405 s.st.CheckFindEntityCall(c, 4, "42") 406 } 407 408 func (s *InstancePollerSuite) TestStatusFailure(c *gc.C) { 409 s.st.SetErrors( 410 errors.New("pow!"), // m1 := FindEntity("1"); Status not called 411 nil, // m2 := FindEntity("2") 412 errors.New("FAIL"), // m2.Status() 413 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 414 ) 415 s.st.SetMachineInfo(c, machineInfo{id: "1"}) 416 s.st.SetMachineInfo(c, machineInfo{id: "2"}) 417 418 result, err := s.api.Status(s.machineEntities) 419 c.Assert(err, jc.ErrorIsNil) 420 c.Assert(result, jc.DeepEquals, params.StatusResults{ 421 Results: []params.StatusResult{ 422 {Error: apiservertesting.ServerError("pow!")}, 423 {Error: apiservertesting.ServerError("FAIL")}, 424 {Error: apiservertesting.NotProvisionedError("42")}, 425 }}, 426 ) 427 428 s.st.CheckFindEntityCall(c, 0, "1") 429 s.st.CheckFindEntityCall(c, 1, "2") 430 s.st.CheckCall(c, 2, "Status") 431 s.st.CheckFindEntityCall(c, 3, "3") 432 } 433 434 func (s *InstancePollerSuite) TestProviderAddressesSuccess(c *gc.C) { 435 addrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8") 436 s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: addrs}) 437 s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil}) 438 439 result, err := s.api.ProviderAddresses(s.mixedEntities) 440 c.Assert(err, jc.ErrorIsNil) 441 c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{ 442 Results: []params.MachineAddressesResult{ 443 {Addresses: toParamAddresses(addrs)}, 444 {Addresses: nil}, 445 {Error: apiservertesting.NotFoundError("machine 42")}, 446 {Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)}, 447 {Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)}, 448 {Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)}, 449 {Error: apiservertesting.ServerError(`"" is not a valid tag`)}, 450 {Error: apiservertesting.ServerError(`"42" is not a valid tag`)}, 451 }}, 452 ) 453 454 s.st.CheckMachineCall(c, 0, "1") 455 s.st.CheckCall(c, 1, "ProviderAddresses") 456 s.st.CheckCall(c, 2, "AllSpaceInfos") 457 s.st.CheckMachineCall(c, 3, "2") 458 s.st.CheckCall(c, 4, "ProviderAddresses") 459 s.st.CheckMachineCall(c, 5, "42") 460 } 461 462 func (s *InstancePollerSuite) TestProviderAddressesFailure(c *gc.C) { 463 s.st.SetErrors( 464 errors.New("pow!"), // m1 := FindEntity("1") 465 nil, // m2 := FindEntity("2") 466 errors.New("FAIL"), // m2.ProviderAddresses()- unused 467 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 468 ) 469 s.st.SetMachineInfo(c, machineInfo{id: "1"}) 470 s.st.SetMachineInfo(c, machineInfo{id: "2"}) 471 472 result, err := s.api.ProviderAddresses(s.machineEntities) 473 c.Assert(err, jc.ErrorIsNil) 474 c.Assert(result, jc.DeepEquals, params.MachineAddressesResults{ 475 Results: []params.MachineAddressesResult{ 476 {Error: apiservertesting.ServerError("pow!")}, 477 {Addresses: nil}, 478 {Error: apiservertesting.NotProvisionedError("42")}, 479 }}, 480 ) 481 482 s.st.CheckMachineCall(c, 0, "1") 483 s.st.CheckMachineCall(c, 1, "2") 484 s.st.CheckCall(c, 2, "ProviderAddresses") 485 s.st.CheckMachineCall(c, 3, "3") 486 } 487 488 func (s *InstancePollerSuite) TestSetProviderAddressesSuccess(c *gc.C) { 489 oldAddrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8") 490 newAddrs := network.SpaceAddresses{ 491 network.NewSpaceAddress("1.2.3.4", network.WithCIDR("1.2.3.0/24")), 492 network.NewSpaceAddress("8.4.4.8", network.WithCIDR("8.4.4.0/24")), 493 network.NewSpaceAddress("2001:db8::"), 494 } 495 496 s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs}) 497 s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil}) 498 499 result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{ 500 MachineAddresses: []params.MachineAddresses{ 501 {Tag: "machine-1", Addresses: nil}, 502 {Tag: "machine-2", Addresses: toParamAddresses(newAddrs)}, 503 {Tag: "machine-42"}, 504 {Tag: "application-unknown"}, 505 {Tag: "invalid-tag"}, 506 {Tag: "unit-missing-1"}, 507 {Tag: ""}, 508 {Tag: "42"}, 509 }}, 510 ) 511 c.Assert(err, jc.ErrorIsNil) 512 c.Assert(result, jc.DeepEquals, s.mixedErrorResults) 513 514 s.st.CheckMachineCall(c, 0, "1") 515 s.st.CheckSetProviderAddressesCall(c, 1, []network.SpaceAddress{}) 516 s.st.CheckMachineCall(c, 2, "2") 517 s.st.CheckCall(c, 3, "AllSpaceInfos") 518 s.st.CheckSetProviderAddressesCall(c, 4, newAddrs) 519 s.st.CheckMachineCall(c, 5, "42") 520 521 // Ensure machines were updated. 522 machine, err := s.st.Machine("1") 523 c.Assert(err, jc.ErrorIsNil) 524 c.Assert(machine.ProviderAddresses(), gc.HasLen, 0) 525 526 machine, err = s.st.Machine("2") 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(machine.ProviderAddresses(), jc.DeepEquals, newAddrs) 529 } 530 531 func (s *InstancePollerSuite) TestSetProviderAddressesFailure(c *gc.C) { 532 s.st.SetErrors( 533 errors.New("pow!"), // m1 := FindEntity("1") 534 nil, // m2 := FindEntity("2") 535 errors.New("FAIL"), // m2.SetProviderAddresses() 536 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 537 ) 538 oldAddrs := network.NewSpaceAddresses("0.1.2.3", "127.0.0.1", "8.8.8.8") 539 newAddrs := network.NewSpaceAddresses("1.2.3.4", "8.4.4.8", "2001:db8::") 540 s.st.SetMachineInfo(c, machineInfo{id: "1", providerAddresses: oldAddrs}) 541 s.st.SetMachineInfo(c, machineInfo{id: "2", providerAddresses: nil}) 542 543 result, err := s.api.SetProviderAddresses(params.SetMachinesAddresses{ 544 MachineAddresses: []params.MachineAddresses{ 545 {Tag: "machine-1"}, 546 {Tag: "machine-2", Addresses: toParamAddresses(newAddrs)}, 547 {Tag: "machine-3"}, 548 }}, 549 ) 550 c.Assert(err, jc.ErrorIsNil) 551 c.Check(result, jc.DeepEquals, s.machineErrorResults) 552 553 s.st.CheckMachineCall(c, 0, "1") 554 s.st.CheckMachineCall(c, 1, "2") 555 s.st.CheckCall(c, 2, "AllSpaceInfos") 556 s.st.CheckSetProviderAddressesCall(c, 3, newAddrs) 557 s.st.CheckMachineCall(c, 4, "3") 558 559 // Ensure machine 2 wasn't updated. 560 machine, err := s.st.Machine("2") 561 c.Assert(err, jc.ErrorIsNil) 562 c.Assert(machine.ProviderAddresses(), gc.HasLen, 0) 563 } 564 565 func toParamAddresses(addrs network.SpaceAddresses) []params.Address { 566 paramAddrs := make([]params.Address, len(addrs)) 567 for i, addr := range addrs { 568 paramAddrs[i] = params.Address{ 569 Value: addr.Value, 570 Type: string(addr.Type), 571 Scope: string(addr.Scope), 572 CIDR: addr.CIDR, 573 } 574 } 575 return paramAddrs 576 } 577 578 func (s *InstancePollerSuite) TestInstanceStatusSuccess(c *gc.C) { 579 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")}) 580 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")}) 581 582 result, err := s.api.InstanceStatus(s.mixedEntities) 583 c.Assert(err, jc.ErrorIsNil) 584 c.Assert(result, jc.DeepEquals, params.StatusResults{ 585 Results: []params.StatusResult{ 586 {Status: "foo"}, 587 {Status: ""}, 588 {Error: apiservertesting.NotFoundError("machine 42")}, 589 {Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)}, 590 {Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)}, 591 {Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)}, 592 {Error: apiservertesting.ServerError(`"" is not a valid tag`)}, 593 {Error: apiservertesting.ServerError(`"42" is not a valid tag`)}, 594 }, 595 }, 596 ) 597 598 s.st.CheckMachineCall(c, 0, "1") 599 s.st.CheckCall(c, 1, "InstanceStatus") 600 s.st.CheckMachineCall(c, 2, "2") 601 s.st.CheckCall(c, 3, "InstanceStatus") 602 s.st.CheckMachineCall(c, 4, "42") 603 } 604 605 func (s *InstancePollerSuite) TestInstanceStatusFailure(c *gc.C) { 606 s.st.SetErrors( 607 errors.New("pow!"), // m1 := FindEntity("1") 608 nil, // m2 := FindEntity("2") 609 errors.New("FAIL"), // m2.InstanceStatus() 610 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 611 ) 612 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")}) 613 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")}) 614 615 result, err := s.api.InstanceStatus(s.machineEntities) 616 c.Assert(err, jc.ErrorIsNil) 617 c.Assert(result, jc.DeepEquals, params.StatusResults{ 618 Results: []params.StatusResult{ 619 {Error: apiservertesting.ServerError("pow!")}, 620 {Error: apiservertesting.ServerError("FAIL")}, 621 {Error: apiservertesting.NotProvisionedError("42")}, 622 }}, 623 ) 624 625 s.st.CheckMachineCall(c, 0, "1") 626 s.st.CheckMachineCall(c, 1, "2") 627 s.st.CheckCall(c, 2, "InstanceStatus") 628 s.st.CheckMachineCall(c, 3, "3") 629 } 630 631 func (s *InstancePollerSuite) TestSetInstanceStatusSuccess(c *gc.C) { 632 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")}) 633 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")}) 634 635 result, err := s.api.SetInstanceStatus(params.SetStatus{ 636 Entities: []params.EntityStatusArgs{ 637 {Tag: "machine-1", Status: ""}, 638 {Tag: "machine-2", Status: "new status"}, 639 {Tag: "machine-42", Status: ""}, 640 {Tag: "application-unknown", Status: ""}, 641 {Tag: "invalid-tag", Status: ""}, 642 {Tag: "unit-missing-1", Status: ""}, 643 {Tag: "", Status: ""}, 644 {Tag: "42", Status: ""}, 645 }}, 646 ) 647 c.Assert(err, jc.ErrorIsNil) 648 c.Assert(result, jc.DeepEquals, s.mixedErrorResults) 649 650 now := s.clock.Now() 651 s.st.CheckMachineCall(c, 0, "1") 652 s.st.CheckCall(c, 1, "SetInstanceStatus", status.StatusInfo{Status: "", Since: &now}) 653 s.st.CheckMachineCall(c, 2, "2") 654 s.st.CheckCall(c, 3, "SetInstanceStatus", status.StatusInfo{Status: "new status", Since: &now}) 655 s.st.CheckMachineCall(c, 4, "42") 656 657 // Ensure machines were updated. 658 machine, err := s.st.Machine("1") 659 c.Assert(err, jc.ErrorIsNil) 660 // TODO (perrito666) there should not be an empty StatusInfo here, 661 // this is certainly a smell. 662 setStatus, err := machine.InstanceStatus() 663 c.Assert(err, jc.ErrorIsNil) 664 setStatus.Since = nil 665 c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{}) 666 667 machine, err = s.st.Machine("2") 668 c.Assert(err, jc.ErrorIsNil) 669 setStatus, err = machine.InstanceStatus() 670 c.Assert(err, jc.ErrorIsNil) 671 setStatus.Since = nil 672 c.Assert(setStatus, gc.DeepEquals, status.StatusInfo{Status: "new status"}) 673 } 674 675 func (s *InstancePollerSuite) TestSetInstanceStatusFailure(c *gc.C) { 676 s.st.SetErrors( 677 errors.New("pow!"), // m1 := FindEntity("1") 678 nil, // m2 := FindEntity("2") 679 errors.New("FAIL"), // m2.SetInstanceStatus() 680 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 681 ) 682 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")}) 683 s.st.SetMachineInfo(c, machineInfo{id: "2", instanceStatus: statusInfo("")}) 684 685 result, err := s.api.SetInstanceStatus(params.SetStatus{ 686 Entities: []params.EntityStatusArgs{ 687 {Tag: "machine-1", Status: "new"}, 688 {Tag: "machine-2", Status: "invalid"}, 689 {Tag: "machine-3", Status: ""}, 690 }}, 691 ) 692 c.Assert(err, jc.ErrorIsNil) 693 c.Assert(result, jc.DeepEquals, s.machineErrorResults) 694 695 s.st.CheckMachineCall(c, 0, "1") 696 s.st.CheckMachineCall(c, 1, "2") 697 now := s.clock.Now() 698 s.st.CheckCall(c, 2, "SetInstanceStatus", status.StatusInfo{Status: "invalid", Since: &now}) 699 s.st.CheckMachineCall(c, 3, "3") 700 } 701 702 func (s *InstancePollerSuite) TestAreManuallyProvisionedSuccess(c *gc.C) { 703 s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true}) 704 s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false}) 705 706 result, err := s.api.AreManuallyProvisioned(s.mixedEntities) 707 c.Assert(err, jc.ErrorIsNil) 708 c.Assert(result, jc.DeepEquals, params.BoolResults{ 709 Results: []params.BoolResult{ 710 {Result: true}, 711 {Result: false}, 712 {Error: apiservertesting.NotFoundError("machine 42")}, 713 {Error: apiservertesting.ServerError(`"application-unknown" is not a valid machine tag`)}, 714 {Error: apiservertesting.ServerError(`"invalid-tag" is not a valid tag`)}, 715 {Error: apiservertesting.ServerError(`"unit-missing-1" is not a valid machine tag`)}, 716 {Error: apiservertesting.ServerError(`"" is not a valid tag`)}, 717 {Error: apiservertesting.ServerError(`"42" is not a valid tag`)}, 718 }}, 719 ) 720 721 s.st.CheckMachineCall(c, 0, "1") 722 s.st.CheckCall(c, 1, "IsManual") 723 s.st.CheckMachineCall(c, 2, "2") 724 s.st.CheckCall(c, 3, "IsManual") 725 s.st.CheckMachineCall(c, 4, "42") 726 } 727 728 func (s *InstancePollerSuite) TestAreManuallyProvisionedFailure(c *gc.C) { 729 s.st.SetErrors( 730 errors.New("pow!"), // m1 := FindEntity("1") 731 nil, // m2 := FindEntity("2") 732 errors.New("FAIL"), // m2.IsManual() 733 errors.NotProvisionedf("machine 42"), // FindEntity("3") (ensure wrapping is preserved) 734 ) 735 s.st.SetMachineInfo(c, machineInfo{id: "1", isManual: true}) 736 s.st.SetMachineInfo(c, machineInfo{id: "2", isManual: false}) 737 738 result, err := s.api.AreManuallyProvisioned(s.machineEntities) 739 c.Assert(err, jc.ErrorIsNil) 740 c.Assert(result, jc.DeepEquals, params.BoolResults{ 741 Results: []params.BoolResult{ 742 {Error: apiservertesting.ServerError("pow!")}, 743 {Error: apiservertesting.ServerError("FAIL")}, 744 {Error: apiservertesting.NotProvisionedError("42")}, 745 }}, 746 ) 747 748 s.st.CheckMachineCall(c, 0, "1") 749 s.st.CheckMachineCall(c, 1, "2") 750 s.st.CheckCall(c, 2, "IsManual") 751 s.st.CheckMachineCall(c, 3, "3") 752 } 753 754 func (s *InstancePollerSuite) TestSetProviderNetworkConfigSuccess(c *gc.C) { 755 s.setDefaultSpaceInfo() 756 757 s.st.SetMachineInfo(c, machineInfo{id: "1", instanceStatus: statusInfo("foo")}) 758 759 results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 760 Args: []params.ProviderNetworkConfig{ 761 { 762 Tag: "machine-1", 763 Configs: []params.NetworkConfig{ 764 { 765 // TODO (manadart 2021-05-31): This tests that we 766 // consider the individual CIDRs and not the deprecated 767 // CIDR for the device. 768 // Remove for Juju 3/4. 769 CIDR: "10.0.0.0/24", 770 Addresses: []params.Address{ 771 { 772 Value: "10.0.0.42", 773 Scope: "local-cloud", 774 CIDR: "10.0.0.0/24", 775 }, 776 { 777 Value: "10.73.37.110", 778 Scope: "local-cloud", 779 CIDR: "10.73.37.0/24", 780 }, 781 // Resolved by provider's space ID. 782 { 783 Value: "10.73.37.111", 784 Scope: "local-cloud", 785 ProviderSpaceID: "my-space-on-maas", 786 }, 787 // This address does not match any of the CIDRs in 788 // the known spaces; we expect this to end up in 789 // alpha space. 790 { 791 Value: "192.168.0.1", 792 Scope: "local-cloud", 793 }, 794 }, 795 ShadowAddresses: []params.Address{ 796 { 797 Value: "1.1.1.42", 798 Scope: "public", 799 }, 800 }, 801 }, 802 }, 803 }, 804 }, 805 }) 806 c.Assert(err, jc.ErrorIsNil) 807 c.Assert(results.Results, gc.HasLen, 1) 808 result := results.Results[0] 809 c.Assert(result.Modified, jc.IsTrue) 810 c.Assert(result.Addresses, gc.HasLen, 5) 811 c.Assert(result.Addresses[0], gc.DeepEquals, params.Address{ 812 Value: "10.0.0.42", 813 Type: "ipv4", 814 Scope: "local-cloud", 815 SpaceName: "space1", 816 }) 817 c.Assert(result.Addresses[1], gc.DeepEquals, params.Address{ 818 Value: "10.73.37.110", 819 Type: "ipv4", 820 Scope: "local-cloud", 821 SpaceName: "my-space-on-maas", 822 }) 823 c.Assert(result.Addresses[2], gc.DeepEquals, params.Address{ 824 Value: "10.73.37.111", 825 Type: "ipv4", 826 Scope: "local-cloud", 827 SpaceName: "my-space-on-maas", 828 }) 829 c.Assert(result.Addresses[3], gc.DeepEquals, params.Address{ 830 Value: "192.168.0.1", 831 Type: "ipv4", 832 Scope: "local-cloud", 833 SpaceName: "alpha", 834 }) 835 c.Assert(result.Addresses[4], gc.DeepEquals, params.Address{ 836 Value: "1.1.1.42", 837 Type: "ipv4", 838 Scope: "public", 839 SpaceName: "alpha", 840 }) 841 842 machine, err := s.st.Machine("1") 843 c.Assert(err, jc.ErrorIsNil) 844 providerAddrs := machine.ProviderAddresses() 845 846 c.Assert(providerAddrs, gc.DeepEquals, network.SpaceAddresses{ 847 makeSpaceAddress("10.0.0.42", network.ScopeCloudLocal, "1"), 848 makeSpaceAddress("10.73.37.110", network.ScopeCloudLocal, "2"), 849 makeSpaceAddress("10.73.37.111", network.ScopeCloudLocal, "2"), 850 makeSpaceAddress("192.168.0.1", network.ScopeCloudLocal, network.AlphaSpaceId), 851 makeSpaceAddress("1.1.1.42", network.ScopePublic, network.AlphaSpaceId), 852 }) 853 } 854 855 func (s *InstancePollerSuite) TestSetProviderNetworkConfigNoChange(c *gc.C) { 856 s.setDefaultSpaceInfo() 857 858 s.st.SetMachineInfo(c, machineInfo{ 859 id: "1", 860 instanceStatus: statusInfo("foo"), 861 providerAddresses: network.SpaceAddresses{ 862 makeSpaceAddress("10.0.0.42", network.ScopeCloudLocal, "1"), 863 makeSpaceAddress("10.73.37.111", network.ScopeCloudLocal, "2"), 864 makeSpaceAddress("192.168.0.1", network.ScopeCloudLocal, network.AlphaSpaceId), 865 makeSpaceAddress("1.1.1.42", network.ScopePublic, network.AlphaSpaceId), 866 }, 867 }) 868 869 results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 870 Args: []params.ProviderNetworkConfig{ 871 { 872 Tag: "machine-1", 873 Configs: []params.NetworkConfig{ 874 { 875 Addresses: []params.Address{ 876 { 877 Value: "10.0.0.42", 878 Scope: "local-cloud", 879 }, 880 { 881 Value: "10.73.37.111", 882 Scope: "local-cloud", 883 ProviderSpaceID: "my-space-on-maas", 884 }, 885 // This address does not match any of the CIDRs in 886 // the known spaces; we expect this to end up in 887 // alpha space. 888 { 889 Value: "192.168.0.1", 890 Scope: "local-cloud", 891 }, 892 }, 893 ShadowAddresses: []params.Address{ 894 { 895 Value: "1.1.1.42", 896 Scope: "public", 897 }, 898 }, 899 }, 900 }, 901 }, 902 }, 903 }) 904 c.Assert(err, jc.ErrorIsNil) 905 c.Assert(results.Results, gc.HasLen, 1) 906 result := results.Results[0] 907 c.Assert(result.Modified, jc.IsFalse) 908 c.Assert(result.Addresses, gc.HasLen, 4) 909 c.Assert(result.Addresses[0], gc.DeepEquals, params.Address{ 910 Value: "10.0.0.42", 911 Type: "ipv4", 912 Scope: "local-cloud", 913 SpaceName: "space1", 914 }) 915 c.Assert(result.Addresses[1], gc.DeepEquals, params.Address{ 916 Value: "10.73.37.111", 917 Type: "ipv4", 918 Scope: "local-cloud", 919 SpaceName: "my-space-on-maas", 920 }) 921 c.Assert(result.Addresses[2], gc.DeepEquals, params.Address{ 922 Value: "192.168.0.1", 923 Type: "ipv4", 924 Scope: "local-cloud", 925 SpaceName: "alpha", 926 }) 927 c.Assert(result.Addresses[3], gc.DeepEquals, params.Address{ 928 Value: "1.1.1.42", 929 Type: "ipv4", 930 Scope: "public", 931 SpaceName: "alpha", 932 }) 933 } 934 935 func (s *InstancePollerSuite) TestSetProviderNetworkConfigNotAlive(c *gc.C) { 936 s.st.SetMachineInfo(c, machineInfo{id: "1", life: state.Dying}) 937 938 results, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 939 Args: []params.ProviderNetworkConfig{{ 940 Tag: "machine-1", 941 Configs: []params.NetworkConfig{{ 942 Addresses: []params.Address{{Value: "10.0.0.42", Scope: "local-cloud"}}, 943 }}, 944 }}, 945 }) 946 c.Assert(err, jc.ErrorIsNil) 947 c.Check(results, jc.DeepEquals, params.SetProviderNetworkConfigResults{ 948 Results: []params.SetProviderNetworkConfigResult{{}}, 949 }) 950 951 // We should just return after seeing that the machine is dying. 952 s.st.Stub.CheckCallNames(c, "AllSpaceInfos", "Machine", "Life", "Id") 953 } 954 955 func (s *InstancePollerSuite) TestSetProviderNetworkConfigRelinquishUnseen(c *gc.C) { 956 ctrl := gomock.NewController(c) 957 defer ctrl.Finish() 958 959 s.setDefaultSpaceInfo() 960 961 // Hardware address not matched. 962 dev := mocks.NewMockLinkLayerDevice(ctrl) 963 dExp := dev.EXPECT() 964 dExp.MACAddress().Return("01:01:01:01:01:01").MinTimes(1) 965 dExp.Name().Return("eth0").MinTimes(1) 966 dExp.SetProviderIDOps(network.Id("")).Return([]txn.Op{{C: "dev-provider-id"}}, nil) 967 968 // Address should be set back to machine origin. 969 addr := mocks.NewMockLinkLayerAddress(ctrl) 970 addr.EXPECT().DeviceName().Return("eth0") 971 addr.EXPECT().SetOriginOps(network.OriginMachine).Return([]txn.Op{{C: "address-origin-manual"}}) 972 973 s.st.SetMachineInfo(c, machineInfo{ 974 id: "1", 975 instanceStatus: statusInfo("foo"), 976 linkLayerDevices: []networkingcommon.LinkLayerDevice{dev}, 977 addresses: []networkingcommon.LinkLayerAddress{addr}, 978 }) 979 980 result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 981 Args: []params.ProviderNetworkConfig{ 982 { 983 Tag: "machine-1", 984 Configs: []params.NetworkConfig{{MACAddress: "00:00:00:00:00:00"}}, 985 }, 986 }, 987 }) 988 c.Assert(err, jc.ErrorIsNil) 989 c.Assert(result.Results, gc.HasLen, 1) 990 c.Assert(result.Results[0].Error, gc.IsNil) 991 992 var buildCalled bool 993 for _, call := range s.st.Calls() { 994 if call.FuncName == "ApplyOperation.Build" { 995 buildCalled = true 996 c.Check(call.Args, gc.DeepEquals, []interface{}{[]txn.Op{ 997 {C: "machine-alive"}, 998 {C: "dev-provider-id"}, 999 {C: "address-origin-manual"}, 1000 }}) 1001 } 1002 } 1003 c.Assert(buildCalled, jc.IsTrue) 1004 } 1005 1006 func (s *InstancePollerSuite) TestSetProviderNetworkClaimProviderOrigin(c *gc.C) { 1007 ctrl := gomock.NewController(c) 1008 defer ctrl.Finish() 1009 1010 s.setDefaultSpaceInfo() 1011 1012 // Hardware address will match; provider ID will be set. 1013 dev := mocks.NewMockLinkLayerDevice(ctrl) 1014 dExp := dev.EXPECT() 1015 dExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1) 1016 dExp.Name().Return("eth0").MinTimes(1) 1017 dExp.ProviderID().Return(network.Id("")) 1018 dExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil) 1019 1020 // Address matched on device/value will have provider IDs set. 1021 addr := mocks.NewMockLinkLayerAddress(ctrl) 1022 aExp := addr.EXPECT() 1023 aExp.DeviceName().Return("eth0") 1024 aExp.Value().Return("10.0.0.42") 1025 aExp.SetProviderIDOps(network.Id("p-addr")).Return([]txn.Op{{C: "addr-provider-id"}}, nil) 1026 aExp.SetProviderNetIDsOps(network.Id("p-net"), network.Id("p-sub")).Return([]txn.Op{{C: "addr-provider-net-ids"}}) 1027 1028 s.st.SetMachineInfo(c, machineInfo{ 1029 id: "1", 1030 instanceStatus: statusInfo("foo"), 1031 linkLayerDevices: []networkingcommon.LinkLayerDevice{dev}, 1032 addresses: []networkingcommon.LinkLayerAddress{addr}, 1033 }) 1034 1035 result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 1036 Args: []params.ProviderNetworkConfig{ 1037 { 1038 Tag: "machine-1", 1039 Configs: []params.NetworkConfig{ 1040 { 1041 // This should still be matched based on hardware address. 1042 InterfaceName: "", 1043 MACAddress: "00:00:00:00:00:00", 1044 ProviderId: "p-dev", 1045 ProviderAddressId: "p-addr", 1046 ProviderNetworkId: "p-net", 1047 ProviderSubnetId: "p-sub", 1048 CIDR: "10.0.0.0/24", 1049 Addresses: []params.Address{{Value: "10.0.0.42"}}, 1050 }, 1051 { 1052 // A duplicate (MAC and addresses) should make no difference. 1053 InterfaceName: "", 1054 MACAddress: "00:00:00:00:00:00", 1055 ProviderId: "p-dev", 1056 ProviderAddressId: "p-addr", 1057 ProviderNetworkId: "p-net", 1058 ProviderSubnetId: "p-sub", 1059 CIDR: "10.0.0.0/24", 1060 Addresses: []params.Address{{Value: "10.0.0.42"}}, 1061 }, 1062 }, 1063 }, 1064 }, 1065 }) 1066 c.Assert(err, jc.ErrorIsNil) 1067 c.Assert(result.Results, gc.HasLen, 1) 1068 c.Assert(result.Results[0].Error, gc.IsNil) 1069 1070 var buildCalled bool 1071 for _, call := range s.st.Calls() { 1072 if call.FuncName == "ApplyOperation.Build" { 1073 buildCalled = true 1074 c.Check(call.Args, gc.DeepEquals, []interface{}{[]txn.Op{ 1075 {C: "machine-alive"}, 1076 {C: "dev-provider-id"}, 1077 {C: "addr-provider-id"}, 1078 {C: "addr-provider-net-ids"}, 1079 }}) 1080 } 1081 } 1082 c.Assert(buildCalled, jc.IsTrue) 1083 } 1084 1085 func (s *InstancePollerSuite) TestSetProviderNetworkProviderIDGoesToEthernetDev(c *gc.C) { 1086 ctrl := gomock.NewController(c) 1087 defer ctrl.Finish() 1088 1089 s.setDefaultSpaceInfo() 1090 1091 // Ethernet device will have the provider ID set. 1092 ethDev := mocks.NewMockLinkLayerDevice(ctrl) 1093 ethExp := ethDev.EXPECT() 1094 ethExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1) 1095 ethExp.Name().Return("eth0").MinTimes(1) 1096 ethExp.ProviderID().Return(network.Id("")).MinTimes(1) 1097 ethExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil) 1098 1099 // Bridge has the same MAC, but will not get the provider ID. 1100 brDev := mocks.NewMockLinkLayerDevice(ctrl) 1101 brExp := brDev.EXPECT() 1102 brExp.MACAddress().Return("00:00:00:00:00:00").MinTimes(1) 1103 brExp.Name().Return("br-eth0").AnyTimes() 1104 brExp.Type().Return(network.BridgeDevice) 1105 1106 s.st.SetMachineInfo(c, machineInfo{ 1107 id: "1", 1108 instanceStatus: statusInfo("foo"), 1109 linkLayerDevices: []networkingcommon.LinkLayerDevice{ethDev, brDev}, 1110 addresses: []networkingcommon.LinkLayerAddress{}, 1111 }) 1112 1113 result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 1114 Args: []params.ProviderNetworkConfig{ 1115 { 1116 Tag: "machine-1", 1117 Configs: []params.NetworkConfig{ 1118 { 1119 // This should still be matched based on hardware address. 1120 InterfaceName: "", 1121 MACAddress: "00:00:00:00:00:00", 1122 ProviderId: "p-dev", 1123 }, 1124 }, 1125 }, 1126 }, 1127 }) 1128 c.Assert(err, jc.ErrorIsNil) 1129 c.Assert(result.Results, gc.HasLen, 1) 1130 c.Assert(result.Results[0].Error, gc.IsNil) 1131 1132 var buildCalled bool 1133 for _, call := range s.st.Calls() { 1134 if call.FuncName == "ApplyOperation.Build" { 1135 buildCalled = true 1136 } 1137 } 1138 c.Assert(buildCalled, jc.IsTrue) 1139 } 1140 1141 func (s *InstancePollerSuite) TestSetProviderNetworkProviderIDMultipleRefsError(c *gc.C) { 1142 ctrl := gomock.NewController(c) 1143 defer ctrl.Finish() 1144 1145 s.setDefaultSpaceInfo() 1146 1147 ethDev := mocks.NewMockLinkLayerDevice(ctrl) 1148 ethExp := ethDev.EXPECT() 1149 ethExp.Name().Return("eth0").MinTimes(1) 1150 ethExp.ProviderID().Return(network.Id("")).MinTimes(1) 1151 ethExp.SetProviderIDOps(network.Id("p-dev")).Return([]txn.Op{{C: "dev-provider-id"}}, nil) 1152 1153 brDev := mocks.NewMockLinkLayerDevice(ctrl) 1154 brExp := brDev.EXPECT() 1155 brExp.Name().Return("br-eth0").AnyTimes() 1156 brExp.ProviderID().Return(network.Id("")).MinTimes(1) 1157 // Note no calls to SetProviderIDOps. 1158 1159 s.st.SetMachineInfo(c, machineInfo{ 1160 id: "1", 1161 instanceStatus: statusInfo("foo"), 1162 linkLayerDevices: []networkingcommon.LinkLayerDevice{ethDev, brDev}, 1163 addresses: []networkingcommon.LinkLayerAddress{}, 1164 }) 1165 1166 // Same provider ID for both. 1167 result, err := s.api.SetProviderNetworkConfig(params.SetProviderNetworkConfig{ 1168 Args: []params.ProviderNetworkConfig{ 1169 { 1170 Tag: "machine-1", 1171 Configs: []params.NetworkConfig{ 1172 { 1173 InterfaceName: "eth0", 1174 MACAddress: "aa:00:00:00:00:00", 1175 ProviderId: "p-dev", 1176 }, 1177 { 1178 InterfaceName: "br-eth0", 1179 MACAddress: "bb:00:00:00:00:00", 1180 ProviderId: "p-dev", 1181 }, 1182 }, 1183 }, 1184 }, 1185 }) 1186 1187 // The error is logged but not returned. 1188 c.Assert(err, jc.ErrorIsNil) 1189 c.Assert(result.Results, gc.HasLen, 1) 1190 c.Assert(result.Results[0].Error, gc.IsNil) 1191 1192 // But we should not have registered a successful call to Build. 1193 // This returns an error. 1194 var buildCalled bool 1195 for _, call := range s.st.Calls() { 1196 if call.FuncName == "ApplyOperation.Build" { 1197 buildCalled = true 1198 } 1199 } 1200 c.Assert(buildCalled, jc.IsFalse) 1201 } 1202 1203 func (s *InstancePollerSuite) setDefaultSpaceInfo() { 1204 s.st.SetSpaceInfo(network.SpaceInfos{ 1205 {ID: network.AlphaSpaceId, Name: network.AlphaSpaceName}, 1206 {ID: "1", Name: "space1", Subnets: []network.SubnetInfo{{CIDR: "10.0.0.0/24"}}}, 1207 {ID: "2", Name: "my-space-on-maas", ProviderId: "my-space-on-maas", Subnets: []network.SubnetInfo{{CIDR: "10.73.37.0/24"}}}, 1208 }) 1209 } 1210 1211 func makeSpaceAddress(ip string, scope network.Scope, spaceID string) network.SpaceAddress { 1212 addr := network.NewSpaceAddress(ip, network.WithScope(scope)) 1213 addr.SpaceID = spaceID 1214 return addr 1215 } 1216 1217 func statusInfo(st string) status.StatusInfo { 1218 return status.StatusInfo{Status: status.Status(st)} 1219 }