github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/resource/cmd/deploy_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package cmd 5 6 import ( 7 "bytes" 8 "io" 9 "os" 10 11 "github.com/juju/errors" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/charm.v6-unstable" 16 charmresource "gopkg.in/juju/charm.v6-unstable/resource" 17 "gopkg.in/macaroon.v1" 18 19 "github.com/juju/juju/charmstore" 20 ) 21 22 type DeploySuite struct { 23 testing.IsolationSuite 24 25 stub *testing.Stub 26 } 27 28 var _ = gc.Suite(&DeploySuite{}) 29 30 func (s *DeploySuite) SetUpTest(c *gc.C) { 31 s.IsolationSuite.SetUpTest(c) 32 33 s.stub = &testing.Stub{} 34 } 35 36 func (s DeploySuite) TestDeployResourcesWithoutFiles(c *gc.C) { 37 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 38 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 39 chID := charmstore.CharmID{ 40 URL: cURL, 41 } 42 csMac := &macaroon.Macaroon{} 43 resources := map[string]charmresource.Meta{ 44 "store-tarball": { 45 Name: "store-tarball", 46 Type: charmresource.TypeFile, 47 Path: "store.tgz", 48 }, 49 "store-zip": { 50 Name: "store-zip", 51 Type: charmresource.TypeFile, 52 Path: "store.zip", 53 }, 54 } 55 56 ids, err := DeployResources(DeployResourcesArgs{ 57 ApplicationID: "mysql", 58 CharmID: chID, 59 CharmStoreMacaroon: csMac, 60 Filenames: nil, 61 Client: deps, 62 ResourcesMeta: resources, 63 }) 64 c.Assert(err, jc.ErrorIsNil) 65 66 c.Check(ids, gc.DeepEquals, map[string]string{ 67 "store-tarball": "id-store-tarball", 68 "store-zip": "id-store-zip", 69 }) 70 71 s.stub.CheckCallNames(c, "AddPendingResources") 72 s.stub.CheckCall(c, 0, "AddPendingResources", "mysql", chID, csMac, []charmresource.Resource{{ 73 Meta: resources["store-tarball"], 74 Origin: charmresource.OriginStore, 75 Revision: -1, 76 }, { 77 Meta: resources["store-zip"], 78 Origin: charmresource.OriginStore, 79 Revision: -1, 80 }}) 81 } 82 83 func (s DeploySuite) TestUploadFilesOnly(c *gc.C) { 84 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 85 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 86 chID := charmstore.CharmID{ 87 URL: cURL, 88 } 89 csMac := &macaroon.Macaroon{} 90 du := deployUploader{ 91 applicationID: "mysql", 92 chID: chID, 93 csMac: csMac, 94 client: deps, 95 resources: map[string]charmresource.Meta{ 96 "upload": { 97 Name: "upload", 98 Type: charmresource.TypeFile, 99 Path: "upload", 100 }, 101 "store": { 102 Name: "store", 103 Type: charmresource.TypeFile, 104 Path: "store", 105 }, 106 }, 107 osOpen: deps.Open, 108 osStat: deps.Stat, 109 } 110 111 files := map[string]string{ 112 "upload": "foobar.txt", 113 } 114 revisions := map[string]int{} 115 ids, err := du.upload(files, revisions) 116 c.Assert(err, jc.ErrorIsNil) 117 c.Check(ids, gc.DeepEquals, map[string]string{ 118 "upload": "id-upload", 119 "store": "id-store", 120 }) 121 122 s.stub.CheckCallNames(c, "Stat", "AddPendingResources", "Open", "AddPendingResource") 123 expectedStore := []charmresource.Resource{ 124 { 125 Meta: du.resources["store"], 126 Origin: charmresource.OriginStore, 127 Revision: -1, 128 }, 129 } 130 s.stub.CheckCall(c, 1, "AddPendingResources", "mysql", chID, csMac, expectedStore) 131 s.stub.CheckCall(c, 2, "Open", "foobar.txt") 132 133 expectedUpload := charmresource.Resource{ 134 Meta: du.resources["upload"], 135 Origin: charmresource.OriginUpload, 136 } 137 s.stub.CheckCall(c, 3, "AddPendingResource", "mysql", expectedUpload, "foobar.txt", deps.ReadSeekCloser) 138 } 139 140 func (s DeploySuite) TestUploadRevisionsOnly(c *gc.C) { 141 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 142 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 143 chID := charmstore.CharmID{ 144 URL: cURL, 145 } 146 csMac := &macaroon.Macaroon{} 147 du := deployUploader{ 148 applicationID: "mysql", 149 chID: chID, 150 csMac: csMac, 151 client: deps, 152 resources: map[string]charmresource.Meta{ 153 "upload": { 154 Name: "upload", 155 Type: charmresource.TypeFile, 156 Path: "upload", 157 }, 158 "store": { 159 Name: "store", 160 Type: charmresource.TypeFile, 161 Path: "store", 162 }, 163 }, 164 osOpen: deps.Open, 165 osStat: deps.Stat, 166 } 167 168 files := map[string]string{} 169 revisions := map[string]int{ 170 "store": 3, 171 } 172 ids, err := du.upload(files, revisions) 173 c.Assert(err, jc.ErrorIsNil) 174 c.Check(ids, gc.DeepEquals, map[string]string{ 175 "upload": "id-upload", 176 "store": "id-store", 177 }) 178 179 s.stub.CheckCallNames(c, "AddPendingResources") 180 expectedStore := []charmresource.Resource{{ 181 Meta: du.resources["store"], 182 Origin: charmresource.OriginStore, 183 Revision: 3, 184 }, { 185 Meta: du.resources["upload"], 186 Origin: charmresource.OriginStore, 187 Revision: -1, 188 }} 189 s.stub.CheckCall(c, 0, "AddPendingResources", "mysql", chID, csMac, expectedStore) 190 } 191 192 func (s DeploySuite) TestUploadFilesAndRevisions(c *gc.C) { 193 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 194 cURL := charm.MustParseURL("cs:~a-user/trusty/spam-5") 195 chID := charmstore.CharmID{ 196 URL: cURL, 197 } 198 csMac := &macaroon.Macaroon{} 199 du := deployUploader{ 200 applicationID: "mysql", 201 chID: chID, 202 csMac: csMac, 203 client: deps, 204 resources: map[string]charmresource.Meta{ 205 "upload": { 206 Name: "upload", 207 Type: charmresource.TypeFile, 208 Path: "upload", 209 }, 210 "store": { 211 Name: "store", 212 Type: charmresource.TypeFile, 213 Path: "store", 214 }, 215 }, 216 osOpen: deps.Open, 217 osStat: deps.Stat, 218 } 219 220 files := map[string]string{ 221 "upload": "foobar.txt", 222 } 223 revisions := map[string]int{ 224 "store": 3, 225 } 226 ids, err := du.upload(files, revisions) 227 c.Assert(err, jc.ErrorIsNil) 228 c.Check(ids, gc.DeepEquals, map[string]string{ 229 "upload": "id-upload", 230 "store": "id-store", 231 }) 232 233 s.stub.CheckCallNames(c, "Stat", "AddPendingResources", "Open", "AddPendingResource") 234 expectedStore := []charmresource.Resource{ 235 { 236 Meta: du.resources["store"], 237 Origin: charmresource.OriginStore, 238 Revision: 3, 239 }, 240 } 241 s.stub.CheckCall(c, 1, "AddPendingResources", "mysql", chID, csMac, expectedStore) 242 s.stub.CheckCall(c, 2, "Open", "foobar.txt") 243 244 expectedUpload := charmresource.Resource{ 245 Meta: du.resources["upload"], 246 Origin: charmresource.OriginUpload, 247 } 248 s.stub.CheckCall(c, 3, "AddPendingResource", "mysql", expectedUpload, "foobar.txt", deps.ReadSeekCloser) 249 } 250 251 func (s DeploySuite) TestUploadUnexpectedResourceFile(c *gc.C) { 252 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 253 du := deployUploader{ 254 applicationID: "mysql", 255 client: deps, 256 resources: map[string]charmresource.Meta{ 257 "res1": { 258 Name: "res1", 259 Type: charmresource.TypeFile, 260 Path: "path", 261 }, 262 }, 263 osOpen: deps.Open, 264 osStat: deps.Stat, 265 } 266 267 files := map[string]string{"some bad resource": "foobar.txt"} 268 revisions := map[string]int{} 269 _, err := du.upload(files, revisions) 270 c.Check(err, gc.ErrorMatches, `unrecognized resource "some bad resource"`) 271 272 s.stub.CheckNoCalls(c) 273 } 274 275 func (s DeploySuite) TestUploadUnexpectedResourceRevision(c *gc.C) { 276 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 277 du := deployUploader{ 278 applicationID: "mysql", 279 client: deps, 280 resources: map[string]charmresource.Meta{ 281 "res1": { 282 Name: "res1", 283 Type: charmresource.TypeFile, 284 Path: "path", 285 }, 286 }, 287 osOpen: deps.Open, 288 osStat: deps.Stat, 289 } 290 291 files := map[string]string{} 292 revisions := map[string]int{"some bad resource": 2} 293 _, err := du.upload(files, revisions) 294 c.Check(err, gc.ErrorMatches, `unrecognized resource "some bad resource"`) 295 296 s.stub.CheckNoCalls(c) 297 } 298 299 func (s DeploySuite) TestMissingResource(c *gc.C) { 300 deps := uploadDeps{s.stub, rsc{&bytes.Buffer{}}} 301 du := deployUploader{ 302 applicationID: "mysql", 303 client: deps, 304 resources: map[string]charmresource.Meta{ 305 "res1": { 306 Name: "res1", 307 Type: charmresource.TypeFile, 308 Path: "path", 309 }, 310 }, 311 osOpen: deps.Open, 312 osStat: deps.Stat, 313 } 314 315 // set the error that will be returned by os.Stat 316 s.stub.SetErrors(os.ErrNotExist) 317 318 files := map[string]string{"res1": "foobar.txt"} 319 revisions := map[string]int{} 320 _, err := du.upload(files, revisions) 321 c.Check(err, gc.ErrorMatches, `file for resource "res1".*`) 322 c.Check(errors.Cause(err), jc.Satisfies, os.IsNotExist) 323 } 324 325 type uploadDeps struct { 326 stub *testing.Stub 327 ReadSeekCloser ReadSeekCloser 328 } 329 330 func (s uploadDeps) AddPendingResources(applicationID string, charmID charmstore.CharmID, csMac *macaroon.Macaroon, resources []charmresource.Resource) (ids []string, err error) { 331 charmresource.Sort(resources) 332 s.stub.AddCall("AddPendingResources", applicationID, charmID, csMac, resources) 333 if err := s.stub.NextErr(); err != nil { 334 return nil, err 335 } 336 ids = make([]string, len(resources)) 337 for i, res := range resources { 338 ids[i] = "id-" + res.Name 339 } 340 return ids, nil 341 } 342 343 func (s uploadDeps) AddPendingResource(applicationID string, resource charmresource.Resource, filename string, r io.ReadSeeker) (id string, err error) { 344 s.stub.AddCall("AddPendingResource", applicationID, resource, filename, r) 345 if err := s.stub.NextErr(); err != nil { 346 return "", err 347 } 348 return "id-" + resource.Name, nil 349 } 350 351 func (s uploadDeps) Open(name string) (ReadSeekCloser, error) { 352 s.stub.AddCall("Open", name) 353 if err := s.stub.NextErr(); err != nil { 354 return nil, err 355 } 356 return s.ReadSeekCloser, nil 357 } 358 359 func (s uploadDeps) Stat(name string) error { 360 s.stub.AddCall("Stat", name) 361 return s.stub.NextErr() 362 } 363 364 type rsc struct { 365 *bytes.Buffer 366 } 367 368 func (rsc) Close() error { 369 return nil 370 } 371 func (rsc) Seek(offset int64, whence int) (int64, error) { 372 return 0, nil 373 }