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: &params.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: &params.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: &params.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: &params.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: &params.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: &params.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  }