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 }