github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/storage/volumelist_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package storage_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"time"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/cmd/cmdtesting"
    12  	"github.com/juju/errors"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	goyaml "gopkg.in/yaml.v2"
    16  
    17  	"github.com/juju/juju/apiserver/params"
    18  	"github.com/juju/juju/cmd/juju/storage"
    19  	"github.com/juju/juju/core/status"
    20  )
    21  
    22  func (s *ListSuite) TestVolumeListEmpty(c *gc.C) {
    23  	s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) {
    24  		return nil, nil
    25  	}
    26  	s.assertValidVolumeList(
    27  		c,
    28  		[]string{"--format", "yaml"},
    29  		"",
    30  	)
    31  }
    32  
    33  func (s *ListSuite) TestVolumeListError(c *gc.C) {
    34  	s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) {
    35  		return nil, errors.New("just my luck")
    36  	}
    37  	context, err := s.runVolumeList(c, "--format", "yaml")
    38  	c.Assert(errors.Cause(err), gc.ErrorMatches, "just my luck")
    39  	s.assertUserFacingVolumeOutput(c, context, "", "")
    40  }
    41  
    42  func (s *ListSuite) TestVolumeListArgs(c *gc.C) {
    43  	var called bool
    44  	expectedArgs := []string{"a", "b", "c"}
    45  	s.mockAPI.listVolumes = func(arg []string) ([]params.VolumeDetailsListResult, error) {
    46  		c.Assert(arg, jc.DeepEquals, expectedArgs)
    47  		called = true
    48  		return nil, nil
    49  	}
    50  	s.assertValidVolumeList(
    51  		c,
    52  		append([]string{"--format", "yaml"}, expectedArgs...),
    53  		"",
    54  	)
    55  	c.Assert(called, jc.IsTrue)
    56  }
    57  
    58  func (s *ListSuite) TestVolumeListYaml(c *gc.C) {
    59  	s.assertUnmarshalledVolumeOutput(
    60  		c,
    61  		goyaml.Unmarshal,
    62  		"", // no error
    63  		"--format", "yaml")
    64  }
    65  
    66  func (s *ListSuite) TestVolumeListJSON(c *gc.C) {
    67  	s.assertUnmarshalledVolumeOutput(
    68  		c,
    69  		json.Unmarshal,
    70  		"", // no error
    71  		"--format", "json")
    72  }
    73  
    74  func (s *ListSuite) TestVolumeListWithErrorResults(c *gc.C) {
    75  	s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) {
    76  		var emptyMockAPI mockListAPI
    77  		results, _ := emptyMockAPI.ListVolumes(nil)
    78  		results = append(results, params.VolumeDetailsListResult{
    79  			Error: &params.Error{Message: "bad"},
    80  		})
    81  		results = append(results, params.VolumeDetailsListResult{
    82  			Error: &params.Error{Message: "ness"},
    83  		})
    84  		return results, nil
    85  	}
    86  	// we should see the error in stderr, but it should not
    87  	// otherwise affect the rendering of valid results.
    88  	s.assertUnmarshalledVolumeOutput(c, json.Unmarshal, "bad\nness\n", "--format", "json")
    89  	s.assertUnmarshalledVolumeOutput(c, goyaml.Unmarshal, "bad\nness\n", "--format", "yaml")
    90  }
    91  
    92  var expectedVolumeListTabular = `
    93  Machine  Unit         Storage id   Volume id  Provider Id                   Device  Size    State      Message
    94  0        abc/0        db-dir/1001  0/0        provider-supplied-volume-0-0  loop0   512MiB  attached   
    95  0        transcode/0  shared-fs/0  4          provider-supplied-volume-4    xvdf2   1.0GiB  attached   
    96  0                                  1          provider-supplied-volume-1            2.0GiB  attaching  failed to attach, will retry
    97  1        transcode/1  shared-fs/0  4          provider-supplied-volume-4    xvdf3   1.0GiB  attached   
    98  1                                  2          provider-supplied-volume-2    xvdf1   3.0MiB  attached   
    99  1                                  3                                                42MiB   pending    
   100  
   101  `[1:]
   102  
   103  func (s *ListSuite) TestVolumeListTabular(c *gc.C) {
   104  	s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular)
   105  
   106  	// Do it again, reversing the results returned by the API.
   107  	// We should get everything sorted in the appropriate order.
   108  	s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) {
   109  		var emptyMockAPI mockListAPI
   110  		results, _ := emptyMockAPI.ListVolumes(nil)
   111  		n := len(results)
   112  		for i := 0; i < n/2; i++ {
   113  			results[i], results[n-i-1] = results[n-i-1], results[i]
   114  		}
   115  		return results, nil
   116  	}
   117  	s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular)
   118  }
   119  
   120  var expectedCAASVolumeListTabular = `
   121  Unit     Storage id   Volume id  Provider Id                 Size    State     Message
   122  mysql/0  db-dir/1001  0          provider-supplied-volume-0  512MiB  attached  
   123  
   124  `[1:]
   125  
   126  func (s *ListSuite) TestCAASVolumeListTabular(c *gc.C) {
   127  	s.assertValidFilesystemList(c, []string{}, expectedFilesystemListTabular)
   128  
   129  	// Do it again, reversing the results returned by the API.
   130  	// We should get everything sorted in the appropriate order.
   131  	s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) {
   132  		results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{
   133  			{
   134  				VolumeTag: "volume-0",
   135  				Info: params.VolumeInfo{
   136  					VolumeId: "provider-supplied-volume-0",
   137  					Size:     512,
   138  				},
   139  				Life:   "alive",
   140  				Status: createTestStatus(status.Attached, "", s.mockAPI.time),
   141  				UnitAttachments: map[string]params.VolumeAttachmentDetails{
   142  					"unit-mysql-0": {
   143  						Life: "alive",
   144  						VolumeAttachmentInfo: params.VolumeAttachmentInfo{
   145  							ReadOnly: true,
   146  						},
   147  					},
   148  				},
   149  				Storage: &params.StorageDetails{
   150  					StorageTag: "storage-db-dir-1001",
   151  					OwnerTag:   "unit-abc-0",
   152  					Kind:       params.StorageKindBlock,
   153  					Life:       "alive",
   154  					Status:     createTestStatus(status.Attached, "", s.mockAPI.time),
   155  					Attachments: map[string]params.StorageAttachmentDetails{
   156  						"unit-mysql-0": {
   157  							StorageTag: "storage-db-dir-1001",
   158  							UnitTag:    "unit-abc-0",
   159  							MachineTag: "machine-0",
   160  							Location:   "/mnt/fuji",
   161  						},
   162  					},
   163  				},
   164  			},
   165  		}}}
   166  		return results, nil
   167  	}
   168  	s.assertValidVolumeList(c, []string{}, expectedCAASVolumeListTabular)
   169  }
   170  
   171  func (s *ListSuite) assertUnmarshalledVolumeOutput(c *gc.C, unmarshal unmarshaller, expectedErr string, args ...string) {
   172  	context, err := s.runVolumeList(c, args...)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  
   175  	var result struct {
   176  		Volumes map[string]storage.VolumeInfo
   177  	}
   178  	err = unmarshal([]byte(cmdtesting.Stdout(context)), &result)
   179  	c.Assert(err, jc.ErrorIsNil)
   180  
   181  	expected := s.expectVolume(c, nil)
   182  	c.Assert(result.Volumes, jc.DeepEquals, expected)
   183  
   184  	obtainedErr := cmdtesting.Stderr(context)
   185  	c.Assert(obtainedErr, gc.Equals, expectedErr)
   186  }
   187  
   188  // expect returns the VolumeInfo mapping we should expect to unmarshal
   189  // from rendered YAML or JSON.
   190  func (s *ListSuite) expectVolume(c *gc.C, machines []string) map[string]storage.VolumeInfo {
   191  	all, err := s.mockAPI.ListVolumes(machines)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  
   194  	var valid []params.VolumeDetails
   195  	for _, result := range all {
   196  		if result.Error == nil {
   197  			valid = append(valid, result.Result...)
   198  		}
   199  	}
   200  	result, err := storage.ConvertToVolumeInfo(valid)
   201  	c.Assert(err, jc.ErrorIsNil)
   202  	return result
   203  }
   204  
   205  func (s *ListSuite) assertValidVolumeList(c *gc.C, args []string, expectedOut string) {
   206  	context, err := s.runVolumeList(c, args...)
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	s.assertUserFacingVolumeOutput(c, context, expectedOut, "")
   209  }
   210  
   211  func (s *ListSuite) runVolumeList(c *gc.C, args ...string) (*cmd.Context, error) {
   212  	return cmdtesting.RunCommand(c,
   213  		storage.NewListCommandForTest(s.mockAPI, s.store), append(args, "--volume")...)
   214  }
   215  
   216  func (s *ListSuite) assertUserFacingVolumeOutput(c *gc.C, context *cmd.Context, expectedOut, expectedErr string) {
   217  	obtainedOut := cmdtesting.Stdout(context)
   218  	c.Assert(obtainedOut, gc.Equals, expectedOut)
   219  
   220  	obtainedErr := cmdtesting.Stderr(context)
   221  	c.Assert(obtainedErr, gc.Equals, expectedErr)
   222  }
   223  
   224  func (s *mockListAPI) ListVolumes(machines []string) ([]params.VolumeDetailsListResult, error) {
   225  	if s.listVolumes != nil {
   226  		return s.listVolumes(machines)
   227  	}
   228  	results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{
   229  		// volume 0/0 is attached to machine 0, assigned to
   230  		// storage db-dir/1001, which is attached to unit
   231  		// abc/0.
   232  		{
   233  			VolumeTag: "volume-0-0",
   234  			Info: params.VolumeInfo{
   235  				VolumeId: "provider-supplied-volume-0-0",
   236  				Pool:     "radiance",
   237  				Size:     512,
   238  			},
   239  			Life:   "alive",
   240  			Status: createTestStatus(status.Attached, "", s.time),
   241  			MachineAttachments: map[string]params.VolumeAttachmentDetails{
   242  				"machine-0": {
   243  					Life: "alive",
   244  					VolumeAttachmentInfo: params.VolumeAttachmentInfo{
   245  						DeviceName: "loop0",
   246  					},
   247  				},
   248  			},
   249  			Storage: &params.StorageDetails{
   250  				StorageTag: "storage-db-dir-1001",
   251  				OwnerTag:   "unit-abc-0",
   252  				Kind:       params.StorageKindBlock,
   253  				Life:       "alive",
   254  				Status:     createTestStatus(status.Attached, "", s.time),
   255  				Attachments: map[string]params.StorageAttachmentDetails{
   256  					"unit-abc-0": {
   257  						StorageTag: "storage-db-dir-1001",
   258  						UnitTag:    "unit-abc-0",
   259  						MachineTag: "machine-0",
   260  						Location:   "/dev/loop0",
   261  					},
   262  				},
   263  			},
   264  		},
   265  		// volume 1 is attaching to machine 0, but is not assigned
   266  		// to any storage.
   267  		{
   268  			VolumeTag: "volume-1",
   269  			Info: params.VolumeInfo{
   270  				VolumeId:   "provider-supplied-volume-1",
   271  				HardwareId: "serial blah blah",
   272  				Persistent: true,
   273  				Size:       2048,
   274  			},
   275  			Status: createTestStatus(status.Attaching, "failed to attach, will retry", s.time),
   276  			MachineAttachments: map[string]params.VolumeAttachmentDetails{
   277  				"machine-0": {},
   278  			},
   279  		},
   280  		// volume 3 is due to be attached to machine 1, but is not
   281  		// assigned to any storage and has not yet been provisioned.
   282  		{
   283  			VolumeTag: "volume-3",
   284  			Info: params.VolumeInfo{
   285  				Size: 42,
   286  			},
   287  			Status: createTestStatus(status.Pending, "", s.time),
   288  			MachineAttachments: map[string]params.VolumeAttachmentDetails{
   289  				"machine-1": {},
   290  			},
   291  		},
   292  		// volume 2 is due to be attached to machine 1, but is not
   293  		// assigned to any storage and has not yet been provisioned.
   294  		{
   295  			VolumeTag: "volume-2",
   296  			Info: params.VolumeInfo{
   297  				VolumeId: "provider-supplied-volume-2",
   298  				Size:     3,
   299  			},
   300  			Status: createTestStatus(status.Attached, "", s.time),
   301  			MachineAttachments: map[string]params.VolumeAttachmentDetails{
   302  				"machine-1": {
   303  					VolumeAttachmentInfo: params.VolumeAttachmentInfo{
   304  						DeviceName: "xvdf1",
   305  					},
   306  				},
   307  			},
   308  		},
   309  		// volume 4 is attached to machines 0 and 1, and is assigned
   310  		// to shared storage.
   311  		{
   312  			VolumeTag: "volume-4",
   313  			Info: params.VolumeInfo{
   314  				VolumeId:   "provider-supplied-volume-4",
   315  				Persistent: true,
   316  				Size:       1024,
   317  			},
   318  			Status: createTestStatus(status.Attached, "", s.time),
   319  			MachineAttachments: map[string]params.VolumeAttachmentDetails{
   320  				"machine-0": {
   321  					VolumeAttachmentInfo: params.VolumeAttachmentInfo{
   322  						DeviceName: "xvdf2",
   323  						ReadOnly:   true,
   324  					},
   325  				},
   326  				"machine-1": {
   327  					VolumeAttachmentInfo: params.VolumeAttachmentInfo{
   328  						DeviceName: "xvdf3",
   329  						ReadOnly:   true,
   330  					},
   331  				},
   332  			},
   333  			Storage: &params.StorageDetails{
   334  				StorageTag: "storage-shared-fs-0",
   335  				OwnerTag:   "application-transcode",
   336  				Kind:       params.StorageKindBlock,
   337  				Status:     createTestStatus(status.Attached, "", s.time),
   338  				Attachments: map[string]params.StorageAttachmentDetails{
   339  					"unit-transcode-0": {
   340  						StorageTag: "storage-shared-fs-0",
   341  						UnitTag:    "unit-transcode-0",
   342  						MachineTag: "machine-0",
   343  						Location:   "/mnt/bits",
   344  					},
   345  					"unit-transcode-1": {
   346  						StorageTag: "storage-shared-fs-0",
   347  						UnitTag:    "unit-transcode-1",
   348  						MachineTag: "machine-1",
   349  						Location:   "/mnt/pieces",
   350  					},
   351  				},
   352  			},
   353  		},
   354  	}}}
   355  	if s.omitPool {
   356  		for _, result := range results {
   357  			for i, details := range result.Result {
   358  				details.Info.Pool = ""
   359  				result.Result[i] = details
   360  			}
   361  		}
   362  	}
   363  	return results, nil
   364  }
   365  
   366  func createTestStatus(testStatus status.Status, message string, since time.Time) params.EntityStatus {
   367  	return params.EntityStatus{
   368  		Status: testStatus,
   369  		Info:   message,
   370  		Since:  &since,
   371  	}
   372  }