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 }