github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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.v2" 15 16 "github.com/juju/juju/apiserver/params" 17 "github.com/juju/juju/cmd/juju/storage" 18 "github.com/juju/juju/status" 19 "github.com/juju/juju/testing" 20 ) 21 22 func (s *ListSuite) TestVolumeListEmpty(c *gc.C) { 23 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 24 return nil, nil 25 } 26 s.assertValidVolumeList( 27 c, 28 []string{"--format", "yaml"}, 29 "", 30 ) 31 } 32 33 func (s *ListSuite) TestVolumeListError(c *gc.C) { 34 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 35 return nil, errors.New("just my luck") 36 } 37 context, err := s.runVolumeList(c, "--format", "yaml") 38 c.Assert(errors.Cause(err), gc.ErrorMatches, "just my luck") 39 s.assertUserFacingVolumeOutput(c, context, "", "") 40 } 41 42 func (s *ListSuite) TestVolumeListArgs(c *gc.C) { 43 var called bool 44 expectedArgs := []string{"a", "b", "c"} 45 s.mockAPI.listVolumes = func(arg []string) ([]params.VolumeDetailsListResult, error) { 46 c.Assert(arg, jc.DeepEquals, expectedArgs) 47 called = true 48 return nil, nil 49 } 50 s.assertValidVolumeList( 51 c, 52 append([]string{"--format", "yaml"}, expectedArgs...), 53 "", 54 ) 55 c.Assert(called, jc.IsTrue) 56 } 57 58 func (s *ListSuite) TestVolumeListYaml(c *gc.C) { 59 s.assertUnmarshalledVolumeOutput( 60 c, 61 goyaml.Unmarshal, 62 "", // no error 63 "--format", "yaml") 64 } 65 66 func (s *ListSuite) TestVolumeListJSON(c *gc.C) { 67 s.assertUnmarshalledVolumeOutput( 68 c, 69 json.Unmarshal, 70 "", // no error 71 "--format", "json") 72 } 73 74 func (s *ListSuite) TestVolumeListWithErrorResults(c *gc.C) { 75 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 76 results, _ := mockListAPI{}.ListVolumes(nil) 77 results = append(results, params.VolumeDetailsListResult{ 78 Error: ¶ms.Error{Message: "bad"}, 79 }) 80 results = append(results, params.VolumeDetailsListResult{ 81 Error: ¶ms.Error{Message: "ness"}, 82 }) 83 return results, nil 84 } 85 // we should see the error in stderr, but it should not 86 // otherwise affect the rendering of valid results. 87 s.assertUnmarshalledVolumeOutput(c, json.Unmarshal, "bad\nness\n", "--format", "json") 88 s.assertUnmarshalledVolumeOutput(c, goyaml.Unmarshal, "bad\nness\n", "--format", "yaml") 89 } 90 91 var expectedVolumeListTabular = ` 92 MACHINE UNIT STORAGE ID PROVIDER-ID DEVICE SIZE STATE MESSAGE 93 0 abc/0 db-dir/1001 0/0 provider-supplied-volume-0-0 loop0 512MiB attached 94 0 transcode/0 shared-fs/0 4 provider-supplied-volume-4 xvdf2 1.0GiB attached 95 0 1 provider-supplied-volume-1 2.0GiB attaching failed to attach, will retry 96 1 transcode/1 shared-fs/0 4 provider-supplied-volume-4 xvdf3 1.0GiB attached 97 1 2 provider-supplied-volume-2 xvdf1 3.0MiB attached 98 1 3 42MiB pending 99 100 `[1:] 101 102 func (s *ListSuite) TestVolumeListTabular(c *gc.C) { 103 s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular) 104 105 // Do it again, reversing the results returned by the API. 106 // We should get everything sorted in the appropriate order. 107 s.mockAPI.listVolumes = func([]string) ([]params.VolumeDetailsListResult, error) { 108 results, _ := mockListAPI{}.ListVolumes(nil) 109 n := len(results) 110 for i := 0; i < n/2; i++ { 111 results[i], results[n-i-1] = results[n-i-1], results[i] 112 } 113 return results, nil 114 } 115 s.assertValidVolumeList(c, []string{}, expectedVolumeListTabular) 116 } 117 118 func (s *ListSuite) assertUnmarshalledVolumeOutput(c *gc.C, unmarshal unmarshaller, expectedErr string, args ...string) { 119 context, err := s.runVolumeList(c, args...) 120 c.Assert(err, jc.ErrorIsNil) 121 122 var result struct { 123 Volumes map[string]storage.VolumeInfo 124 } 125 err = unmarshal([]byte(testing.Stdout(context)), &result) 126 c.Assert(err, jc.ErrorIsNil) 127 128 expected := s.expectVolume(c, nil) 129 c.Assert(result.Volumes, jc.DeepEquals, expected) 130 131 obtainedErr := testing.Stderr(context) 132 c.Assert(obtainedErr, gc.Equals, expectedErr) 133 } 134 135 // expect returns the VolumeInfo mapping we should expect to unmarshal 136 // from rendered YAML or JSON. 137 func (s *ListSuite) expectVolume(c *gc.C, machines []string) map[string]storage.VolumeInfo { 138 all, err := s.mockAPI.ListVolumes(machines) 139 c.Assert(err, jc.ErrorIsNil) 140 141 var valid []params.VolumeDetails 142 for _, result := range all { 143 if result.Error == nil { 144 valid = append(valid, result.Result...) 145 } 146 } 147 result, err := storage.ConvertToVolumeInfo(valid) 148 c.Assert(err, jc.ErrorIsNil) 149 return result 150 } 151 152 func (s *ListSuite) assertValidVolumeList(c *gc.C, args []string, expectedOut string) { 153 context, err := s.runVolumeList(c, args...) 154 c.Assert(err, jc.ErrorIsNil) 155 s.assertUserFacingVolumeOutput(c, context, expectedOut, "") 156 } 157 158 func (s *ListSuite) runVolumeList(c *gc.C, args ...string) (*cmd.Context, error) { 159 return testing.RunCommand(c, 160 storage.NewListCommandForTest(s.mockAPI, s.store), append(args, "--volume")...) 161 } 162 163 func (s *ListSuite) assertUserFacingVolumeOutput(c *gc.C, context *cmd.Context, expectedOut, expectedErr string) { 164 obtainedOut := testing.Stdout(context) 165 c.Assert(obtainedOut, gc.Equals, expectedOut) 166 167 obtainedErr := testing.Stderr(context) 168 c.Assert(obtainedErr, gc.Equals, expectedErr) 169 } 170 171 func (s mockListAPI) ListVolumes(machines []string) ([]params.VolumeDetailsListResult, error) { 172 if s.listVolumes != nil { 173 return s.listVolumes(machines) 174 } 175 results := []params.VolumeDetailsListResult{{Result: []params.VolumeDetails{ 176 // volume 0/0 is attached to machine 0, assigned to 177 // storage db-dir/1001, which is attached to unit 178 // abc/0. 179 { 180 VolumeTag: "volume-0-0", 181 Info: params.VolumeInfo{ 182 VolumeId: "provider-supplied-volume-0-0", 183 Size: 512, 184 }, 185 Status: createTestStatus(status.StatusAttached, ""), 186 MachineAttachments: map[string]params.VolumeAttachmentInfo{ 187 "machine-0": params.VolumeAttachmentInfo{ 188 DeviceName: "loop0", 189 }, 190 }, 191 Storage: ¶ms.StorageDetails{ 192 StorageTag: "storage-db-dir-1001", 193 OwnerTag: "unit-abc-0", 194 Kind: params.StorageKindBlock, 195 Status: createTestStatus(status.StatusAttached, ""), 196 Attachments: map[string]params.StorageAttachmentDetails{ 197 "unit-abc-0": params.StorageAttachmentDetails{ 198 StorageTag: "storage-db-dir-1001", 199 UnitTag: "unit-abc-0", 200 MachineTag: "machine-0", 201 Location: "/dev/loop0", 202 }, 203 }, 204 }, 205 }, 206 // volume 1 is attaching to machine 0, but is not assigned 207 // to any storage. 208 { 209 VolumeTag: "volume-1", 210 Info: params.VolumeInfo{ 211 VolumeId: "provider-supplied-volume-1", 212 HardwareId: "serial blah blah", 213 Persistent: true, 214 Size: 2048, 215 }, 216 Status: createTestStatus(status.StatusAttaching, "failed to attach, will retry"), 217 MachineAttachments: map[string]params.VolumeAttachmentInfo{ 218 "machine-0": params.VolumeAttachmentInfo{}, 219 }, 220 }, 221 // volume 3 is due to be attached to machine 1, but is not 222 // assigned to any storage and has not yet been provisioned. 223 { 224 VolumeTag: "volume-3", 225 Info: params.VolumeInfo{ 226 Size: 42, 227 }, 228 Status: createTestStatus(status.StatusPending, ""), 229 MachineAttachments: map[string]params.VolumeAttachmentInfo{ 230 "machine-1": params.VolumeAttachmentInfo{}, 231 }, 232 }, 233 // volume 2 is due to be attached to machine 1, but is not 234 // assigned to any storage and has not yet been provisioned. 235 { 236 VolumeTag: "volume-2", 237 Info: params.VolumeInfo{ 238 VolumeId: "provider-supplied-volume-2", 239 Size: 3, 240 }, 241 Status: createTestStatus(status.StatusAttached, ""), 242 MachineAttachments: map[string]params.VolumeAttachmentInfo{ 243 "machine-1": params.VolumeAttachmentInfo{ 244 DeviceName: "xvdf1", 245 }, 246 }, 247 }, 248 // volume 4 is attached to machines 0 and 1, and is assigned 249 // to shared storage. 250 { 251 VolumeTag: "volume-4", 252 Info: params.VolumeInfo{ 253 VolumeId: "provider-supplied-volume-4", 254 Persistent: true, 255 Size: 1024, 256 }, 257 Status: createTestStatus(status.StatusAttached, ""), 258 MachineAttachments: map[string]params.VolumeAttachmentInfo{ 259 "machine-0": params.VolumeAttachmentInfo{ 260 DeviceName: "xvdf2", 261 ReadOnly: true, 262 }, 263 "machine-1": params.VolumeAttachmentInfo{ 264 DeviceName: "xvdf3", 265 ReadOnly: true, 266 }, 267 }, 268 Storage: ¶ms.StorageDetails{ 269 StorageTag: "storage-shared-fs-0", 270 OwnerTag: "service-transcode", 271 Kind: params.StorageKindBlock, 272 Status: createTestStatus(status.StatusAttached, ""), 273 Attachments: map[string]params.StorageAttachmentDetails{ 274 "unit-transcode-0": params.StorageAttachmentDetails{ 275 StorageTag: "storage-shared-fs-0", 276 UnitTag: "unit-transcode-0", 277 MachineTag: "machine-0", 278 Location: "/mnt/bits", 279 }, 280 "unit-transcode-1": params.StorageAttachmentDetails{ 281 StorageTag: "storage-shared-fs-0", 282 UnitTag: "unit-transcode-1", 283 MachineTag: "machine-1", 284 Location: "/mnt/pieces", 285 }, 286 }, 287 }, 288 }, 289 }}} 290 return results, nil 291 } 292 293 func createTestStatus(testStatus status.Status, message string) params.EntityStatus { 294 return params.EntityStatus{ 295 Status: testStatus, 296 Info: message, 297 Since: &time.Time{}, 298 } 299 }