github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"bytes"
     8  	"encoding/json"
     9  	"time"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/names"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	goyaml "gopkg.in/yaml.v1"
    17  
    18  	"fmt"
    19  
    20  	"github.com/juju/juju/apiserver/common"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/cmd/envcmd"
    23  	"github.com/juju/juju/cmd/juju/storage"
    24  	"github.com/juju/juju/testing"
    25  )
    26  
    27  type volumeListSuite struct {
    28  	SubStorageSuite
    29  	mockAPI *mockVolumeListAPI
    30  }
    31  
    32  var _ = gc.Suite(&volumeListSuite{})
    33  
    34  func (s *volumeListSuite) SetUpTest(c *gc.C) {
    35  	s.SubStorageSuite.SetUpTest(c)
    36  
    37  	s.mockAPI = &mockVolumeListAPI{fillDeviceName: true, addErrItem: true}
    38  	s.PatchValue(storage.GetVolumeListAPI,
    39  		func(c *storage.VolumeListCommand) (storage.VolumeListAPI, error) {
    40  			return s.mockAPI, nil
    41  		})
    42  }
    43  
    44  func (s *volumeListSuite) TestVolumeListEmpty(c *gc.C) {
    45  	s.mockAPI.listEmpty = true
    46  	s.assertValidList(
    47  		c,
    48  		[]string{"--format", "yaml"},
    49  		"",
    50  		"",
    51  	)
    52  }
    53  
    54  func (s *volumeListSuite) TestVolumeListError(c *gc.C) {
    55  	s.mockAPI.errOut = "just my luck"
    56  
    57  	context, err := runVolumeList(c, "--format", "yaml")
    58  	c.Assert(errors.Cause(err), gc.ErrorMatches, s.mockAPI.errOut)
    59  	s.assertUserFacingOutput(c, context, "", "")
    60  }
    61  
    62  func (s *volumeListSuite) TestVolumeListAll(c *gc.C) {
    63  	s.mockAPI.listAll = true
    64  	s.assertUnmarshalledOutput(
    65  		c,
    66  		goyaml.Unmarshal,
    67  		// mock will ignore any value here, as listAll flag above has precedence
    68  		"",
    69  		"--format", "yaml")
    70  }
    71  
    72  func (s *volumeListSuite) TestVolumeListYaml(c *gc.C) {
    73  	s.assertUnmarshalledOutput(
    74  		c,
    75  		goyaml.Unmarshal,
    76  		"2",
    77  		"--format", "yaml")
    78  }
    79  
    80  func (s *volumeListSuite) TestVolumeListYamlNoDeviceName(c *gc.C) {
    81  	s.mockAPI.fillDeviceName = false
    82  	s.assertUnmarshalledOutput(
    83  		c,
    84  		goyaml.Unmarshal,
    85  		"2",
    86  		"--format", "yaml")
    87  }
    88  
    89  func (s *volumeListSuite) TestVolumeListJSON(c *gc.C) {
    90  	s.assertUnmarshalledOutput(
    91  		c,
    92  		json.Unmarshal,
    93  		"2",
    94  		"--format", "json")
    95  }
    96  
    97  func (s *volumeListSuite) TestVolumeListTabular(c *gc.C) {
    98  	s.assertValidList(
    99  		c,
   100  		[]string{"2"},
   101  		// Default format is tabular
   102  		`
   103  MACHINE  UNIT          STORAGE      DEVICE      VOLUME      ID                            SIZE    STATE      MESSAGE
   104  2        postgresql/0  shared-fs/0  testdevice  0/1         provider-supplied-0/1         1.0GiB  attaching  failed to attach
   105  2        unattached    shared-fs/0  testdevice  0/abc/0/88  provider-supplied-0/abc/0/88  1.0GiB  attached   
   106  
   107  `[1:],
   108  		`
   109  volume item error
   110  `[1:],
   111  	)
   112  }
   113  
   114  func (s *volumeListSuite) TestVolumeListTabularSort(c *gc.C) {
   115  	s.assertValidList(
   116  		c,
   117  		[]string{"2", "3"},
   118  		// Default format is tabular
   119  		`
   120  MACHINE  UNIT          STORAGE      DEVICE      VOLUME      ID                            SIZE    STATE      MESSAGE
   121  2        postgresql/0  shared-fs/0  testdevice  0/1         provider-supplied-0/1         1.0GiB  attaching  failed to attach
   122  2        unattached    shared-fs/0  testdevice  0/abc/0/88  provider-supplied-0/abc/0/88  1.0GiB  attached   
   123  3        postgresql/0  shared-fs/0  testdevice  0/1         provider-supplied-0/1         1.0GiB  attaching  failed to attach
   124  3        unattached    shared-fs/0  testdevice  0/abc/0/88  provider-supplied-0/abc/0/88  1.0GiB  attached   
   125  
   126  `[1:],
   127  		`
   128  volume item error
   129  `[1:],
   130  	)
   131  }
   132  
   133  func (s *volumeListSuite) TestVolumeListTabularSortWithUnattached(c *gc.C) {
   134  	s.mockAPI.listAll = true
   135  	s.assertValidList(
   136  		c,
   137  		[]string{"2", "3"},
   138  		// Default format is tabular
   139  		`
   140  MACHINE     UNIT          STORAGE      DEVICE      VOLUME      ID                            SIZE    STATE       MESSAGE
   141  25          postgresql/0  shared-fs/0  testdevice  0/1         provider-supplied-0/1         1.0GiB  attaching   failed to attach
   142  25          unattached    shared-fs/0  testdevice  0/abc/0/88  provider-supplied-0/abc/0/88  1.0GiB  attached    
   143  42          postgresql/0  shared-fs/0  testdevice  0/1         provider-supplied-0/1         1.0GiB  attaching   failed to attach
   144  42          unattached    shared-fs/0  testdevice  0/abc/0/88  provider-supplied-0/abc/0/88  1.0GiB  attached    
   145  unattached  abc/0         db-dir/1000              3/4         provider-supplied-3/4         1.0GiB  destroying  
   146  unattached  unattached    unassigned               3/3         provider-supplied-3/3         1.0GiB  destroying  
   147  
   148  `[1:],
   149  		`
   150  volume item error
   151  `[1:],
   152  	)
   153  }
   154  
   155  func (s *volumeListSuite) assertUnmarshalledOutput(c *gc.C, unmarshall unmarshaller, machine string, args ...string) {
   156  	all := []string{machine}
   157  	context, err := runVolumeList(c, append(all, args...)...)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	var result map[string]map[string]map[string]storage.VolumeInfo
   160  	err = unmarshall(context.Stdout.(*bytes.Buffer).Bytes(), &result)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	expected := s.expect(c, []string{machine})
   163  	// This comparison cannot rely on gc.DeepEquals as
   164  	// json.Unmarshal unmarshalls the number as a float64,
   165  	// rather than an int
   166  	s.assertSameVolumeInfos(c, result, expected)
   167  
   168  	obtainedErr := testing.Stderr(context)
   169  	c.Assert(obtainedErr, gc.Equals, `
   170  volume item error
   171  `[1:])
   172  }
   173  
   174  func (s *volumeListSuite) expect(c *gc.C, machines []string) map[string]map[string]map[string]storage.VolumeInfo {
   175  	//no need for this element as we are building output on out stream not err
   176  	s.mockAPI.addErrItem = false
   177  	all, err := s.mockAPI.ListVolumes(machines)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	result, err := storage.ConvertToVolumeInfo(all)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	return result
   182  }
   183  
   184  func (s *volumeListSuite) assertSameVolumeInfos(c *gc.C, one, two map[string]map[string]map[string]storage.VolumeInfo) {
   185  	c.Assert(len(one), gc.Equals, len(two))
   186  
   187  	propertyCompare := func(a, b interface{}) {
   188  		// As some types may have been unmarshalled incorrectly, for example
   189  		// int versus float64, compare values' string representations
   190  		c.Assert(fmt.Sprintf("%v", a), jc.DeepEquals, fmt.Sprintf("%v", b))
   191  
   192  	}
   193  	for machineKey, machineVolumes1 := range one {
   194  		machineVolumes2, ok := two[machineKey]
   195  		c.Assert(ok, jc.IsTrue)
   196  		// these are maps
   197  		c.Assert(len(machineVolumes1), gc.Equals, len(machineVolumes2))
   198  		for unitKey, units1 := range machineVolumes1 {
   199  			units2, ok := machineVolumes2[unitKey]
   200  			c.Assert(ok, jc.IsTrue)
   201  			// these are maps
   202  			c.Assert(len(units1), gc.Equals, len(units2))
   203  			for storageKey, info1 := range units1 {
   204  				info2, ok := units2[storageKey]
   205  				c.Assert(ok, jc.IsTrue)
   206  				propertyCompare(info1.VolumeId, info2.VolumeId)
   207  				propertyCompare(info1.HardwareId, info2.HardwareId)
   208  				propertyCompare(info1.Size, info2.Size)
   209  				propertyCompare(info1.Persistent, info2.Persistent)
   210  				propertyCompare(info1.DeviceName, info2.DeviceName)
   211  				propertyCompare(info1.ReadOnly, info2.ReadOnly)
   212  			}
   213  		}
   214  	}
   215  }
   216  
   217  func (s *volumeListSuite) assertValidList(c *gc.C, args []string, expectedOut, expectedErr string) {
   218  	context, err := runVolumeList(c, args...)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	s.assertUserFacingOutput(c, context, expectedOut, expectedErr)
   221  }
   222  
   223  func runVolumeList(c *gc.C, args ...string) (*cmd.Context, error) {
   224  	return testing.RunCommand(c,
   225  		envcmd.Wrap(&storage.VolumeListCommand{}),
   226  		args...)
   227  }
   228  
   229  func (s *volumeListSuite) assertUserFacingOutput(c *gc.C, context *cmd.Context, expectedOut, expectedErr string) {
   230  	obtainedOut := testing.Stdout(context)
   231  	c.Assert(obtainedOut, gc.Equals, expectedOut)
   232  
   233  	obtainedErr := testing.Stderr(context)
   234  	c.Assert(obtainedErr, gc.Equals, expectedErr)
   235  }
   236  
   237  type mockVolumeListAPI struct {
   238  	listAll, listEmpty, fillDeviceName, addErrItem bool
   239  	errOut                                         string
   240  }
   241  
   242  func (s mockVolumeListAPI) Close() error {
   243  	return nil
   244  }
   245  
   246  func (s mockVolumeListAPI) ListVolumes(machines []string) ([]params.VolumeItem, error) {
   247  	if s.errOut != "" {
   248  		return nil, errors.New(s.errOut)
   249  	}
   250  	if s.listEmpty {
   251  		return nil, nil
   252  	}
   253  	result := []params.VolumeItem{}
   254  	if s.addErrItem {
   255  		result = append(result, params.VolumeItem{
   256  			Error: common.ServerError(errors.New("volume item error"))})
   257  	}
   258  	if s.listAll {
   259  		machines = []string{"25", "42"}
   260  		//unattached
   261  		result = append(result, s.createTestVolumeItem(
   262  			"3/4", true, "db-dir/1000", "abc/0", nil,
   263  			createTestStatus(params.StatusDestroying, ""),
   264  		))
   265  		result = append(result, s.createTestVolumeItem(
   266  			"3/3", false, "", "", nil,
   267  			createTestStatus(params.StatusDestroying, ""),
   268  		))
   269  	}
   270  	result = append(result, s.createTestVolumeItem(
   271  		"0/1", true, "shared-fs/0", "postgresql/0", machines,
   272  		createTestStatus(params.StatusAttaching, "failed to attach"),
   273  	))
   274  	result = append(result, s.createTestVolumeItem(
   275  		"0/abc/0/88", false, "shared-fs/0", "", machines,
   276  		createTestStatus(params.StatusAttached, ""),
   277  	))
   278  	return result, nil
   279  }
   280  
   281  func (s mockVolumeListAPI) createTestVolumeItem(
   282  	id string,
   283  	persistent bool,
   284  	storageid, unitid string,
   285  	machines []string,
   286  	status params.EntityStatus,
   287  ) params.VolumeItem {
   288  	volume := s.createTestVolume(id, persistent, storageid, unitid, status)
   289  
   290  	// Create unattached volume
   291  	if len(machines) == 0 {
   292  		return params.VolumeItem{Volume: volume}
   293  	}
   294  
   295  	// Create volume attachments
   296  	attachments := make([]params.VolumeAttachment, len(machines))
   297  	for i, machine := range machines {
   298  		attachments[i] = s.createTestAttachment(volume.VolumeTag, machine, i%2 == 0)
   299  	}
   300  
   301  	return params.VolumeItem{
   302  		Volume:      volume,
   303  		Attachments: attachments,
   304  	}
   305  }
   306  
   307  func (s mockVolumeListAPI) createTestVolume(id string, persistent bool, storageid, unitid string, status params.EntityStatus) params.VolumeInstance {
   308  	tag := names.NewVolumeTag(id)
   309  	result := params.VolumeInstance{
   310  		VolumeTag:  tag.String(),
   311  		VolumeId:   "provider-supplied-" + tag.Id(),
   312  		HardwareId: "serial blah blah",
   313  		Persistent: persistent,
   314  		Size:       uint64(1024),
   315  		Status:     status,
   316  	}
   317  	if storageid != "" {
   318  		result.StorageTag = names.NewStorageTag(storageid).String()
   319  	}
   320  	if unitid != "" {
   321  		result.UnitTag = names.NewUnitTag(unitid).String()
   322  	}
   323  	return result
   324  }
   325  
   326  func (s mockVolumeListAPI) createTestAttachment(volumeTag, machine string, readonly bool) params.VolumeAttachment {
   327  	result := params.VolumeAttachment{
   328  		VolumeTag:  volumeTag,
   329  		MachineTag: names.NewMachineTag(machine).String(),
   330  		Info: params.VolumeAttachmentInfo{
   331  			ReadOnly: readonly,
   332  		},
   333  	}
   334  	if s.fillDeviceName {
   335  		result.Info.DeviceName = "testdevice"
   336  	}
   337  	return result
   338  }
   339  
   340  func createTestStatus(status params.Status, message string) params.EntityStatus {
   341  	return params.EntityStatus{
   342  		Status: status,
   343  		Info:   message,
   344  		Since:  &time.Time{},
   345  	}
   346  }