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