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