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: ¶ms.Error{Message: "bad"}, 96 }) 97 results = append(results, params.VolumeDetailsResult{ 98 Error: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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 }