github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/status/status_test.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package status_test 5 6 import ( 7 "errors" 8 "time" 9 10 "github.com/juju/cmd" 11 "github.com/juju/cmd/cmdtesting" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/cmd/juju/status" 17 corestatus "github.com/juju/juju/core/status" 18 "github.com/juju/juju/testing" 19 ) 20 21 type MinimalStatusSuite struct { 22 testing.BaseSuite 23 24 statusapi *fakeStatusAPI 25 storageapi *mockListStorageAPI 26 clock *timeRecorder 27 } 28 29 var _ = gc.Suite(&MinimalStatusSuite{}) 30 31 func (s *MinimalStatusSuite) SetUpTest(c *gc.C) { 32 s.BaseSuite.SetUpTest(c) 33 s.statusapi = &fakeStatusAPI{ 34 result: ¶ms.FullStatus{ 35 Model: params.ModelStatusInfo{ 36 Name: "test", 37 CloudTag: "cloud-foo", 38 }, 39 }, 40 } 41 s.storageapi = &mockListStorageAPI{} 42 s.clock = &timeRecorder{} 43 s.SetModelAndController(c, "test", "admin/test") 44 } 45 46 func (s *MinimalStatusSuite) runStatus(c *gc.C, args ...string) (*cmd.Context, error) { 47 statusCmd := status.NewTestStatusCommand(s.statusapi, s.storageapi, s.clock) 48 return cmdtesting.RunCommand(c, statusCmd, args...) 49 } 50 51 func (s *MinimalStatusSuite) TestGoodCall(c *gc.C) { 52 _, err := s.runStatus(c) 53 c.Assert(err, jc.ErrorIsNil) 54 c.Assert(s.clock.waits, gc.HasLen, 0) 55 } 56 57 func (s *MinimalStatusSuite) TestGoodCallWithStorage(c *gc.C) { 58 context, err := s.runStatus(c, "--storage") 59 c.Assert(err, jc.ErrorIsNil) 60 c.Assert(s.clock.waits, gc.HasLen, 0) 61 62 obtainedValid := cmdtesting.Stdout(context) 63 c.Assert(obtainedValid, gc.Equals, ` 64 Model Controller Cloud/Region Version 65 test test foo 66 67 Storage Unit Storage id Type Pool Mountpoint Size Status Message 68 persistent/1 filesystem detached 69 postgresql/0 db-dir/1100 block 3.0MiB attached 70 transcode/0 db-dir/1000 block pending creating volume 71 transcode/0 shared-fs/0 filesystem radiance 1.0GiB attached 72 transcode/1 shared-fs/0 filesystem radiance 1.0GiB attached 73 74 `[1:]) 75 } 76 77 func (s *MinimalStatusSuite) TestRetryOnError(c *gc.C) { 78 s.statusapi.errors = []error{ 79 errors.New("boom"), 80 errors.New("splat"), 81 } 82 83 _, err := s.runStatus(c) 84 c.Assert(err, jc.ErrorIsNil) 85 delay := 100 * time.Millisecond 86 // Two delays of the default time. 87 c.Assert(s.clock.waits, jc.DeepEquals, []time.Duration{delay, delay}) 88 } 89 90 func (s *MinimalStatusSuite) TestRetryDelays(c *gc.C) { 91 s.statusapi.errors = []error{ 92 errors.New("boom"), 93 errors.New("splat"), 94 } 95 96 _, err := s.runStatus(c, "--retry-delay", "250ms") 97 c.Assert(err, jc.ErrorIsNil) 98 delay := 250 * time.Millisecond 99 c.Assert(s.clock.waits, jc.DeepEquals, []time.Duration{delay, delay}) 100 } 101 102 func (s *MinimalStatusSuite) TestRetryCount(c *gc.C) { 103 s.statusapi.errors = []error{ 104 errors.New("error 1"), 105 errors.New("error 2"), 106 errors.New("error 3"), 107 errors.New("error 4"), 108 errors.New("error 5"), 109 errors.New("error 6"), 110 errors.New("error 7"), 111 } 112 113 _, err := s.runStatus(c, "--retry-count", "5") 114 c.Assert(err.Error(), gc.Equals, "error 6") 115 // We expect five waits of the default duration. 116 delay := 100 * time.Millisecond 117 c.Assert(s.clock.waits, jc.DeepEquals, []time.Duration{delay, delay, delay, delay, delay}) 118 } 119 120 func (s *MinimalStatusSuite) TestRetryCountOfZero(c *gc.C) { 121 s.statusapi.errors = []error{ 122 errors.New("error 1"), 123 errors.New("error 2"), 124 errors.New("error 3"), 125 } 126 127 _, err := s.runStatus(c, "--retry-count", "0") 128 c.Assert(err.Error(), gc.Equals, "error 1") 129 // No delays. 130 c.Assert(s.clock.waits, gc.HasLen, 0) 131 } 132 133 type fakeStatusAPI struct { 134 result *params.FullStatus 135 errors []error 136 } 137 138 func (f *fakeStatusAPI) Status(patterns []string) (*params.FullStatus, error) { 139 if len(f.errors) > 0 { 140 err, rest := f.errors[0], f.errors[1:] 141 f.errors = rest 142 return nil, err 143 } 144 return f.result, nil 145 } 146 147 func (*fakeStatusAPI) Close() error { 148 return nil 149 } 150 151 type timeRecorder struct { 152 waits []time.Duration 153 result chan time.Time 154 } 155 156 func (r *timeRecorder) After(d time.Duration) <-chan time.Time { 157 r.waits = append(r.waits, d) 158 if r.result == nil { 159 // If we haven't yet, make a closed time channel so it immediately 160 // passes. 161 r.result = make(chan time.Time) 162 close(r.result) 163 } 164 return r.result 165 } 166 167 type mockListStorageAPI struct { 168 listErrors bool 169 listFilesystems func([]string) ([]params.FilesystemDetailsListResult, error) 170 listVolumes func([]string) ([]params.VolumeDetailsListResult, error) 171 omitPool bool 172 time time.Time 173 } 174 175 func (s *mockListStorageAPI) Close() error { 176 return nil 177 } 178 179 func (s *mockListStorageAPI) ListStorageDetails() ([]params.StorageDetails, error) { 180 if s.listErrors { 181 return nil, errors.New("list fails") 182 } 183 results := []params.StorageDetails{{ 184 StorageTag: "storage-db-dir-1000", 185 OwnerTag: "unit-transcode-0", 186 Kind: params.StorageKindBlock, 187 Status: params.EntityStatus{ 188 Status: corestatus.Pending, 189 Since: &s.time, 190 Info: "creating volume", 191 }, 192 Attachments: map[string]params.StorageAttachmentDetails{ 193 "unit-transcode-0": { 194 Location: "thither", 195 }, 196 }, 197 }, { 198 StorageTag: "storage-db-dir-1100", 199 OwnerTag: "unit-postgresql-0", 200 Kind: params.StorageKindBlock, 201 Life: "dying", 202 Status: params.EntityStatus{ 203 Status: corestatus.Attached, 204 Since: &s.time, 205 }, 206 Persistent: true, 207 Attachments: map[string]params.StorageAttachmentDetails{ 208 "unit-postgresql-0": { 209 Location: "hither", 210 Life: "dying", 211 }, 212 }, 213 }, { 214 StorageTag: "storage-shared-fs-0", 215 OwnerTag: "application-transcode", 216 Kind: params.StorageKindFilesystem, 217 Status: params.EntityStatus{ 218 Status: corestatus.Attached, 219 Since: &s.time, 220 }, 221 Persistent: true, 222 Attachments: map[string]params.StorageAttachmentDetails{ 223 "unit-transcode-0": { 224 Location: "there", 225 }, 226 "unit-transcode-1": { 227 Location: "here", 228 }, 229 }, 230 }, { 231 StorageTag: "storage-persistent-1", 232 Kind: params.StorageKindFilesystem, 233 Status: params.EntityStatus{ 234 Status: corestatus.Detached, 235 Since: &s.time, 236 }, 237 Persistent: true, 238 }} 239 return results, nil 240 } 241 242 func (s *mockListStorageAPI) ListFilesystems(machines []string) ([]params.FilesystemDetailsListResult, error) { 243 if s.listFilesystems != nil { 244 return s.listFilesystems(machines) 245 } 246 results := []params.FilesystemDetailsListResult{{Result: []params.FilesystemDetails{ 247 // filesystem 0/0 is attached to machine 0, assigned to 248 // storage db-dir/1001, which is attached to unit 249 // abc/0. 250 { 251 FilesystemTag: "filesystem-0-0", 252 VolumeTag: "volume-0-1", 253 Info: params.FilesystemInfo{ 254 FilesystemId: "provider-supplied-filesystem-0-0", 255 Size: 512, 256 }, 257 Life: "alive", 258 Status: createTestStatus(corestatus.Attached, "", s.time), 259 MachineAttachments: map[string]params.FilesystemAttachmentDetails{ 260 "machine-0": { 261 Life: "alive", 262 FilesystemAttachmentInfo: params.FilesystemAttachmentInfo{ 263 MountPoint: "/mnt/fuji", 264 }, 265 }, 266 }, 267 Storage: ¶ms.StorageDetails{ 268 StorageTag: "storage-db-dir-1001", 269 OwnerTag: "unit-abc-0", 270 Kind: params.StorageKindBlock, 271 Life: "alive", 272 Status: createTestStatus(corestatus.Attached, "", s.time), 273 Attachments: map[string]params.StorageAttachmentDetails{ 274 "unit-abc-0": { 275 StorageTag: "storage-db-dir-1001", 276 UnitTag: "unit-abc-0", 277 MachineTag: "machine-0", 278 Location: "/mnt/fuji", 279 }, 280 }, 281 }, 282 }, 283 // filesystem 1 is attaching to machine 0, but is not assigned 284 // to any storage. 285 { 286 FilesystemTag: "filesystem-1", 287 Info: params.FilesystemInfo{ 288 FilesystemId: "provider-supplied-filesystem-1", 289 Size: 2048, 290 }, 291 Status: createTestStatus(corestatus.Attaching, "failed to attach, will retry", s.time), 292 MachineAttachments: map[string]params.FilesystemAttachmentDetails{ 293 "machine-0": {}, 294 }, 295 }, 296 // filesystem 3 is due to be attached to machine 1, but is not 297 // assigned to any storage and has not yet been provisioned. 298 { 299 FilesystemTag: "filesystem-3", 300 Info: params.FilesystemInfo{ 301 Size: 42, 302 }, 303 Status: createTestStatus(corestatus.Pending, "", s.time), 304 MachineAttachments: map[string]params.FilesystemAttachmentDetails{ 305 "machine-1": {}, 306 }, 307 }, 308 // filesystem 2 is due to be attached to machine 1, but is not 309 // assigned to any storage. 310 { 311 FilesystemTag: "filesystem-2", 312 Info: params.FilesystemInfo{ 313 FilesystemId: "provider-supplied-filesystem-2", 314 Size: 3, 315 }, 316 Status: createTestStatus(corestatus.Attached, "", s.time), 317 MachineAttachments: map[string]params.FilesystemAttachmentDetails{ 318 "machine-1": { 319 FilesystemAttachmentInfo: params.FilesystemAttachmentInfo{ 320 MountPoint: "/mnt/zion", 321 }, 322 }, 323 }, 324 }, 325 // filesystem 4 is attached to machines 0 and 1, and is assigned 326 // to shared storage. 327 { 328 FilesystemTag: "filesystem-4", 329 Info: params.FilesystemInfo{ 330 FilesystemId: "provider-supplied-filesystem-4", 331 Pool: "radiance", 332 Size: 1024, 333 }, 334 Status: createTestStatus(corestatus.Attached, "", s.time), 335 MachineAttachments: map[string]params.FilesystemAttachmentDetails{ 336 "machine-0": { 337 FilesystemAttachmentInfo: params.FilesystemAttachmentInfo{ 338 MountPoint: "/mnt/doom", 339 ReadOnly: true, 340 }, 341 }, 342 "machine-1": { 343 FilesystemAttachmentInfo: params.FilesystemAttachmentInfo{ 344 MountPoint: "/mnt/huang", 345 ReadOnly: true, 346 }, 347 }, 348 }, 349 Storage: ¶ms.StorageDetails{ 350 StorageTag: "storage-shared-fs-0", 351 OwnerTag: "application-transcode", 352 Kind: params.StorageKindBlock, 353 Status: createTestStatus(corestatus.Attached, "", s.time), 354 Attachments: map[string]params.StorageAttachmentDetails{ 355 "unit-transcode-0": { 356 StorageTag: "storage-shared-fs-0", 357 UnitTag: "unit-transcode-0", 358 MachineTag: "machine-0", 359 Location: "/mnt/bits", 360 }, 361 "unit-transcode-1": { 362 StorageTag: "storage-shared-fs-0", 363 UnitTag: "unit-transcode-1", 364 MachineTag: "machine-1", 365 Location: "/mnt/pieces", 366 }, 367 }, 368 }, 369 }, { 370 // filesystem 5 is assigned to db-dir/1100, but is not yet 371 // attached to any machines. 372 FilesystemTag: "filesystem-5", 373 Info: params.FilesystemInfo{ 374 FilesystemId: "provider-supplied-filesystem-5", 375 Size: 3, 376 }, 377 Status: createTestStatus(corestatus.Attached, "", s.time), 378 Storage: ¶ms.StorageDetails{ 379 StorageTag: "storage-db-dir-1100", 380 OwnerTag: "unit-abc-0", 381 Kind: params.StorageKindBlock, 382 Life: "alive", 383 Status: createTestStatus(corestatus.Attached, "", s.time), 384 Attachments: map[string]params.StorageAttachmentDetails{ 385 "unit-abc-0": { 386 StorageTag: "storage-db-dir-1100", 387 UnitTag: "unit-abc-0", 388 Location: "/mnt/fuji", 389 }, 390 }, 391 }, 392 }, 393 }}} 394 if s.omitPool { 395 for _, result := range results { 396 for i, details := range result.Result { 397 details.Info.Pool = "" 398 result.Result[i] = details 399 } 400 } 401 } 402 return results, nil 403 } 404 405 func (s *mockListStorageAPI) ListVolumes(machines []string) ([]params.VolumeDetailsListResult, error) { 406 if s.listVolumes != nil { 407 return s.listVolumes(machines) 408 } 409 results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{ 410 // volume 0/0 is attached to machine 0, assigned to 411 // storage db-dir/1001, which is attached to unit 412 // abc/0. 413 { 414 VolumeTag: "volume-0-0", 415 Info: params.VolumeInfo{ 416 VolumeId: "provider-supplied-volume-0-0", 417 Pool: "radiance", 418 Size: 512, 419 }, 420 Life: "alive", 421 Status: createTestStatus(corestatus.Attached, "", s.time), 422 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 423 "machine-0": { 424 Life: "alive", 425 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 426 DeviceName: "loop0", 427 }, 428 }, 429 }, 430 Storage: ¶ms.StorageDetails{ 431 StorageTag: "storage-db-dir-1001", 432 OwnerTag: "unit-abc-0", 433 Kind: params.StorageKindBlock, 434 Life: "alive", 435 Status: createTestStatus(corestatus.Attached, "", s.time), 436 Attachments: map[string]params.StorageAttachmentDetails{ 437 "unit-abc-0": { 438 StorageTag: "storage-db-dir-1001", 439 UnitTag: "unit-abc-0", 440 MachineTag: "machine-0", 441 Location: "/dev/loop0", 442 }, 443 }, 444 }, 445 }, 446 // volume 1 is attaching to machine 0, but is not assigned 447 // to any storage. 448 { 449 VolumeTag: "volume-1", 450 Info: params.VolumeInfo{ 451 VolumeId: "provider-supplied-volume-1", 452 HardwareId: "serial blah blah", 453 Persistent: true, 454 Size: 2048, 455 }, 456 Status: createTestStatus(corestatus.Attaching, "failed to attach, will retry", s.time), 457 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 458 "machine-0": {}, 459 }, 460 }, 461 // volume 3 is due to be attached to machine 1, but is not 462 // assigned to any storage and has not yet been provisioned. 463 { 464 VolumeTag: "volume-3", 465 Info: params.VolumeInfo{ 466 Size: 42, 467 }, 468 Status: createTestStatus(corestatus.Pending, "", s.time), 469 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 470 "machine-1": {}, 471 }, 472 }, 473 // volume 2 is due to be attached to machine 1, but is not 474 // assigned to any storage and has not yet been provisioned. 475 { 476 VolumeTag: "volume-2", 477 Info: params.VolumeInfo{ 478 VolumeId: "provider-supplied-volume-2", 479 Size: 3, 480 }, 481 Status: createTestStatus(corestatus.Attached, "", s.time), 482 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 483 "machine-1": { 484 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 485 DeviceName: "xvdf1", 486 }, 487 }, 488 }, 489 }, 490 // volume 4 is attached to machines 0 and 1, and is assigned 491 // to shared storage. 492 { 493 VolumeTag: "volume-4", 494 Info: params.VolumeInfo{ 495 VolumeId: "provider-supplied-volume-4", 496 Persistent: true, 497 Size: 1024, 498 }, 499 Status: createTestStatus(corestatus.Attached, "", s.time), 500 MachineAttachments: map[string]params.VolumeAttachmentDetails{ 501 "machine-0": { 502 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 503 DeviceName: "xvdf2", 504 ReadOnly: true, 505 }, 506 }, 507 "machine-1": { 508 VolumeAttachmentInfo: params.VolumeAttachmentInfo{ 509 DeviceName: "xvdf3", 510 ReadOnly: true, 511 }, 512 }, 513 }, 514 Storage: ¶ms.StorageDetails{ 515 StorageTag: "storage-shared-fs-0", 516 OwnerTag: "application-transcode", 517 Kind: params.StorageKindBlock, 518 Status: createTestStatus(corestatus.Attached, "", s.time), 519 Attachments: map[string]params.StorageAttachmentDetails{ 520 "unit-transcode-0": { 521 StorageTag: "storage-shared-fs-0", 522 UnitTag: "unit-transcode-0", 523 MachineTag: "machine-0", 524 Location: "/mnt/bits", 525 }, 526 "unit-transcode-1": { 527 StorageTag: "storage-shared-fs-0", 528 UnitTag: "unit-transcode-1", 529 MachineTag: "machine-1", 530 Location: "/mnt/pieces", 531 }, 532 }, 533 }, 534 }, 535 }}} 536 if s.omitPool { 537 for _, result := range results { 538 for i, details := range result.Result { 539 details.Info.Pool = "" 540 result.Result[i] = details 541 } 542 } 543 } 544 return results, nil 545 } 546 547 func createTestStatus(testStatus corestatus.Status, message string, since time.Time) params.EntityStatus { 548 return params.EntityStatus{ 549 Status: testStatus, 550 Info: message, 551 Since: &since, 552 } 553 }