Merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:~go-bot/juju-core/trunk

Proposed by Dimiter Naydenov
Status: Merged
Approved by: Dimiter Naydenov
Approved revision: no longer in the source branch.
Merged at revision: 2281
Proposed branch: lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 1365 lines (+560/-190)
21 files modified
charm/repo.go (+2/-9)
charm/testing/mockstore.go (+12/-9)
environs/sync/sync.go (+6/-12)
environs/testing/storage.go (+17/-0)
environs/tools/testing/testing.go (+4/-9)
juju/conn.go (+1/-6)
juju/conn_test.go (+1/-1)
juju/testing/conn.go (+1/-1)
juju/testing/repo.go (+2/-7)
state/apiserver/charms.go (+2/-17)
state/apiserver/charms_test.go (+11/-16)
state/apiserver/client/client.go (+37/-13)
state/apiserver/client/client_test.go (+128/-14)
state/apiserver/export_test.go (+3/-4)
state/state.go (+144/-21)
state/state_test.go (+95/-28)
testing/charm.go (+19/-9)
utils/trivial.go (+26/-0)
utils/trivial_test.go (+46/-2)
worker/uniter/charm/charm.go (+2/-6)
worker/uniter/uniter_test.go (+1/-6)
To merge this branch: bzr merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm
Reviewer Review Type Date Requested Status
Dimiter Naydenov (community) Approve
Review via email: mp+203291@code.launchpad.net

Commit message

state;apiserver: Fix a race - lp bug #1067979

This introduces some changes to how charm store
charms are added through the API (in state and
to provider storage). Now PrepareStoreCharmUpload
is called before trying to download the charm,
repackage it and upload it to storage, in order
to reserve a charm URL in state with pending
state. Added a test that demonstrates multiple
concurrent deployments of the same charm does
not cause the race issues, like mentioned in
the bug.

A few drive-by fixes brought up during review:
* Added ReadSHA256 and ReadFileSHA256 helpers in
  utils, and changed most places where hashes
  are calculated to use them.
* Charms are now uploaded to storage with a
  randomly generated archive names with the
  format "<charm-name>-<revision>-<uuid>".
  This allows multiple concurrent uploads
  to happen safely, and at the end AddCharm
  in the API checks to see if the charm info
  is already updated in state and if so, deletes
  the duplicated upload.
* Added GetEnvironStorage helper to environs/testing.
* Fixed potential compatibility issues with older
  versions and the recently added PendingUpload
  and Placeholder fields of the charm document.

Also tested multiple concurrent deployments with
the local provider manually and updated the bug
accordingly.

https://codereview.appspot.com/53210044/

R=fwereade

Description of the change

state;apiserver: Fix a race - lp bug #1067979

This introduces some changes to how charm store
charms are added through the API (in state and
to provider storage). Now PrepareStoreCharmUpload
is called before trying to download the charm,
repackage it and upload it to storage, in order
to reserve a charm URL in state with pending
state. Added a test that demonstrates multiple
concurrent deployments of the same charm does
not cause the race issues, like mentioned in
the bug.

A few drive-by fixes brought up during review:
* Added ReadSHA256 and ReadFileSHA256 helpers in
  utils, and changed most places where hashes
  are calculated to use them.
* Charms are now uploaded to storage with a
  randomly generated archive names with the
  format "<charm-name>-<revision>-<uuid>".
  This allows multiple concurrent uploads
  to happen safely, and at the end AddCharm
  in the API checks to see if the charm info
  is already updated in state and if so, deletes
  the duplicated upload.
* Added GetEnvironStorage helper to environs/testing.
* Fixed potential compatibility issues with older
  versions and the recently added PendingUpload
  and Placeholder fields of the charm document.

Also tested multiple concurrent deployments with
the local provider manually and updated the bug
accordingly.

https://codereview.appspot.com/53210044/

To post a comment you must log in.
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Reviewers: mp+203291_code.launchpad.net,

Message:
Please take a look.

Description:
state;apiserver: Fix a race - lp bug #1067979

This introduces some changes to how charm store
charms are added through the API (in state and
to provider storage). Now PrepareStoreCharmUpload
is called before trying to download the charm,
repackage it and upload it to storage, in order
to reserve a charm URL in state with pending
state. Added a test that demonstrates multiple
concurrent deployments of the same charm does
not cause the race issues, like mentioned in
the bug.

Also tested manually with the local provider
and updated the bug accordingly.

https://code.launchpad.net/~dimitern/juju-core/260-lp-1067979-duplicate-charm/+merge/203291

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/53210044/

Affected files (+236, -54 lines):
   A [revision details]
   M state/apiserver/charms_test.go
   M state/apiserver/client/client.go
   M state/apiserver/client/client_test.go
   M state/state.go
   M state/state_test.go

Revision history for this message
William Reade (fwereade) wrote :

A few comments, reprising what we discussed at lunch

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client.go
File state/apiserver/client/client.go (right):

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client.go#newcode885
state/apiserver/client/client.go:885: storageURL, err :=
storage.URL(name)
I'd prefer to generate unique names than to potentially have multiple
writers to the same storage location. I think it'd be best to have
multiple independent downloads, and multiple independent uploads, and
then just to have a single worker win the race and set a URL that
definitely worked in state. No need to rename or anything -- as far as
I'm aware, charm URL is totally independent of storage URL anyway, and
if it's not that's a bug that should be fixed anyway ;).

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client_test.go#newcode1850
state/apiserver/client/client_test.go:1850: }
in light of the previous comment,we *do* want multiple charms uploaded
(but all except one to be deleted). And whichever one isn't deleted
should match the archive url of the charm in state.

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client.go
File state/apiserver/client/client.go (right):

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client.go#newcode885
state/apiserver/client/client.go:885: storageURL, err :=
storage.URL(name)
On 2014/01/27 12:46:16, fwereade wrote:
> I'd prefer to generate unique names than to potentially have multiple
writers to
> the same storage location. I think it'd be best to have multiple
independent
> downloads, and multiple independent uploads, and then just to have a
single
> worker win the race and set a URL that definitely worked in state. No
need to
> rename or anything -- as far as I'm aware, charm URL is totally
independent of
> storage URL anyway, and if it's not that's a bug that should be fixed
anyway ;).

I'll add code to generate a storage URL based on a random UUID string.
If you don't mind, I'll also follow John's suggestion to include the
charm name and revision as prefix, to make the string a bit less
cryptic.

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/1/state/apiserver/client/client_test.go#newcode1850
state/apiserver/client/client_test.go:1850: }
On 2014/01/27 12:46:16, fwereade wrote:
> in light of the previous comment,we *do* want multiple charms uploaded
(but all
> except one to be deleted). And whichever one isn't deleted should
match the
> archive url of the charm in state.

Will change accordingly.

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
William Reade (fwereade) wrote :

few more comments, but the heart of it looks solid

https://codereview.appspot.com/53210044/diff/40001/charm/url.go
File charm/url.go (right):

https://codereview.appspot.com/53210044/diff/40001/charm/url.go#newcode261
charm/url.go:261: func (u *URL) ArchiveName() (string, error) {
I'm certain this is not part of the charm package -- np with the code,
but it's very definitely an internal detail of the state server.

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/charms.go
File state/apiserver/charms.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/charms.go#newcode243
state/apiserver/charms.go:243: func GetSHA256(source io.Reader) (string,
int64, error) {
whereas this feels maybe utilsy -- and we do this *all over the place*,
so please hunt them down and make them all use the same utils func,
assuming that is practical

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client.go
File state/apiserver/client/client.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client.go#newcode908
state/apiserver/client/client.go:908: return fmt.Errorf("cannot remove
duplicated charm from storage: %v", err)
log it, I think, but don't spoil the user's day -- we still managed to
do what she asked, after all

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go#newcode1821
state/apiserver/client/client_test.go:1821: close(ops)
race? we should expect 9 Deletes as well, I think

https://codereview.appspot.com/53210044/diff/40001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode606
state/state.go:606: err = st.charms.Find(D{{"_id", curl},
{"placeholder", false}}).One(&uploadedCharm)
placeholder might be false in a new installation, or missing in an old
one

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode624
state/state.go:624: Insert: uploadedCharm,
if there *was* a placeholder charm, the insert would fail, wouldn't it?

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode642
state/state.go:642: StillPlaceholder = D{{"placeholder", true}}
I think these should probably not be exported

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Download full text (3.3 KiB)

Please take a look.

https://codereview.appspot.com/53210044/diff/40001/charm/url.go
File charm/url.go (right):

https://codereview.appspot.com/53210044/diff/40001/charm/url.go#newcode261
charm/url.go:261: func (u *URL) ArchiveName() (string, error) {
On 2014/01/28 08:19:41, fwereade wrote:
> I'm certain this is not part of the charm package -- np with the code,
but it's
> very definitely an internal detail of the state server.

I'll move it to the state package then.

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/charms.go
File state/apiserver/charms.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/charms.go#newcode243
state/apiserver/charms.go:243: func GetSHA256(source io.Reader) (string,
int64, error) {
On 2014/01/28 08:19:41, fwereade wrote:
> whereas this feels maybe utilsy -- and we do this *all over the
place*, so
> please hunt them down and make them all use the same utils func,
assuming that
> is practical

Done.

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client.go
File state/apiserver/client/client.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client.go#newcode908
state/apiserver/client/client.go:908: return fmt.Errorf("cannot remove
duplicated charm from storage: %v", err)
On 2014/01/28 08:19:41, fwereade wrote:
> log it, I think, but don't spoil the user's day -- we still managed to
do what
> she asked, after all

Wasn't sure whether to log an error or warning. I'll just log it as an
error then, because it seems serious enough (it shouldn't happen
usually).

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go#newcode1821
state/apiserver/client/client_test.go:1821: close(ops)
On 2014/01/28 08:19:41, fwereade wrote:
> race? we should expect 9 Deletes as well, I think

There is no way to detect storage removals with the dummy provider I
could find. But I am checking below that we do have a single upload at
the end. Not sure what race do you mean?

https://codereview.appspot.com/53210044/diff/40001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode606
state/state.go:606: err = st.charms.Find(D{{"_id", curl},
{"placeholder", false}}).One(&uploadedCharm)
On 2014/01/28 08:19:41, fwereade wrote:
> placeholder might be false in a new installation, or missing in an old
one

I'll modify the condition, but this applies to other places in the code
as well.

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode624
state/state.go:624: Insert: uploadedCharm,
On 2014/01/28 08:19:41, fwereade wrote:
> if there *was* a placeholder charm, the insert would fail, wouldn't
it?

Right, but since I changed how we select the document above it shouldn't
be a problem.

https://codereview.appspot.com/53210044/diff/40001/state/state.go#newcode642
state/state.go:642: StillPlaceholder = D{{"placeholder", true}}
On 2014/01/28 08:19:41, fwereade wrote:
> I think these should pr...

Read more...

Revision history for this message
William Reade (fwereade) wrote :

Very nice, last round I think

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/40001/state/apiserver/client/client_test.go#newcode1821
state/apiserver/client/client_test.go:1821: close(ops)
On 2014/01/28 10:55:05, dimitern wrote:
> On 2014/01/28 08:19:41, fwereade wrote:
> > race? we should expect 9 Deletes as well, I think

> There is no way to detect storage removals with the dummy provider I
could find.
> But I am checking below that we do have a single upload at the end.
Not sure
> what race do you mean?

I was worried that the deletes *were* pushing ops, and that channel
might sometimes be closed before the ops stopped. But yeah, looks like
no delete ops; and I'm fine with the test. Just seemed a bit weird to
mix the testing styles -- but I think we're good as is. Thanks.

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/charms.go
File state/apiserver/charms.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/charms.go#newcode229
state/apiserver/charms.go:229: func GetEnvironStorage(st *state.State)
(storage.Storage, error) {
maybe put this in export_test rather than growing the interface here?

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/client/client_test.go#newcode1828
state/apiserver/client/client_test.go:1828: storage, err :=
apiserver.GetEnvironStorage(s.State)
ah, ok, you can't put it in export_test. Hmm. I think I'd maybe prefer
it if this were accessed via the environ on the test, but I'm not sure
of the tradeoffs -- I'll leave it to your judgment.

https://codereview.appspot.com/53210044/diff/60001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode480
state/state.go:480: what := D{{"_id", curl}, {"placeholder", false},
{"pendingupload", false}}
Yeah, these bits do need to be fixed. Sorry, but they *will* break
stuff.

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode625
state/state.go:625: Insert: uploadedCharm,
Don't we still need to deal with a placeholder here?

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go
File utils/trivial.go (right):

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode127
utils/trivial.go:127: func GetSHA256(source io.Reader) (string, int64,
error) {
ReadSHA256?

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode139
utils/trivial.go:139: func GetFileSHA256(filename string) (string,
int64, error) {
ReadFileSHA256?

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode146
utils/trivial.go:146: }
Also, please test these. Not quite trivial enough to be skippable IMO.

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/charms.go
File state/apiserver/charms.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/charms.go#newcode229
state/apiserver/charms.go:229: func GetEnvironStorage(st *state.State)
(storage.Storage, error) {
On 2014/01/28 12:00:32, fwereade wrote:
> maybe put this in export_test rather than growing the interface here?

It was there, but I needed it in two places. I'll put it in
environs/testing.

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/client/client_test.go
File state/apiserver/client/client_test.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/apiserver/client/client_test.go#newcode1828
state/apiserver/client/client_test.go:1828: storage, err :=
apiserver.GetEnvironStorage(s.State)
On 2014/01/28 12:00:32, fwereade wrote:
> ah, ok, you can't put it in export_test. Hmm. I think I'd maybe prefer
it if
> this were accessed via the environ on the test, but I'm not sure of
the
> tradeoffs -- I'll leave it to your judgment.

I'll put it in environs/testing.

https://codereview.appspot.com/53210044/diff/60001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode480
state/state.go:480: what := D{{"_id", curl}, {"placeholder", false},
{"pendingupload", false}}
On 2014/01/28 12:00:32, fwereade wrote:
> Yeah, these bits do need to be fixed. Sorry, but they *will* break
stuff.

Done.

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode625
state/state.go:625: Insert: uploadedCharm,
On 2014/01/28 12:00:32, fwereade wrote:
> Don't we still need to deal with a placeholder here?

If we reach this point we know it's not a placeholder and there's no
document for this charm URL. Not setting Placeholder: false here works,
because it's the default value. So it's OK I think.

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go
File utils/trivial.go (right):

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode127
utils/trivial.go:127: func GetSHA256(source io.Reader) (string, int64,
error) {
On 2014/01/28 12:00:32, fwereade wrote:
> ReadSHA256?

Done.

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode139
utils/trivial.go:139: func GetFileSHA256(filename string) (string,
int64, error) {
On 2014/01/28 12:00:32, fwereade wrote:
> ReadFileSHA256?

Done.

https://codereview.appspot.com/53210044/diff/60001/utils/trivial.go#newcode146
utils/trivial.go:146: }
On 2014/01/28 12:00:32, fwereade wrote:
> Also, please test these. Not quite trivial enough to be skippable IMO.

Done.

https://codereview.appspot.com/53210044/

Revision history for this message
William Reade (fwereade) wrote :

I still think there's something up with PrepareStoreCharmUpload, but
it's getting late and I may be being dense. Ping me tomorrow morning?

https://codereview.appspot.com/53210044/diff/60001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode625
state/state.go:625: Insert: uploadedCharm,
On 2014/01/28 17:15:02, dimitern wrote:
> On 2014/01/28 12:00:32, fwereade wrote:
> > Don't we still need to deal with a placeholder here?

> If we reach this point we know it's not a placeholder and there's no
document
> for this charm URL. Not setting Placeholder: false here works, because
it's the
> default value. So it's OK I think.

We will still have fallen through the block above if err is nil but
placeholder is true. Right? I had to think about this harder than I'd
like, which might indicate that we could make that block clearer, but
nothing really springs to mind very obviously... perhaps a switch?

ops := []txn.Op{}
switch err {
case mgo.ErrNotFound:
     ops = append(ops, <insert op>)
case nil:
     if uploadedCharm.Placeholder {
         ops = append(ops, <update op>)
     } else {
         return newCharm(st, &uploadedCharm)
     }
default:
     return nil, err
}

..? Maybe not very compelling?

This might be a case where really pedantic SetTransactionHooks-style
testing would be a boon -- even if the logic is hard to follow we can
just churn through the possible test cases and have some more certainty.

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/53210044/diff/60001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/60001/state/state.go#newcode625
state/state.go:625: Insert: uploadedCharm,
On 2014/01/28 23:09:31, fwereade wrote:
> On 2014/01/28 17:15:02, dimitern wrote:
> > On 2014/01/28 12:00:32, fwereade wrote:
> > > Don't we still need to deal with a placeholder here?
> >
> > If we reach this point we know it's not a placeholder and there's no
document
> > for this charm URL. Not setting Placeholder: false here works,
because it's
> the
> > default value. So it's OK I think.

> We will still have fallen through the block above if err is nil but
placeholder
> is true. Right? I had to think about this harder than I'd like, which
might
> indicate that we could make that block clearer, but nothing really
springs to
> mind very obviously... perhaps a switch?

> ops := []txn.Op{}
> switch err {
> case mgo.ErrNotFound:
> ops = append(ops, <insert op>)
> case nil:
> if uploadedCharm.Placeholder {
> ops = append(ops, <update op>)
> } else {
> return newCharm(st, &uploadedCharm)
> }
> default:
> return nil, err
> }

> ..? Maybe not very compelling?

> This might be a case where really pedantic SetTransactionHooks-style
testing
> would be a boon -- even if the logic is hard to follow we can just
churn through
> the possible test cases and have some more certainty.

I added a specific test case to cover "uploading a store charm when a
placeholder exists", and indeed it uncovered issues, which I fixed now.
Thanks!

As for the transaction hooks, I can't use them unfortunately outside the
state packages (they're all defined in export_test). But despite that, I
think the code looks solid now.

https://codereview.appspot.com/53210044/

Revision history for this message
William Reade (fwereade) wrote :

Most of these are genuinely trivial now -- if we're in agreement on them
all, LGTM, but ping me if there's any uncertainty

https://codereview.appspot.com/53210044/diff/100001/state/charm.go
File state/charm.go (right):

https://codereview.appspot.com/53210044/diff/100001/state/charm.go#newcode87
state/charm.go:87: func CharmArchiveName(name string, revision int)
(string, error) {
quibble quibble whine, I think this probably belongs in apiserver/client

https://codereview.appspot.com/53210044/diff/100001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/100001/state/state.go#newcode633
state/state.go:633: Assert: txn.DocExists,
hmm, should be be asserting that it hasn't had archiveurl (etc) filled
in? I think there's a very very cornery corner case in which we read
while it's still a placeholder, but write when it's already uploaded.

That'd be a good transaction-hooks test ;).

https://codereview.appspot.com/53210044/diff/100001/state/state.go#newcode661
state/state.go:661: if err != nil {
this makes me twitch a little -- AFAICT the only possible value is
txn.ErrAborted, but that assumption feels very vulnerable to code
perturbations in the loop above. Maybe check for err == nil inside the
loop, and return newCharm() there, so that ErrAborted becomes the *only*
way to fall out of the loop, and just always return
ErrExcessiveContention?

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go
File utils/trivial_test.go (right):

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go#newcode87
utils/trivial_test.go:87: {
}{{
     ...
},{
     ...

to compress both vertical and horizontal space?

And, hey, can we please just move these inside the test func? If they're
not used elsewhere they really don't deserve to be global IMO.

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go#newcode105
utils/trivial_test.go:105: func (utilsSuite)
TestReadSHA256AndReadFileSHA256(c *gc.C) {
*utilsSuite, please, across the board, lest someone add methods that
require pointer receivers and silently deactivate all the other tests.

https://codereview.appspot.com/53210044/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Please take a look.

https://codereview.appspot.com/53210044/diff/100001/state/charm.go
File state/charm.go (right):

https://codereview.appspot.com/53210044/diff/100001/state/charm.go#newcode87
state/charm.go:87: func CharmArchiveName(name string, revision int)
(string, error) {
On 2014/01/29 16:01:22, fwereade wrote:
> quibble quibble whine, I think this probably belongs in
apiserver/client

Done.

https://codereview.appspot.com/53210044/diff/100001/state/state.go
File state/state.go (right):

https://codereview.appspot.com/53210044/diff/100001/state/state.go#newcode633
state/state.go:633: Assert: txn.DocExists,
On 2014/01/29 16:01:22, fwereade wrote:
> hmm, should be be asserting that it hasn't had archiveurl (etc) filled
in? I
> think there's a very very cornery corner case in which we read while
it's still
> a placeholder, but write when it's already uploaded.

> That'd be a good transaction-hooks test ;).

Done.

https://codereview.appspot.com/53210044/diff/100001/state/state.go#newcode661
state/state.go:661: if err != nil {
On 2014/01/29 16:01:22, fwereade wrote:
> this makes me twitch a little -- AFAICT the only possible value is
> txn.ErrAborted, but that assumption feels very vulnerable to code
perturbations
> in the loop above. Maybe check for err == nil inside the loop, and
return
> newCharm() there, so that ErrAborted becomes the *only* way to fall
out of the
> loop, and just always return ErrExcessiveContention?

Done.

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go
File utils/trivial_test.go (right):

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go#newcode87
utils/trivial_test.go:87: {
On 2014/01/29 16:01:22, fwereade wrote:
> }{{
> ...
> },{
> ...

> to compress both vertical and horizontal space?

> And, hey, can we please just move these inside the test func? If
they're not
> used elsewhere they really don't deserve to be global IMO.

Done.

https://codereview.appspot.com/53210044/diff/100001/utils/trivial_test.go#newcode105
utils/trivial_test.go:105: func (utilsSuite)
TestReadSHA256AndReadFileSHA256(c *gc.C) {
On 2014/01/29 16:01:22, fwereade wrote:
> *utilsSuite, please, across the board, lest someone add methods that
require
> pointer receivers and silently deactivate all the other tests.

Done.

https://codereview.appspot.com/53210044/

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (592.0 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.291s
ok launchpad.net/juju-core/agent/tools 0.305s
ok launchpad.net/juju-core/bzr 7.397s
ok launchpad.net/juju-core/cert 3.282s
ok launchpad.net/juju-core/charm 0.605s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.033s
ok launchpad.net/juju-core/cloudinit/sshinit 1.105s
ok launchpad.net/juju-core/cmd 0.247s
ok launchpad.net/juju-core/cmd/charm-admin 0.867s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 277.574s
ok launchpad.net/juju-core/cmd/jujud 62.207s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 9.802s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.028s
ok launchpad.net/juju-core/container 0.037s
ok launchpad.net/juju-core/container/factory 0.052s
ok launchpad.net/juju-core/container/kvm 0.249s
ok launchpad.net/juju-core/container/kvm/mock 0.036s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.422s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.263s
ok launchpad.net/juju-core/environs 3.143s
ok launchpad.net/juju-core/environs/bootstrap 5.124s
ok launchpad.net/juju-core/environs/cloudinit 0.736s
ok launchpad.net/juju-core/environs/config 3.309s
ok launchpad.net/juju-core/environs/configstore 0.056s
ok launchpad.net/juju-core/environs/filestorage 0.035s
ok launchpad.net/juju-core/environs/httpstorage 0.864s
ok launchpad.net/juju-core/environs/imagemetadata 0.652s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.063s
ok launchpad.net/juju-core/environs/jujutest 0.216s
ok launchpad.net/juju-core/environs/manual 8.488s
ok launchpad.net/juju-core/environs/simplestreams 0.401s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.387s
ok launchpad.net/juju-core/environs/storage 1.199s
ok launchpad.net/juju-core/environs/sync 35.792s
ok launchpad.net/juju-core/environs/testing 0.217s
ok launchpad.net/juju-core/environs/tools 6.906s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.016s
ok launchpad.net/juju-core/instance 0.023s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 24.611s
ok launchpad.net/juju-core/juju/osenv 0.021s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.016s
ok launchpad.net/juju-core/log/syslog 0.025s
ok launchpad.net/juju-core/names 0.028s
? launchpad....

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (591.7 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 0.855s
ok launchpad.net/juju-core/agent/tools 0.243s
ok launchpad.net/juju-core/bzr 7.603s
ok launchpad.net/juju-core/cert 3.491s
ok launchpad.net/juju-core/charm 0.600s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.033s
ok launchpad.net/juju-core/cloudinit/sshinit 1.085s
ok launchpad.net/juju-core/cmd 0.213s
ok launchpad.net/juju-core/cmd/charm-admin 0.864s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 238.171s
ok launchpad.net/juju-core/cmd/jujud 62.620s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 11.807s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.030s
ok launchpad.net/juju-core/container 0.037s
ok launchpad.net/juju-core/container/factory 0.052s
ok launchpad.net/juju-core/container/kvm 0.295s
ok launchpad.net/juju-core/container/kvm/mock 0.052s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.445s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.387s
ok launchpad.net/juju-core/environs 3.076s
ok launchpad.net/juju-core/environs/bootstrap 4.712s
ok launchpad.net/juju-core/environs/cloudinit 0.631s
ok launchpad.net/juju-core/environs/config 3.180s
ok launchpad.net/juju-core/environs/configstore 0.040s
ok launchpad.net/juju-core/environs/filestorage 0.034s
ok launchpad.net/juju-core/environs/httpstorage 0.956s
ok launchpad.net/juju-core/environs/imagemetadata 0.601s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.061s
ok launchpad.net/juju-core/environs/jujutest 0.229s
ok launchpad.net/juju-core/environs/manual 9.763s
ok launchpad.net/juju-core/environs/simplestreams 0.367s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.223s
ok launchpad.net/juju-core/environs/storage 1.177s
ok launchpad.net/juju-core/environs/sync 34.614s
ok launchpad.net/juju-core/environs/testing 0.214s
ok launchpad.net/juju-core/environs/tools 6.831s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.016s
ok launchpad.net/juju-core/instance 0.024s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 24.499s
ok launchpad.net/juju-core/juju/osenv 0.022s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.016s
ok launchpad.net/juju-core/log/syslog 0.025s
ok launchpad.net/juju-core/names 0.027s
? launchpad...

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (675.3 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.313s
ok launchpad.net/juju-core/agent/tools 0.253s
ok launchpad.net/juju-core/bzr 7.768s
ok launchpad.net/juju-core/cert 3.751s
ok launchpad.net/juju-core/charm 0.598s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.032s
ok launchpad.net/juju-core/cloudinit/sshinit 1.137s
ok launchpad.net/juju-core/cmd 0.243s
ok launchpad.net/juju-core/cmd/charm-admin 0.817s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 258.730s
ok launchpad.net/juju-core/cmd/jujud 62.132s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 9.465s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.028s
ok launchpad.net/juju-core/container 0.038s
ok launchpad.net/juju-core/container/factory 0.055s
ok launchpad.net/juju-core/container/kvm 0.342s
ok launchpad.net/juju-core/container/kvm/mock 0.056s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.421s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.320s
ok launchpad.net/juju-core/environs 3.263s
ok launchpad.net/juju-core/environs/bootstrap 4.696s
ok launchpad.net/juju-core/environs/cloudinit 0.676s
ok launchpad.net/juju-core/environs/config 4.070s
ok launchpad.net/juju-core/environs/configstore 0.080s
ok launchpad.net/juju-core/environs/filestorage 0.034s
ok launchpad.net/juju-core/environs/httpstorage 0.908s
ok launchpad.net/juju-core/environs/imagemetadata 0.695s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.063s
ok launchpad.net/juju-core/environs/jujutest 0.257s
ok launchpad.net/juju-core/environs/manual 9.759s
ok launchpad.net/juju-core/environs/simplestreams 0.355s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.335s
ok launchpad.net/juju-core/environs/storage 1.165s
ok launchpad.net/juju-core/environs/sync 35.200s
ok launchpad.net/juju-core/environs/testing 0.209s
ok launchpad.net/juju-core/environs/tools 6.839s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.016s
ok launchpad.net/juju-core/instance 0.024s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 21.534s
ok launchpad.net/juju-core/juju/osenv 0.020s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.015s
ok launchpad.net/juju-core/log/syslog 0.022s
ok launchpad.net/juju-core/names 0.027s
? launchpad....

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (672.8 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.241s
ok launchpad.net/juju-core/agent/tools 0.265s
ok launchpad.net/juju-core/bzr 7.509s
ok launchpad.net/juju-core/cert 3.259s
ok launchpad.net/juju-core/charm 0.659s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.033s
ok launchpad.net/juju-core/cloudinit/sshinit 1.063s
ok launchpad.net/juju-core/cmd 0.231s
ok launchpad.net/juju-core/cmd/charm-admin 0.827s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 257.042s
ok launchpad.net/juju-core/cmd/jujud 63.020s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 13.084s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.029s
ok launchpad.net/juju-core/container 0.043s
ok launchpad.net/juju-core/container/factory 0.074s
ok launchpad.net/juju-core/container/kvm 0.257s
ok launchpad.net/juju-core/container/kvm/mock 0.036s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.363s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.281s
ok launchpad.net/juju-core/environs 3.259s
ok launchpad.net/juju-core/environs/bootstrap 4.443s
ok launchpad.net/juju-core/environs/cloudinit 0.672s
ok launchpad.net/juju-core/environs/config 2.722s
ok launchpad.net/juju-core/environs/configstore 0.049s
ok launchpad.net/juju-core/environs/filestorage 0.032s
ok launchpad.net/juju-core/environs/httpstorage 1.023s
ok launchpad.net/juju-core/environs/imagemetadata 0.685s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.070s
ok launchpad.net/juju-core/environs/jujutest 0.249s
ok launchpad.net/juju-core/environs/manual 8.911s
ok launchpad.net/juju-core/environs/simplestreams 0.359s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.247s
ok launchpad.net/juju-core/environs/storage 1.211s
ok launchpad.net/juju-core/environs/sync 34.656s
ok launchpad.net/juju-core/environs/testing 0.203s
ok launchpad.net/juju-core/environs/tools 6.884s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.016s
ok launchpad.net/juju-core/instance 0.023s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 25.459s
ok launchpad.net/juju-core/juju/osenv 0.020s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.015s
ok launchpad.net/juju-core/log/syslog 0.035s
ok launchpad.net/juju-core/names 0.027s
? launchpad...

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Self-approving after fixing uniter tests.

review: Approve
Revision history for this message
Go Bot (go-bot) wrote :
Download full text (100.5 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.293s
ok launchpad.net/juju-core/agent/tools 0.253s
ok launchpad.net/juju-core/bzr 7.562s
ok launchpad.net/juju-core/cert 3.083s
ok launchpad.net/juju-core/charm 0.669s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.032s
ok launchpad.net/juju-core/cloudinit/sshinit 1.096s
ok launchpad.net/juju-core/cmd 0.279s
ok launchpad.net/juju-core/cmd/charm-admin 0.867s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 253.577s
*** Test killed: ran too long (10m0s).
FAIL launchpad.net/juju-core/cmd/jujud 600.018s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 13.476s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.048s
ok launchpad.net/juju-core/container 0.055s
ok launchpad.net/juju-core/container/factory 0.061s
ok launchpad.net/juju-core/container/kvm 0.307s
ok launchpad.net/juju-core/container/kvm/mock 0.048s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.384s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.287s
ok launchpad.net/juju-core/environs 3.200s
ok launchpad.net/juju-core/environs/bootstrap 4.596s
ok launchpad.net/juju-core/environs/cloudinit 0.660s
ok launchpad.net/juju-core/environs/config 2.831s
ok launchpad.net/juju-core/environs/configstore 0.062s
ok launchpad.net/juju-core/environs/filestorage 0.040s
ok launchpad.net/juju-core/environs/httpstorage 1.054s
ok launchpad.net/juju-core/environs/imagemetadata 0.666s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.080s
ok launchpad.net/juju-core/environs/jujutest 0.303s
ok launchpad.net/juju-core/environs/manual 9.640s
ok launchpad.net/juju-core/environs/simplestreams 0.380s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 2.166s
ok launchpad.net/juju-core/environs/storage 1.244s
ok launchpad.net/juju-core/environs/sync 34.538s
ok launchpad.net/juju-core/environs/testing 0.243s
ok launchpad.net/juju-core/environs/tools 7.067s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.025s
ok launchpad.net/juju-core/instance 0.035s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 27.392s
ok launchpad.net/juju-core/juju/osenv 0.040s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.029s
ok launchpad.net/juju-core/log/syslog 0.027s
ok launchpad.n...

Revision history for this message
Dimiter Naydenov (dimitern) wrote :
Revision history for this message
Go Bot (go-bot) wrote :
Download full text (8.9 KiB)

The attempt to merge lp:~dimitern/juju-core/260-lp-1067979-duplicate-charm into lp:juju-core failed. Below is the output from the failed tests.

ok launchpad.net/juju-core/agent 1.188s
ok launchpad.net/juju-core/agent/tools 0.257s
ok launchpad.net/juju-core/bzr 7.914s
ok launchpad.net/juju-core/cert 3.140s
ok launchpad.net/juju-core/charm 0.642s
? launchpad.net/juju-core/charm/hooks [no test files]
? launchpad.net/juju-core/charm/testing [no test files]
ok launchpad.net/juju-core/cloudinit 0.033s
ok launchpad.net/juju-core/cloudinit/sshinit 1.071s
ok launchpad.net/juju-core/cmd 0.222s
ok launchpad.net/juju-core/cmd/charm-admin 0.865s
? launchpad.net/juju-core/cmd/charmd [no test files]
? launchpad.net/juju-core/cmd/charmload [no test files]
ok launchpad.net/juju-core/cmd/juju 250.766s
ok launchpad.net/juju-core/cmd/jujud 63.331s
ok launchpad.net/juju-core/cmd/plugins/juju-metadata 12.447s
? launchpad.net/juju-core/cmd/plugins/juju-restore [no test files]
ok launchpad.net/juju-core/constraints 0.029s
ok launchpad.net/juju-core/container 0.038s
ok launchpad.net/juju-core/container/factory 0.065s
ok launchpad.net/juju-core/container/kvm 0.354s
ok launchpad.net/juju-core/container/kvm/mock 0.055s
? launchpad.net/juju-core/container/kvm/testing [no test files]
ok launchpad.net/juju-core/container/lxc 0.314s
? launchpad.net/juju-core/container/lxc/mock [no test files]
? launchpad.net/juju-core/container/lxc/testing [no test files]
? launchpad.net/juju-core/container/testing [no test files]
ok launchpad.net/juju-core/downloader 5.287s
ok launchpad.net/juju-core/environs 3.346s
ok launchpad.net/juju-core/environs/bootstrap 5.054s
ok launchpad.net/juju-core/environs/cloudinit 0.684s
ok launchpad.net/juju-core/environs/config 5.756s
ok launchpad.net/juju-core/environs/configstore 0.068s
ok launchpad.net/juju-core/environs/filestorage 0.034s
ok launchpad.net/juju-core/environs/httpstorage 1.023s
ok launchpad.net/juju-core/environs/imagemetadata 0.799s
? launchpad.net/juju-core/environs/imagemetadata/testing [no test files]
ok launchpad.net/juju-core/environs/instances 0.063s
ok launchpad.net/juju-core/environs/jujutest 0.339s
ok launchpad.net/juju-core/environs/manual 7.733s
ok launchpad.net/juju-core/environs/simplestreams 0.331s
? launchpad.net/juju-core/environs/simplestreams/testing [no test files]
ok launchpad.net/juju-core/environs/sshstorage 1.588s
ok launchpad.net/juju-core/environs/storage 1.217s
ok launchpad.net/juju-core/environs/sync 36.899s
ok launchpad.net/juju-core/environs/testing 0.201s
ok launchpad.net/juju-core/environs/tools 7.064s
? launchpad.net/juju-core/environs/tools/testing [no test files]
ok launchpad.net/juju-core/errors 0.017s
ok launchpad.net/juju-core/instance 0.024s
? launchpad.net/juju-core/instance/testing [no test files]
ok launchpad.net/juju-core/juju 30.207s
ok launchpad.net/juju-core/juju/osenv 0.021s
? launchpad.net/juju-core/juju/testing [no test files]
ok launchpad.net/juju-core/log 0.016s
ok launchpad.net/juju-core/log/syslog 0.029s
ok launchpad.net/juju-core/names 0.030s
? launchpad...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charm/repo.go'
--- charm/repo.go 2014-01-22 16:48:19 +0000
+++ charm/repo.go 2014-01-30 14:23:21 +0000
@@ -4,8 +4,6 @@
4package charm4package charm
55
6import (6import (
7 "crypto/sha256"
8 "encoding/hex"
9 "encoding/json"7 "encoding/json"
10 "fmt"8 "fmt"
11 "io"9 "io"
@@ -299,16 +297,11 @@
299// verify returns an error unless a file exists at path with a hex-encoded297// verify returns an error unless a file exists at path with a hex-encoded
300// SHA256 matching digest.298// SHA256 matching digest.
301func verify(path, digest string) error {299func verify(path, digest string) error {
302 f, err := os.Open(path)300 hash, _, err := utils.ReadFileSHA256(path)
303 if err != nil {301 if err != nil {
304 return err302 return err
305 }303 }
306 defer f.Close()304 if hash != digest {
307 h := sha256.New()
308 if _, err := io.Copy(h, f); err != nil {
309 return err
310 }
311 if hex.EncodeToString(h.Sum(nil)) != digest {
312 return fmt.Errorf("bad SHA256 of %q", path)305 return fmt.Errorf("bad SHA256 of %q", path)
313 }306 }
314 return nil307 return nil
315308
=== modified file 'charm/testing/mockstore.go'
--- charm/testing/mockstore.go 2014-01-28 04:58:43 +0000
+++ charm/testing/mockstore.go 2014-01-30 14:23:21 +0000
@@ -4,12 +4,12 @@
4package testing4package testing
55
6import (6import (
7 "crypto/sha256"7 "bytes"
8 "encoding/hex"
9 "encoding/json"8 "encoding/json"
10 "io/ioutil"9 "io"
11 "net"10 "net"
12 "net/http"11 "net/http"
12 "os"
13 "strconv"13 "strconv"
14 "strings"14 "strings"
1515
@@ -18,6 +18,7 @@
1818
19 "launchpad.net/juju-core/charm"19 "launchpad.net/juju-core/charm"
20 "launchpad.net/juju-core/testing"20 "launchpad.net/juju-core/testing"
21 "launchpad.net/juju-core/utils"
21)22)
2223
23var logger = loggo.GetLogger("juju.charm.testing.mockstore")24var logger = loggo.GetLogger("juju.charm.testing.mockstore")
@@ -38,12 +39,14 @@
38// NewMockStore creates a mock charm store containing the specified charms.39// NewMockStore creates a mock charm store containing the specified charms.
39func NewMockStore(c *gc.C, charms map[string]int) *MockStore {40func NewMockStore(c *gc.C, charms map[string]int) *MockStore {
40 s := &MockStore{charms: charms}41 s := &MockStore{charms: charms}
41 bytes, err := ioutil.ReadFile(testing.Charms.BundlePath(c.MkDir(), "dummy"))42 f, err := os.Open(testing.Charms.BundlePath(c.MkDir(), "dummy"))
42 c.Assert(err, gc.IsNil)43 c.Assert(err, gc.IsNil)
43 s.bundleBytes = bytes44 defer f.Close()
44 h := sha256.New()45 buf := &bytes.Buffer{}
45 h.Write(bytes)46 s.bundleSha256, _, err = utils.ReadSHA256(io.TeeReader(f, buf))
46 s.bundleSha256 = hex.EncodeToString(h.Sum(nil))47 c.Assert(err, gc.IsNil)
48 s.bundleBytes = buf.Bytes()
49 c.Assert(err, gc.IsNil)
47 s.mux = http.NewServeMux()50 s.mux = http.NewServeMux()
48 s.mux.HandleFunc("/charm-info", s.serveInfo)51 s.mux.HandleFunc("/charm-info", s.serveInfo)
49 s.mux.HandleFunc("/charm-event", s.serveEvent)52 s.mux.HandleFunc("/charm-event", s.serveEvent)
5053
=== modified file 'environs/sync/sync.go'
--- environs/sync/sync.go 2014-01-30 06:21:03 +0000
+++ environs/sync/sync.go 2014-01-30 14:23:21 +0000
@@ -5,7 +5,6 @@
55
6import (6import (
7 "bytes"7 "bytes"
8 "crypto/sha256"
9 "fmt"8 "fmt"
10 "io"9 "io"
11 "io/ioutil"10 "io/ioutil"
@@ -166,22 +165,17 @@
166 if err != nil {165 if err != nil {
167 return err166 return err
168 }167 }
168 buf := &bytes.Buffer{}
169 srcFile := resp.Body169 srcFile := resp.Body
170 defer srcFile.Close()170 defer srcFile.Close()
171 // We have to buffer the content, because Put requires the content171 tool.SHA256, tool.Size, err = utils.ReadSHA256(io.TeeReader(srcFile, buf))
172 // length, but Get only returns us a ReadCloser
173 buf := &bytes.Buffer{}
174 nBytes, err := io.Copy(buf, srcFile)
175 if err != nil {172 if err != nil {
176 return err173 return err
177 }174 }
178 logger.Infof("downloaded %v (%dkB), uploading", toolsName, (nBytes+512)/1024)175 sizeInKB := (tool.Size + 512) / 1024
179 logger.Infof("download %dkB, uploading", (nBytes+512)/1024)176 logger.Infof("downloaded %v (%dkB), uploading", toolsName, sizeInKB)
180 sha256hash := sha256.New()177 logger.Infof("download %dkB, uploading", sizeInKB)
181 sha256hash.Write(buf.Bytes())178 return dest.Put(toolsName, buf, tool.Size)
182 tool.SHA256 = fmt.Sprintf("%x", sha256hash.Sum(nil))
183 tool.Size = nBytes
184 return dest.Put(toolsName, buf, nBytes)
185}179}
186180
187// UploadFunc is the type of Upload, which may be181// UploadFunc is the type of Upload, which may be
188182
=== modified file 'environs/testing/storage.go'
--- environs/testing/storage.go 2014-01-14 06:37:55 +0000
+++ environs/testing/storage.go 2014-01-30 14:23:21 +0000
@@ -4,13 +4,16 @@
4package testing4package testing
55
6import (6import (
7 "fmt"
7 "io"8 "io"
89
9 gc "launchpad.net/gocheck"10 gc "launchpad.net/gocheck"
1011
12 "launchpad.net/juju-core/environs"
11 "launchpad.net/juju-core/environs/filestorage"13 "launchpad.net/juju-core/environs/filestorage"
12 "launchpad.net/juju-core/environs/httpstorage"14 "launchpad.net/juju-core/environs/httpstorage"
13 "launchpad.net/juju-core/environs/storage"15 "launchpad.net/juju-core/environs/storage"
16 "launchpad.net/juju-core/state"
14)17)
1518
16// CreateLocalTestStorage returns the listener, which needs to be closed, and19// CreateLocalTestStorage returns the listener, which needs to be closed, and
@@ -26,3 +29,17 @@
26 closer = listener29 closer = listener
27 return30 return
28}31}
32
33// GetEnvironStorage creates an Environ from the config in state and
34// returns its storage interface.
35func GetEnvironStorage(st *state.State) (storage.Storage, error) {
36 envConfig, err := st.EnvironConfig()
37 if err != nil {
38 return nil, fmt.Errorf("cannot get environment config: %v", err)
39 }
40 env, err := environs.New(envConfig)
41 if err != nil {
42 return nil, fmt.Errorf("cannot access environment: %v", err)
43 }
44 return env.Storage(), nil
45}
2946
=== modified file 'environs/tools/testing/testing.go'
--- environs/tools/testing/testing.go 2014-01-30 06:21:03 +0000
+++ environs/tools/testing/testing.go 2014-01-30 14:23:21 +0000
@@ -4,9 +4,7 @@
4package testing4package testing
55
6import (6import (
7 "crypto/sha256"
8 "fmt"7 "fmt"
9 "io"
10 "io/ioutil"8 "io/ioutil"
11 "os"9 "os"
12 "path"10 "path"
@@ -22,6 +20,7 @@
22 "launchpad.net/juju-core/environs/tools"20 "launchpad.net/juju-core/environs/tools"
23 jc "launchpad.net/juju-core/testing/checkers"21 jc "launchpad.net/juju-core/testing/checkers"
24 coretools "launchpad.net/juju-core/tools"22 coretools "launchpad.net/juju-core/tools"
23 "launchpad.net/juju-core/utils"
25 "launchpad.net/juju-core/utils/set"24 "launchpad.net/juju-core/utils/set"
26 "launchpad.net/juju-core/version"25 "launchpad.net/juju-core/version"
27)26)
@@ -70,13 +69,9 @@
70 if strings.HasPrefix(path, "file://") {69 if strings.HasPrefix(path, "file://") {
71 path = path[len("file://"):]70 path = path[len("file://"):]
72 }71 }
73 f, err := os.Open(path)72 hash, size, err := utils.ReadFileSHA256(path)
74 c.Assert(err, gc.IsNil)73 c.Assert(err, gc.IsNil)
75 defer f.Close()74 return size, hash
76 hash := sha256.New()
77 size, err := io.Copy(hash, f)
78 c.Assert(err, gc.IsNil)
79 return size, fmt.Sprintf("%x", hash.Sum(nil))
80}75}
8176
82// ParseMetadataFromDir loads ToolsMetadata from the specified directory.77// ParseMetadataFromDir loads ToolsMetadata from the specified directory.
8378
=== modified file 'juju/conn.go'
--- juju/conn.go 2014-01-22 22:48:54 +0000
+++ juju/conn.go 2014-01-30 14:23:21 +0000
@@ -4,11 +4,8 @@
4package juju4package juju
55
6import (6import (
7 "crypto/sha256"
8 "encoding/hex"
9 stderrors "errors"7 stderrors "errors"
10 "fmt"8 "fmt"
11 "io"
12 "io/ioutil"9 "io/ioutil"
13 "net/url"10 "net/url"
14 "os"11 "os"
@@ -223,12 +220,10 @@
223 default:220 default:
224 return nil, fmt.Errorf("unknown charm type %T", ch)221 return nil, fmt.Errorf("unknown charm type %T", ch)
225 }222 }
226 h := sha256.New()223 digest, size, err := utils.ReadSHA256(f)
227 size, err := io.Copy(h, f)
228 if err != nil {224 if err != nil {
229 return nil, err225 return nil, err
230 }226 }
231 digest := hex.EncodeToString(h.Sum(nil))
232 if _, err := f.Seek(0, 0); err != nil {227 if _, err := f.Seek(0, 0); err != nil {
233 return nil, err228 return nil, err
234 }229 }
235230
=== modified file 'juju/conn_test.go'
--- juju/conn_test.go 2014-01-24 12:39:48 +0000
+++ juju/conn_test.go 2014-01-30 14:23:21 +0000
@@ -453,7 +453,7 @@
453453
454func (s *DeployLocalSuite) SetUpSuite(c *gc.C) {454func (s *DeployLocalSuite) SetUpSuite(c *gc.C) {
455 s.JujuConnSuite.SetUpSuite(c)455 s.JujuConnSuite.SetUpSuite(c)
456 s.repo = &charm.LocalRepository{Path: coretesting.Charms.Path}456 s.repo = &charm.LocalRepository{Path: coretesting.Charms.Path()}
457 s.oldCacheDir, charm.CacheDir = charm.CacheDir, c.MkDir()457 s.oldCacheDir, charm.CacheDir = charm.CacheDir, c.MkDir()
458}458}
459459
460460
=== modified file 'juju/testing/conn.go'
--- juju/testing/conn.go 2014-01-24 12:39:48 +0000
+++ juju/testing/conn.go 2014-01-30 14:23:21 +0000
@@ -280,7 +280,7 @@
280 ch := testing.Charms.Dir(name)280 ch := testing.Charms.Dir(name)
281 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())281 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
282 curl := charm.MustParseURL("local:quantal/" + ident)282 curl := charm.MustParseURL("local:quantal/" + ident)
283 repo, err := charm.InferRepository(curl, testing.Charms.Path)283 repo, err := charm.InferRepository(curl, testing.Charms.Path())
284 c.Assert(err, gc.IsNil)284 c.Assert(err, gc.IsNil)
285 sch, err := s.Conn.PutCharm(curl, repo, false)285 sch, err := s.Conn.PutCharm(curl, repo, false)
286 c.Assert(err, gc.IsNil)286 c.Assert(err, gc.IsNil)
287287
=== modified file 'juju/testing/repo.go'
--- juju/testing/repo.go 2013-12-19 12:39:49 +0000
+++ juju/testing/repo.go 2014-01-30 14:23:21 +0000
@@ -1,9 +1,6 @@
1package testing1package testing
22
3import (3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "io/ioutil"
7 "net/http"4 "net/http"
8 "os"5 "os"
9 "path/filepath"6 "path/filepath"
@@ -13,6 +10,7 @@
1310
14 "launchpad.net/juju-core/charm"11 "launchpad.net/juju-core/charm"
15 "launchpad.net/juju-core/state"12 "launchpad.net/juju-core/state"
13 "launchpad.net/juju-core/utils"
16)14)
1715
18// RepoSuite acts as a JujuConnSuite but also sets up16// RepoSuite acts as a JujuConnSuite but also sets up
@@ -77,11 +75,8 @@
77 resp, err := http.Get(url.String())75 resp, err := http.Get(url.String())
78 c.Assert(err, gc.IsNil)76 c.Assert(err, gc.IsNil)
79 defer resp.Body.Close()77 defer resp.Body.Close()
80 body, err := ioutil.ReadAll(resp.Body)78 digest, _, err := utils.ReadSHA256(resp.Body)
81 c.Assert(err, gc.IsNil)79 c.Assert(err, gc.IsNil)
82 h := sha256.New()
83 h.Write(body)
84 digest := hex.EncodeToString(h.Sum(nil))
85 c.Assert(ch.BundleSha256(), gc.Equals, digest)80 c.Assert(ch.BundleSha256(), gc.Equals, digest)
86}81}
8782
8883
=== modified file 'state/apiserver/charms.go'
--- state/apiserver/charms.go 2013-12-17 09:49:11 +0000
+++ state/apiserver/charms.go 2014-01-30 14:23:21 +0000
@@ -18,8 +18,7 @@
18 "strings"18 "strings"
1919
20 "launchpad.net/juju-core/charm"20 "launchpad.net/juju-core/charm"
21 "launchpad.net/juju-core/environs"21 envtesting "launchpad.net/juju-core/environs/testing"
22 "launchpad.net/juju-core/environs/storage"
23 "launchpad.net/juju-core/names"22 "launchpad.net/juju-core/names"
24 "launchpad.net/juju-core/state"23 "launchpad.net/juju-core/state"
25 "launchpad.net/juju-core/state/api/params"24 "launchpad.net/juju-core/state/api/params"
@@ -199,7 +198,7 @@
199 }198 }
200199
201 // Now upload to provider storage.200 // Now upload to provider storage.
202 storage, err := getEnvironStorage(h.state)201 storage, err := envtesting.GetEnvironStorage(h.state)
203 if err != nil {202 if err != nil {
204 return fmt.Errorf("cannot access provider storage: %v", err)203 return fmt.Errorf("cannot access provider storage: %v", err)
205 }204 }
@@ -223,17 +222,3 @@
223 }222 }
224 return nil223 return nil
225}224}
226
227// getEnvironStorage creates an Environ from the config in state and
228// returns its storage interface.
229func getEnvironStorage(st *state.State) (storage.Storage, error) {
230 envConfig, err := st.EnvironConfig()
231 if err != nil {
232 return nil, fmt.Errorf("cannot get environment config: %v", err)
233 }
234 env, err := environs.New(envConfig)
235 if err != nil {
236 return nil, fmt.Errorf("cannot access environment: %v", err)
237 }
238 return env.Storage(), nil
239}
240225
=== modified file 'state/apiserver/charms_test.go'
--- state/apiserver/charms_test.go 2013-12-17 09:49:11 +0000
+++ state/apiserver/charms_test.go 2014-01-30 14:23:21 +0000
@@ -4,8 +4,6 @@
4package apiserver_test4package apiserver_test
55
6import (6import (
7 "crypto/sha256"
8 "encoding/hex"
9 "encoding/json"7 "encoding/json"
10 "fmt"8 "fmt"
11 "io"9 "io"
@@ -17,10 +15,10 @@
17 gc "launchpad.net/gocheck"15 gc "launchpad.net/gocheck"
1816
19 "launchpad.net/juju-core/charm"17 "launchpad.net/juju-core/charm"
18 envtesting "launchpad.net/juju-core/environs/testing"
20 jujutesting "launchpad.net/juju-core/juju/testing"19 jujutesting "launchpad.net/juju-core/juju/testing"
21 "launchpad.net/juju-core/state"20 "launchpad.net/juju-core/state"
22 "launchpad.net/juju-core/state/api/params"21 "launchpad.net/juju-core/state/api/params"
23 "launchpad.net/juju-core/state/apiserver"
24 coretesting "launchpad.net/juju-core/testing"22 coretesting "launchpad.net/juju-core/testing"
25 jc "launchpad.net/juju-core/testing/checkers"23 jc "launchpad.net/juju-core/testing/checkers"
26 "launchpad.net/juju-core/utils"24 "launchpad.net/juju-core/utils"
@@ -131,7 +129,7 @@
131 c.Assert(sch.Revision(), gc.Equals, 2)129 c.Assert(sch.Revision(), gc.Equals, 2)
132 c.Assert(sch.IsUploaded(), jc.IsTrue)130 c.Assert(sch.IsUploaded(), jc.IsTrue)
133 // No more checks for these two here, because they131 // No more checks for these two here, because they
134 // are verified in TestUploadRequiresSingleUploadedFile.132 // are verified in TestUploadRespectsLocalRevision.
135 c.Assert(sch.BundleURL(), gc.Not(gc.Equals), "")133 c.Assert(sch.BundleURL(), gc.Not(gc.Equals), "")
136 c.Assert(sch.BundleSha256(), gc.Not(gc.Equals), "")134 c.Assert(sch.BundleSha256(), gc.Not(gc.Equals), "")
137}135}
@@ -164,16 +162,23 @@
164 c.Assert(err, gc.IsNil)162 c.Assert(err, gc.IsNil)
165163
166 // Finally, verify the SHA256 and uploaded URL.164 // Finally, verify the SHA256 and uploaded URL.
167 expectedSHA256, _, err := getSHA256(tempFile)165 expectedSHA256, _, err := utils.ReadSHA256(tempFile)
168 c.Assert(err, gc.IsNil)166 c.Assert(err, gc.IsNil)
169 name := charm.Quote(expectedURL.String())167 name := charm.Quote(expectedURL.String())
170 storage, err := apiserver.GetEnvironStorage(s.State)168 storage, err := envtesting.GetEnvironStorage(s.State)
171 c.Assert(err, gc.IsNil)169 c.Assert(err, gc.IsNil)
172 expectedUploadURL, err := storage.URL(name)170 expectedUploadURL, err := storage.URL(name)
173 c.Assert(err, gc.IsNil)171 c.Assert(err, gc.IsNil)
174172
175 c.Assert(sch.BundleURL().String(), gc.Equals, expectedUploadURL)173 c.Assert(sch.BundleURL().String(), gc.Equals, expectedUploadURL)
176 c.Assert(sch.BundleSha256(), gc.Equals, expectedSHA256)174 c.Assert(sch.BundleSha256(), gc.Equals, expectedSHA256)
175
176 reader, err := storage.Get(name)
177 c.Assert(err, gc.IsNil)
178 defer reader.Close()
179 downloadedSHA256, _, err := utils.ReadSHA256(reader)
180 c.Assert(err, gc.IsNil)
181 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
177}182}
178183
179func (s *charmsSuite) charmsURI(c *gc.C, query string) string {184func (s *charmsSuite) charmsURI(c *gc.C, query string) string {
@@ -230,13 +235,3 @@
230 }235 }
231 c.Check(resp.StatusCode, gc.Equals, expCode)236 c.Check(resp.StatusCode, gc.Equals, expCode)
232}237}
233
234func getSHA256(source io.ReadSeeker) (string, int64, error) {
235 hash := sha256.New()
236 size, err := io.Copy(hash, source)
237 if err != nil {
238 return "", 0, err
239 }
240 digest := hex.EncodeToString(hash.Sum(nil))
241 return digest, size, nil
242}
243238
=== modified file 'state/apiserver/client/client.go'
--- state/apiserver/client/client.go 2014-01-30 02:20:09 +0000
+++ state/apiserver/client/client.go 2014-01-30 14:23:21 +0000
@@ -4,10 +4,7 @@
4package client4package client
55
6import (6import (
7 "crypto/sha256"
8 "encoding/hex"
9 "fmt"7 "fmt"
10 "io"
11 "net/url"8 "net/url"
12 "os"9 "os"
13 "strings"10 "strings"
@@ -29,6 +26,7 @@
29 "launchpad.net/juju-core/state/api/params"26 "launchpad.net/juju-core/state/api/params"
30 "launchpad.net/juju-core/state/apiserver/common"27 "launchpad.net/juju-core/state/apiserver/common"
31 "launchpad.net/juju-core/state/statecmd"28 "launchpad.net/juju-core/state/statecmd"
29 "launchpad.net/juju-core/utils"
32)30)
3331
34var logger = loggo.GetLogger("juju.state.apiserver.client")32var logger = loggo.GetLogger("juju.state.apiserver.client")
@@ -836,9 +834,13 @@
836 return fmt.Errorf("charm URL must include revision")834 return fmt.Errorf("charm URL must include revision")
837 }835 }
838836
839 // First check the charm is not already in state.837 // First, check if a pending or a real charm exists in state.
840 if _, err := c.api.state.Charm(charmURL); err == nil {838 stateCharm, err := c.api.state.PrepareStoreCharmUpload(charmURL)
839 if err == nil && stateCharm.IsUploaded() {
840 // Charm already in state (it was uploaded already).
841 return nil841 return nil
842 } else if err != nil {
843 return err
842 }844 }
843845
844 // Get the charm and its information from the store.846 // Get the charm and its information from the store.
@@ -862,12 +864,10 @@
862 return fmt.Errorf("cannot read downloaded charm: %v", err)864 return fmt.Errorf("cannot read downloaded charm: %v", err)
863 }865 }
864 defer archive.Close()866 defer archive.Close()
865 hash := sha256.New()867 bundleSHA256, size, err := utils.ReadSHA256(archive)
866 size, err := io.Copy(hash, archive)
867 if err != nil {868 if err != nil {
868 return fmt.Errorf("cannot calculate SHA256 hash of charm: %v", err)869 return fmt.Errorf("cannot calculate SHA256 hash of charm: %v", err)
869 }870 }
870 bundleSHA256 := hex.EncodeToString(hash.Sum(nil))
871 if _, err := archive.Seek(0, 0); err != nil {871 if _, err := archive.Seek(0, 0); err != nil {
872 return fmt.Errorf("cannot rewind charm archive: %v", err)872 return fmt.Errorf("cannot rewind charm archive: %v", err)
873 }873 }
@@ -878,12 +878,14 @@
878 return fmt.Errorf("cannot access environment: %v", err)878 return fmt.Errorf("cannot access environment: %v", err)
879 }879 }
880 storage := env.Storage()880 storage := env.Storage()
881 name := charm.Quote(charmURL.String())881 archiveName, err := CharmArchiveName(charmURL.Name, charmURL.Revision)
882 err = storage.Put(name, archive, size)
883 if err != nil {882 if err != nil {
883 return fmt.Errorf("cannot generate charm archive name: %v", err)
884 }
885 if err := storage.Put(archiveName, archive, size); err != nil {
884 return fmt.Errorf("cannot upload charm to provider storage: %v", err)886 return fmt.Errorf("cannot upload charm to provider storage: %v", err)
885 }887 }
886 storageURL, err := storage.URL(name)888 storageURL, err := storage.URL(archiveName)
887 if err != nil {889 if err != nil {
888 return fmt.Errorf("cannot get storage URL for charm: %v", err)890 return fmt.Errorf("cannot get storage URL for charm: %v", err)
889 }891 }
@@ -892,7 +894,29 @@
892 return fmt.Errorf("cannot parse storage URL: %v", err)894 return fmt.Errorf("cannot parse storage URL: %v", err)
893 }895 }
894896
895 // Finally, add the charm to state.897 // Finally, update the charm data in state and mark it as no longer pending.
896 _, err = c.api.state.AddCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256)898 _, err = c.api.state.UpdateUploadedCharm(downloadedCharm, charmURL, bundleURL, bundleSHA256)
899 if err == state.ErrCharmRevisionAlreadyModified ||
900 state.IsCharmAlreadyUploadedError(err) {
901 // This is not an error, it just signifies somebody else
902 // managed to upload and update the charm in state before
903 // us. This means we have to delete what we just uploaded
904 // to storage.
905 if err := storage.Remove(archiveName); err != nil {
906 logger.Errorf("cannot remove duplicated charm from storage: %v", err)
907 }
908 return nil
909 }
897 return err910 return err
898}911}
912
913// CharmArchiveName returns a string that is suitable as a file name
914// in a storage URL. It is constructed from the charm name, revision
915// and a random UUID string.
916func CharmArchiveName(name string, revision int) (string, error) {
917 uuid, err := utils.NewUUID()
918 if err != nil {
919 return "", err
920 }
921 return charm.Quote(fmt.Sprintf("%s-%d-%s", name, revision, uuid)), nil
922}
899923
=== modified file 'state/apiserver/client/client_test.go'
--- state/apiserver/client/client_test.go 2014-01-30 03:30:30 +0000
+++ state/apiserver/client/client_test.go 2014-01-30 14:23:21 +0000
@@ -8,6 +8,8 @@
8 "net/url"8 "net/url"
9 "strconv"9 "strconv"
10 "strings"10 "strings"
11 "sync"
12 "time"
1113
12 gc "launchpad.net/gocheck"14 gc "launchpad.net/gocheck"
1315
@@ -17,8 +19,11 @@
17 "launchpad.net/juju-core/constraints"19 "launchpad.net/juju-core/constraints"
18 "launchpad.net/juju-core/environs/cloudinit"20 "launchpad.net/juju-core/environs/cloudinit"
19 "launchpad.net/juju-core/environs/config"21 "launchpad.net/juju-core/environs/config"
22 "launchpad.net/juju-core/environs/storage"
23 envtesting "launchpad.net/juju-core/environs/testing"
20 "launchpad.net/juju-core/errors"24 "launchpad.net/juju-core/errors"
21 "launchpad.net/juju-core/instance"25 "launchpad.net/juju-core/instance"
26 "launchpad.net/juju-core/provider/dummy"
22 "launchpad.net/juju-core/state"27 "launchpad.net/juju-core/state"
23 "launchpad.net/juju-core/state/api"28 "launchpad.net/juju-core/state/api"
24 "launchpad.net/juju-core/state/api/params"29 "launchpad.net/juju-core/state/api/params"
@@ -26,6 +31,7 @@
26 "launchpad.net/juju-core/state/statecmd"31 "launchpad.net/juju-core/state/statecmd"
27 coretesting "launchpad.net/juju-core/testing"32 coretesting "launchpad.net/juju-core/testing"
28 jc "launchpad.net/juju-core/testing/checkers"33 jc "launchpad.net/juju-core/testing/checkers"
34 "launchpad.net/juju-core/utils"
29 "launchpad.net/juju-core/version"35 "launchpad.net/juju-core/version"
30)36)
3137
@@ -1787,6 +1793,7 @@
1787 bundleURL, err := url.Parse("http://bundles.testing.invalid/" + ident)1793 bundleURL, err := url.Parse("http://bundles.testing.invalid/" + ident)
1788 c.Assert(err, gc.IsNil)1794 c.Assert(err, gc.IsNil)
1789 sch, err := s.State.AddCharm(charmDir, curl, bundleURL, ident+"-sha256")1795 sch, err := s.State.AddCharm(charmDir, curl, bundleURL, ident+"-sha256")
1796 c.Assert(err, gc.IsNil)
17901797
1791 name := charm.Quote(sch.URL().String())1798 name := charm.Quote(sch.URL().String())
1792 storage := s.Conn.Environ.Storage()1799 storage := s.Conn.Environ.Storage()
@@ -1804,20 +1811,127 @@
1804 err = client.AddCharm(curl)1811 err = client.AddCharm(curl)
1805 c.Assert(err, gc.IsNil)1812 c.Assert(err, gc.IsNil)
18061813
1807 // Verify it's in state.1814 // Verify it's in state and it got uploaded.
1808 sch, err = s.State.Charm(curl)1815 sch, err = s.State.Charm(curl)
1809 c.Assert(err, gc.IsNil)1816 c.Assert(err, gc.IsNil)
18101817 s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256())
1811 name = charm.Quote(curl.String())1818}
1812 storageURL, err := storage.URL(name)1819
1813 c.Assert(err, gc.IsNil)1820func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) {
18141821 store, restore := makeMockCharmStore()
1815 c.Assert(sch.BundleURL().String(), gc.Equals, storageURL)1822 defer restore()
1816 // We don't care about the exact value of the hash here, just that1823
1817 // it's set.1824 client := s.APIState.Client()
1818 c.Assert(sch.BundleSha256(), gc.Not(gc.Equals), "")1825 curl, _ := addCharm(c, store, "wordpress")
18191826
1820 // Verify it's added to storage.1827 // Expect storage Put() to be called once for each goroutine
1821 _, err = storage.Get(name)1828 // below.
1822 c.Assert(err, gc.IsNil)1829 ops := make(chan dummy.Operation, 500)
1830 dummy.Listen(ops)
1831 go s.assertPutCalled(c, ops, 10)
1832
1833 // Try adding the same charm concurrently from multiple goroutines
1834 // to test no "duplicate key errors" are reported (see lp bug
1835 // #1067979) and also at the end only one charm document is
1836 // created.
1837
1838 var wg sync.WaitGroup
1839 for i := 0; i < 10; i++ {
1840 wg.Add(1)
1841 go func(index int) {
1842 defer wg.Done()
1843
1844 c.Assert(client.AddCharm(curl), gc.IsNil, gc.Commentf("goroutine %d", index))
1845 sch, err := s.State.Charm(curl)
1846 c.Assert(err, gc.IsNil, gc.Commentf("goroutine %d", index))
1847 c.Assert(sch.URL(), jc.DeepEquals, curl, gc.Commentf("goroutine %d", index))
1848 expectedName := fmt.Sprintf("%s-%d-[0-9a-f-]+", curl.Name, curl.Revision)
1849 c.Assert(getArchiveName(sch.BundleURL()), gc.Matches, expectedName)
1850 }(i)
1851 }
1852 wg.Wait()
1853 close(ops)
1854
1855 // Verify there is only a single uploaded charm remains and it
1856 // contains the correct data.
1857 sch, err := s.State.Charm(curl)
1858 c.Assert(err, gc.IsNil)
1859 storage, err := envtesting.GetEnvironStorage(s.State)
1860 c.Assert(err, gc.IsNil)
1861 uploads, err := storage.List(fmt.Sprintf("%s-%d-", curl.Name, curl.Revision))
1862 c.Assert(err, gc.IsNil)
1863 c.Assert(uploads, gc.HasLen, 1)
1864 c.Assert(getArchiveName(sch.BundleURL()), gc.Equals, uploads[0])
1865 s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256())
1866}
1867
1868func (s *clientSuite) TestAddCharmOverwritesPlaceholders(c *gc.C) {
1869 store, restore := makeMockCharmStore()
1870 defer restore()
1871
1872 client := s.APIState.Client()
1873 curl, _ := addCharm(c, store, "wordpress")
1874
1875 // Add a placeholder with the same charm URL.
1876 err := s.State.AddStoreCharmPlaceholder(curl)
1877 c.Assert(err, gc.IsNil)
1878 _, err = s.State.Charm(curl)
1879 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
1880
1881 // Now try to add the charm, which will convert the placeholder to
1882 // a pending charm.
1883 err = client.AddCharm(curl)
1884 c.Assert(err, gc.IsNil)
1885
1886 // Make sure the document's flags were reset as expected.
1887 sch, err := s.State.Charm(curl)
1888 c.Assert(err, gc.IsNil)
1889 c.Assert(sch.URL(), jc.DeepEquals, curl)
1890 c.Assert(sch.IsPlaceholder(), jc.IsFalse)
1891 c.Assert(sch.IsUploaded(), jc.IsTrue)
1892}
1893
1894func (s *clientSuite) TestCharmArchiveName(c *gc.C) {
1895 for rev, name := range []string{"Foo", "bar", "wordpress", "mysql"} {
1896 archiveFormat := fmt.Sprintf("%s-%d-[0-9a-f-]+", name, rev)
1897 archiveName, err := client.CharmArchiveName(name, rev)
1898 c.Check(err, gc.IsNil)
1899 c.Check(archiveName, gc.Matches, archiveFormat)
1900 }
1901}
1902
1903func (s *clientSuite) assertPutCalled(c *gc.C, ops chan dummy.Operation, numCalls int) {
1904 calls := 0
1905 select {
1906 case op, ok := <-ops:
1907 if !ok {
1908 return
1909 }
1910 if op, ok := op.(dummy.OpPutFile); ok {
1911 calls++
1912 if calls > numCalls {
1913 c.Fatalf("storage Put() called %d times, expected %d times", calls, numCalls)
1914 return
1915 }
1916 nameFormat := "[0-9a-z-]+-[0-9]+-[0-9a-f-]+"
1917 c.Assert(op.FileName, gc.Matches, nameFormat)
1918 }
1919 case <-time.After(coretesting.LongWait):
1920 c.Fatalf("timed out while waiting for a storage Put() calls")
1921 return
1922 }
1923}
1924
1925func (s *clientSuite) assertUploaded(c *gc.C, storage storage.Storage, bundleURL *url.URL, expectedSHA256 string) {
1926 archiveName := getArchiveName(bundleURL)
1927 reader, err := storage.Get(archiveName)
1928 c.Assert(err, gc.IsNil)
1929 defer reader.Close()
1930 downloadedSHA256, _, err := utils.ReadSHA256(reader)
1931 c.Assert(err, gc.IsNil)
1932 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256)
1933}
1934
1935func getArchiveName(bundleURL *url.URL) string {
1936 return strings.TrimPrefix(bundleURL.RequestURI(), "/dummyenv/private/")
1823}1937}
18241938
=== modified file 'state/apiserver/export_test.go'
--- state/apiserver/export_test.go 2014-01-17 19:33:42 +0000
+++ state/apiserver/export_test.go 2014-01-30 14:23:21 +0000
@@ -6,8 +6,7 @@
6import "reflect"6import "reflect"
77
8var (8var (
9 RootType = reflect.TypeOf(&srvRoot{})9 RootType = reflect.TypeOf(&srvRoot{})
10 NewPingTimeout = newPingTimeout10 NewPingTimeout = newPingTimeout
11 GetEnvironStorage = getEnvironStorage11 MaxPingInterval = &maxPingInterval
12 MaxPingInterval = &maxPingInterval
13)12)
1413
=== modified file 'state/state.go'
--- state/state.go 2014-01-28 04:58:43 +0000
+++ state/state.go 2014-01-30 14:23:21 +0000
@@ -445,12 +445,13 @@
445 return coll, id, nil445 return coll, id, nil
446}446}
447447
448// AddCharm adds the ch charm with curl to the state. bundleUrl must be448// AddCharm adds the ch charm with curl to the state. bundleURL must
449// set to a URL where the bundle for ch may be downloaded from.449// be set to a URL where the bundle for ch may be downloaded from. On
450// On success the newly added charm state is returned.450// success the newly added charm state is returned.
451func (st *State) AddCharm(ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string) (stch *Charm, err error) {451func (st *State) AddCharm(ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string) (stch *Charm, err error) {
452 // The charm may already exist in state as a placeholder, so we check for that situation and update the452 // The charm may already exist in state as a placeholder, so we
453 // existing charm record if necessary, otherwise add a new record.453 // check for that situation and update the existing charm record
454 // if necessary, otherwise add a new record.
454 var existing charmDoc455 var existing charmDoc
455 err = st.charms.Find(D{{"_id", curl.String()}, {"placeholder", true}}).One(&existing)456 err = st.charms.Find(D{{"_id", curl.String()}, {"placeholder", true}}).One(&existing)
456 if err == mgo.ErrNotFound {457 if err == mgo.ErrNotFound {
@@ -469,13 +470,19 @@
469 } else if err != nil {470 } else if err != nil {
470 return nil, err471 return nil, err
471 }472 }
472 return st.updateCharmDoc(ch, curl, bundleURL, bundleSha256, StillPlaceholder)473 return st.updateCharmDoc(ch, curl, bundleURL, bundleSha256, stillPlaceholder)
473}474}
474475
475// Charm returns the charm with the given URL.476// Charm returns the charm with the given URL. Charms pending upload
477// to storage and placeholders are never returned.
476func (st *State) Charm(curl *charm.URL) (*Charm, error) {478func (st *State) Charm(curl *charm.URL) (*Charm, error) {
477 cdoc := &charmDoc{}479 cdoc := &charmDoc{}
478 err := st.charms.Find(D{{"_id", curl}, {"pendingupload", false}, {"placeholder", false}}).One(cdoc)480 what := D{
481 {"_id", curl},
482 {"placeholder", D{{"$ne", true}}},
483 {"pendingupload", D{{"$ne", true}}},
484 }
485 err := st.charms.Find(what).One(&cdoc)
479 if err == mgo.ErrNotFound {486 if err == mgo.ErrNotFound {
480 return nil, errors.NotFoundf("charm %q", curl)487 return nil, errors.NotFoundf("charm %q", curl)
481 }488 }
@@ -488,7 +495,8 @@
488 return newCharm(st, cdoc)495 return newCharm(st, cdoc)
489}496}
490497
491// LatestPlaceholderCharm returns the latest charm described by the given URL but which is not yet deployed.498// LatestPlaceholderCharm returns the latest charm described by the
499// given URL but which is not yet deployed.
492func (st *State) LatestPlaceholderCharm(curl *charm.URL) (*Charm, error) {500func (st *State) LatestPlaceholderCharm(curl *charm.URL) (*Charm, error) {
493 noRevURL := curl.WithRevision(-1)501 noRevURL := curl.WithRevision(-1)
494 curlRegex := "^" + regexp.QuoteMeta(noRevURL.String())502 curlRegex := "^" + regexp.QuoteMeta(noRevURL.String())
@@ -510,10 +518,10 @@
510 return newCharm(st, &latest)518 return newCharm(st, &latest)
511}519}
512520
513// PrepareLocalCharmUpload must be called before a charm is uploaded521// PrepareLocalCharmUpload must be called before a local charm is
514// to the provider storage in order to create a charm document in522// uploaded to the provider storage in order to create a charm
515// state. It returns a new Charm with no metadata and a unique523// document in state. It returns the chosen unique charm URL reserved
516// revision number.524// in state for the charm.
517//525//
518// The url's schema must be "local" and it must include a revision.526// The url's schema must be "local" and it must include a revision.
519func (st *State) PrepareLocalCharmUpload(curl *charm.URL) (chosenUrl *charm.URL, err error) {527func (st *State) PrepareLocalCharmUpload(curl *charm.URL) (chosenUrl *charm.URL, err error) {
@@ -575,8 +583,95 @@
575 return chosenUrl, nil583 return chosenUrl, nil
576}584}
577585
578var StillPending = D{{"pendingupload", true}}586// PrepareStoreCharmUpload must be called before a charm store charm
579var StillPlaceholder = D{{"placeholder", true}}587// is uploaded to the provider storage in order to create a charm
588// document in state. If a charm with the same URL is already in
589// state, it will be returned as a *state.Charm (is can be still
590// pending or already uploaded). Otherwise, a new charm document is
591// added in state with just the given charm URL and
592// PendingUpload=true, which is then returned as a *state.Charm.
593//
594// The url's schema must be "cs" and it must include a revision.
595func (st *State) PrepareStoreCharmUpload(curl *charm.URL) (*Charm, error) {
596 // Perform a few sanity checks first.
597 if curl.Schema != "cs" {
598 return nil, fmt.Errorf("expected charm URL with cs schema, got %q", curl)
599 }
600 if curl.Revision < 0 {
601 return nil, fmt.Errorf("expected charm URL with revision, got %q", curl)
602 }
603
604 var (
605 uploadedCharm charmDoc
606 err error
607 )
608 for attempt := 0; attempt < 3; attempt++ {
609 // Find an uploaded or pending charm with the given exact curl.
610 err = st.charms.FindId(curl).One(&uploadedCharm)
611 if err != nil && err != mgo.ErrNotFound {
612 return nil, err
613 } else if err == nil && !uploadedCharm.Placeholder {
614 // The charm exists and it's either uploaded or still
615 // pending, but it's not a placeholder. In any case, we
616 // just return what we got.
617 return newCharm(st, &uploadedCharm)
618 } else if err == mgo.ErrNotFound {
619 // Prepare the pending charm document for insertion.
620 uploadedCharm = charmDoc{
621 URL: curl,
622 PendingUpload: true,
623 Placeholder: false,
624 }
625 }
626
627 var ops []txn.Op
628 if uploadedCharm.Placeholder {
629 // Convert the placeholder to a pending charm, while
630 // asserting the fields updated after an upload have not
631 // changed yet.
632 ops = []txn.Op{{
633 C: st.charms.Name,
634 Id: curl,
635 Assert: D{
636 {"bundlesha256", ""},
637 {"pendingupload", false},
638 {"placeholder", true},
639 },
640 Update: D{{"$set", D{
641 {"pendingupload", true},
642 {"placeholder", false},
643 }}},
644 }}
645 // Update the fields of the document we're returning.
646 uploadedCharm.PendingUpload = true
647 uploadedCharm.Placeholder = false
648 } else {
649 // No charm document with this curl yet, insert it.
650 ops = []txn.Op{{
651 C: st.charms.Name,
652 Id: curl,
653 Assert: txn.DocMissing,
654 Insert: uploadedCharm,
655 }}
656 }
657
658 // Run the transaction, and retry on abort.
659 err = st.runTransaction(ops)
660 if err == txn.ErrAborted {
661 continue
662 } else if err != nil {
663 return nil, err
664 } else if err == nil {
665 return newCharm(st, &uploadedCharm)
666 }
667 }
668 return nil, ErrExcessiveContention
669}
670
671var (
672 stillPending = D{{"pendingupload", true}}
673 stillPlaceholder = D{{"placeholder", true}}
674)
580675
581// AddStoreCharmPlaceholder creates a charm document in state for the given charm URL which676// AddStoreCharmPlaceholder creates a charm document in state for the given charm URL which
582// must reference a charm from the store. The charm document is marked as a placeholder which677// must reference a charm from the store. The charm document is marked as a placeholder which
@@ -653,13 +748,39 @@
653 ops = append(ops, txn.Op{748 ops = append(ops, txn.Op{
654 C: st.charms.Name,749 C: st.charms.Name,
655 Id: doc.URL.String(),750 Id: doc.URL.String(),
656 Assert: StillPlaceholder,751 Assert: stillPlaceholder,
657 Remove: true,752 Remove: true,
658 })753 })
659 }754 }
660 return ops, nil755 return ops, nil
661}756}
662757
758// ErrCharmAlreadyUploaded is returned by UpdateUploadedCharm() when
759// the given charm is already uploaded and marked as not pending in
760// state.
761type ErrCharmAlreadyUploaded struct {
762 curl *charm.URL
763}
764
765func (e *ErrCharmAlreadyUploaded) Error() string {
766 return fmt.Sprintf("charm %q already uploaded", e.curl)
767}
768
769// IsCharmAlreadyUploadedError returns if the given error is
770// ErrCharmAlreadyUploaded.
771func IsCharmAlreadyUploadedError(err interface{}) bool {
772 if err == nil {
773 return false
774 }
775 _, ok := err.(*ErrCharmAlreadyUploaded)
776 return ok
777}
778
779// ErrCharmRevisionAlreadyModified is returned when a pending or
780// placeholder charm is no longer pending or a placeholder, signaling
781// the charm is available in state with its full information.
782var ErrCharmRevisionAlreadyModified = fmt.Errorf("charm revision already modified")
783
663// UpdateUploadedCharm marks the given charm URL as uploaded and784// UpdateUploadedCharm marks the given charm URL as uploaded and
664// updates the rest of its data, returning it as *state.Charm.785// updates the rest of its data, returning it as *state.Charm.
665func (st *State) UpdateUploadedCharm(ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string) (*Charm, error) {786func (st *State) UpdateUploadedCharm(ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string) (*Charm, error) {
@@ -672,14 +793,16 @@
672 return nil, err793 return nil, err
673 }794 }
674 if !doc.PendingUpload {795 if !doc.PendingUpload {
675 return nil, fmt.Errorf("charm %q already uploaded", curl)796 return nil, &ErrCharmAlreadyUploaded{curl}
676 }797 }
677798
678 return st.updateCharmDoc(ch, curl, bundleURL, bundleSha256, StillPending)799 return st.updateCharmDoc(ch, curl, bundleURL, bundleSha256, stillPending)
679}800}
680801
681// updateCharmDoc updates the charm with specified URL with the given data, and resets the placeholder802// updateCharmDoc updates the charm with specified URL with the given
682// and pendingupdate flags.803// data, and resets the placeholder and pendingupdate flags. If the
804// charm is no longer a placeholder or pending (depending on preReq),
805// it returns ErrCharmRevisionAlreadyModified.
683func (st *State) updateCharmDoc(806func (st *State) updateCharmDoc(
684 ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string, preReq interface{}) (*Charm, error) {807 ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSha256 string, preReq interface{}) (*Charm, error) {
685808
@@ -698,7 +821,7 @@
698 Update: updateFields,821 Update: updateFields,
699 }}822 }}
700 if err := st.runTransaction(ops); err != nil {823 if err := st.runTransaction(ops); err != nil {
701 return nil, onAbort(err, fmt.Errorf("charm revision already modified"))824 return nil, onAbort(err, ErrCharmRevisionAlreadyModified)
702 }825 }
703 return st.Charm(curl)826 return st.Charm(curl)
704}827}
705828
=== modified file 'state/state_test.go'
--- state/state_test.go 2014-01-25 11:45:42 +0000
+++ state/state_test.go 2014-01-30 14:23:21 +0000
@@ -129,15 +129,26 @@
129 c.Assert(err2, jc.Satisfies, errors.IsNotFoundError)129 c.Assert(err2, jc.Satisfies, errors.IsNotFoundError)
130}130}
131131
132func (s *StateSuite) dummyCharm(c *gc.C, curlOverride string) (ch charm.Charm, curl *charm.URL, bundleURL *url.URL, bundleSHA256 string) {
133 var err error
134 ch = testing.Charms.Dir("dummy")
135 if curlOverride != "" {
136 curl = charm.MustParseURL(curlOverride)
137 } else {
138 curl = charm.MustParseURL(
139 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
140 )
141 }
142 bundleURL, err = url.Parse("http://bundles.testing.invalid/dummy-1")
143 c.Assert(err, gc.IsNil)
144 bundleSHA256 = "dummy-1-sha256"
145 return ch, curl, bundleURL, bundleSHA256
146}
147
132func (s *StateSuite) TestAddCharm(c *gc.C) {148func (s *StateSuite) TestAddCharm(c *gc.C) {
133 // Check that adding charms from scratch works correctly.149 // Check that adding charms from scratch works correctly.
134 ch := testing.Charms.Dir("dummy")150 ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "")
135 curl := charm.MustParseURL(151 dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
136 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
137 )
138 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
139 c.Assert(err, gc.IsNil)
140 dummy, err := s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256")
141 c.Assert(err, gc.IsNil)152 c.Assert(err, gc.IsNil)
142 c.Assert(dummy.URL().String(), gc.Equals, curl.String())153 c.Assert(dummy.URL().String(), gc.Equals, curl.String())
143154
@@ -161,7 +172,8 @@
161 // Add a deployed charm.172 // Add a deployed charm.
162 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")173 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
163 c.Assert(err, gc.IsNil)174 c.Assert(err, gc.IsNil)
164 dummy, err := s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256")175 bundleSHA256 := "dummy-1-sha256"
176 dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
165 c.Assert(err, gc.IsNil)177 c.Assert(err, gc.IsNil)
166 c.Assert(dummy.URL().String(), gc.Equals, curl.String())178 c.Assert(dummy.URL().String(), gc.Equals, curl.String())
167179
@@ -231,18 +243,78 @@
231 c.Assert(curl.Revision, gc.Equals, 1234)243 c.Assert(curl.Revision, gc.Equals, 1234)
232}244}
233245
246func (s *StateSuite) TestPrepareStoreCharmUpload(c *gc.C) {
247 // First test the sanity checks.
248 sch, err := s.State.PrepareStoreCharmUpload(charm.MustParseURL("cs:quantal/dummy"))
249 c.Assert(err, gc.ErrorMatches, "expected charm URL with revision, got .*")
250 c.Assert(sch, gc.IsNil)
251 sch, err = s.State.PrepareStoreCharmUpload(charm.MustParseURL("local:quantal/dummy"))
252 c.Assert(err, gc.ErrorMatches, "expected charm URL with cs schema, got .*")
253 c.Assert(sch, gc.IsNil)
254
255 // No charm in state, so the call should respect given revision.
256 testCurl := charm.MustParseURL("cs:quantal/missing-123")
257 sch, err = s.State.PrepareStoreCharmUpload(testCurl)
258 c.Assert(err, gc.IsNil)
259 c.Assert(sch.URL(), gc.DeepEquals, testCurl)
260 c.Assert(sch.IsUploaded(), jc.IsFalse)
261
262 s.assertPendingCharmExists(c, sch.URL())
263
264 // Try adding it again with the same revision and ensure we get the same document.
265 schCopy, err := s.State.PrepareStoreCharmUpload(testCurl)
266 c.Assert(err, gc.IsNil)
267 c.Assert(sch, jc.DeepEquals, schCopy)
268
269 // Now add a charm and try again - we should get the same result
270 // as with AddCharm.
271 ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:precise/dummy-2")
272 sch, err = s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
273 c.Assert(err, gc.IsNil)
274 schCopy, err = s.State.PrepareStoreCharmUpload(curl)
275 c.Assert(err, gc.IsNil)
276 c.Assert(sch, jc.DeepEquals, schCopy)
277
278 // Finally, try poking around the state with a placeholder and
279 // bundlesha256 to make sure we do the right thing.
280 curl = curl.WithRevision(999)
281 first := state.TransactionHook{
282 Before: func() {
283 err := s.State.AddStoreCharmPlaceholder(curl)
284 c.Assert(err, gc.IsNil)
285 },
286 After: func() {
287 err := s.charms.RemoveId(curl)
288 c.Assert(err, gc.IsNil)
289 },
290 }
291 second := state.TransactionHook{
292 Before: func() {
293 err := s.State.AddStoreCharmPlaceholder(curl)
294 c.Assert(err, gc.IsNil)
295 },
296 After: func() {
297 err := s.charms.UpdateId(curl, D{{"$set", D{
298 {"bundlesha256", "fake"}},
299 }})
300 c.Assert(err, gc.IsNil)
301 },
302 }
303 defer state.SetTransactionHooks(
304 c, s.State, first, second, first,
305 ).Check()
306
307 _, err = s.State.PrepareStoreCharmUpload(curl)
308 c.Assert(err, gc.Equals, state.ErrExcessiveContention)
309}
310
234func (s *StateSuite) TestUpdateUploadedCharm(c *gc.C) {311func (s *StateSuite) TestUpdateUploadedCharm(c *gc.C) {
235 ch := testing.Charms.Dir("dummy")312 ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "")
236 curl := charm.MustParseURL(313 _, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
237 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()),
238 )
239 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
240 c.Assert(err, gc.IsNil)
241 _, err = s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256")
242 c.Assert(err, gc.IsNil)314 c.Assert(err, gc.IsNil)
243315
244 // First test with already uploaded and a missing charms.316 // Test with already uploaded and a missing charms.
245 sch, err := s.State.UpdateUploadedCharm(ch, curl, bundleURL, "dummy-1-sha256")317 sch, err := s.State.UpdateUploadedCharm(ch, curl, bundleURL, bundleSHA256)
246 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("charm %q already uploaded", curl))318 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("charm %q already uploaded", curl))
247 c.Assert(sch, gc.IsNil)319 c.Assert(sch, gc.IsNil)
248 missingCurl := charm.MustParseURL("local:quantal/missing-1")320 missingCurl := charm.MustParseURL("local:quantal/missing-1")
@@ -250,7 +322,7 @@
250 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)322 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
251 c.Assert(sch, gc.IsNil)323 c.Assert(sch, gc.IsNil)
252324
253 // Now try with an uploaded charm.325 // Test with with an uploaded local charm.
254 _, err = s.State.PrepareLocalCharmUpload(missingCurl)326 _, err = s.State.PrepareLocalCharmUpload(missingCurl)
255 c.Assert(err, gc.IsNil)327 c.Assert(err, gc.IsNil)
256 sch, err = s.State.UpdateUploadedCharm(ch, missingCurl, bundleURL, "missing")328 sch, err = s.State.UpdateUploadedCharm(ch, missingCurl, bundleURL, "missing")
@@ -258,6 +330,7 @@
258 c.Assert(sch.URL(), gc.DeepEquals, missingCurl)330 c.Assert(sch.URL(), gc.DeepEquals, missingCurl)
259 c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision)331 c.Assert(sch.Revision(), gc.Equals, missingCurl.Revision)
260 c.Assert(sch.IsUploaded(), jc.IsTrue)332 c.Assert(sch.IsUploaded(), jc.IsTrue)
333 c.Assert(sch.IsPlaceholder(), jc.IsFalse)
261 c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta())334 c.Assert(sch.Meta(), gc.DeepEquals, ch.Meta())
262 c.Assert(sch.Config(), gc.DeepEquals, ch.Config())335 c.Assert(sch.Config(), gc.DeepEquals, ch.Config())
263 c.Assert(sch.BundleURL(), gc.DeepEquals, bundleURL)336 c.Assert(sch.BundleURL(), gc.DeepEquals, bundleURL)
@@ -285,11 +358,8 @@
285358
286func (s *StateSuite) TestLatestPlaceholderCharm(c *gc.C) {359func (s *StateSuite) TestLatestPlaceholderCharm(c *gc.C) {
287 // Add a deployed charm360 // Add a deployed charm
288 ch := testing.Charms.Dir("dummy")361 ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:quantal/dummy-1")
289 curl := charm.MustParseURL("cs:quantal/dummy-1")362 _, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
290 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
291 c.Assert(err, gc.IsNil)
292 _, err = s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256")
293 c.Assert(err, gc.IsNil)363 c.Assert(err, gc.IsNil)
294364
295 // Deployed charm not found.365 // Deployed charm not found.
@@ -341,11 +411,8 @@
341411
342func (s *StateSuite) assertAddStoreCharmPlaceholder(c *gc.C) (*charm.URL, *charm.URL, *state.Charm) {412func (s *StateSuite) assertAddStoreCharmPlaceholder(c *gc.C) (*charm.URL, *charm.URL, *state.Charm) {
343 // Add a deployed charm413 // Add a deployed charm
344 ch := testing.Charms.Dir("dummy")414 ch, curl, bundleURL, bundleSHA256 := s.dummyCharm(c, "cs:quantal/dummy-1")
345 curl := charm.MustParseURL("cs:quantal/dummy-1")415 dummy, err := s.State.AddCharm(ch, curl, bundleURL, bundleSHA256)
346 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1")
347 c.Assert(err, gc.IsNil)
348 dummy, err := s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256")
349 c.Assert(err, gc.IsNil)416 c.Assert(err, gc.IsNil)
350417
351 // Add a charm placeholder418 // Add a charm placeholder
352419
=== modified file 'testing/charm.go'
--- testing/charm.go 2014-01-09 04:04:54 +0000
+++ testing/charm.go 2014-01-30 14:23:21 +0000
@@ -9,16 +9,11 @@
9 "os"9 "os"
10 "os/exec"10 "os/exec"
11 "path/filepath"11 "path/filepath"
12 "sync"
1213
13 "launchpad.net/juju-core/charm"14 "launchpad.net/juju-core/charm"
14)15)
1516
16func init() {
17 p, err := build.Import("launchpad.net/juju-core/testing", "", build.FindOnly)
18 check(err)
19 Charms = &Repo{Path: filepath.Join(p.Dir, "repo")}
20}
21
22func check(err error) {17func check(err error) {
23 if err != nil {18 if err != nil {
24 panic(err)19 panic(err)
@@ -27,12 +22,27 @@
2722
28// Repo represents a charm repository used for testing.23// Repo represents a charm repository used for testing.
29type Repo struct {24type Repo struct {
30 Path string25 once sync.Once
26 path string
27}
28
29func (r *Repo) Path() string {
30 r.once.Do(r.init)
31 return r.path
32}
33
34// init is called once when r.Path() is called for the first time, and
35// it initializes r.path to the location of the local testing
36// repository.
37func (r *Repo) init() {
38 p, err := build.Import("launchpad.net/juju-core/testing", "", build.FindOnly)
39 check(err)
40 r.path = filepath.Join(p.Dir, "repo")
31}41}
3242
33// Charms represents the specific charm repository stored in this package and43// Charms represents the specific charm repository stored in this package and
34// used by the Juju unit tests. The series name is "quantal".44// used by the Juju unit tests. The series name is "quantal".
35var Charms *Repo45var Charms = &Repo{}
3646
37func clone(dst, src string) string {47func clone(dst, src string) string {
38 check(exec.Command("cp", "-r", src, dst).Run())48 check(exec.Command("cp", "-r", src, dst).Run())
@@ -42,7 +52,7 @@
42// DirPath returns the path to a charm directory with the given name in the52// DirPath returns the path to a charm directory with the given name in the
43// default series53// default series
44func (r *Repo) DirPath(name string) string {54func (r *Repo) DirPath(name string) string {
45 return filepath.Join(r.Path, "quantal", name)55 return filepath.Join(r.Path(), "quantal", name)
46}56}
4757
48// Dir returns the actual charm.Dir named name.58// Dir returns the actual charm.Dir named name.
4959
=== modified file 'utils/trivial.go'
--- utils/trivial.go 2014-01-14 06:49:14 +0000
+++ utils/trivial.go 2014-01-30 14:23:21 +0000
@@ -6,8 +6,11 @@
6import (6import (
7 "bytes"7 "bytes"
8 "compress/gzip"8 "compress/gzip"
9 "crypto/sha256"
10 "encoding/hex"
9 "errors"11 "errors"
10 "fmt"12 "fmt"
13 "io"
11 "io/ioutil"14 "io/ioutil"
12 "os"15 "os"
13 "strings"16 "strings"
@@ -118,3 +121,26 @@
118 }121 }
119 return ioutil.ReadAll(r)122 return ioutil.ReadAll(r)
120}123}
124
125// ReadSHA256 returns the SHA256 hash of the contents read from source
126// (hex encoded) and the size of the source in bytes.
127func ReadSHA256(source io.Reader) (string, int64, error) {
128 hash := sha256.New()
129 size, err := io.Copy(hash, source)
130 if err != nil {
131 return "", 0, err
132 }
133 digest := hex.EncodeToString(hash.Sum(nil))
134 return digest, size, nil
135}
136
137// ReadFileSHA256 is like ReadSHA256 but reads the contents of the
138// given file.
139func ReadFileSHA256(filename string) (string, int64, error) {
140 f, err := os.Open(filename)
141 if err != nil {
142 return "", 0, err
143 }
144 defer f.Close()
145 return ReadSHA256(f)
146}
121147
=== modified file 'utils/trivial_test.go'
--- utils/trivial_test.go 2014-01-17 02:35:28 +0000
+++ utils/trivial_test.go 2014-01-30 14:23:21 +0000
@@ -4,6 +4,10 @@
4package utils_test4package utils_test
55
6import (6import (
7 "bytes"
8 "fmt"
9 "io/ioutil"
10 "path/filepath"
7 "strings"11 "strings"
8 "testing"12 "testing"
913
@@ -36,7 +40,7 @@
36 }40 }
37)41)
3842
39func (utilsSuite) TestCompression(c *gc.C) {43func (*utilsSuite) TestCompression(c *gc.C) {
40 cdata := utils.Gzip(data)44 cdata := utils.Gzip(data)
41 c.Assert(len(cdata) < len(data), gc.Equals, true)45 c.Assert(len(cdata) < len(data), gc.Equals, true)
42 data1, err := utils.Gunzip(cdata)46 data1, err := utils.Gunzip(cdata)
@@ -48,7 +52,7 @@
48 c.Assert(data1, gc.DeepEquals, data)52 c.Assert(data1, gc.DeepEquals, data)
49}53}
5054
51func (utilsSuite) TestCommandString(c *gc.C) {55func (*utilsSuite) TestCommandString(c *gc.C) {
52 type test struct {56 type test struct {
53 args []string57 args []string
54 expected string58 expected string
@@ -71,3 +75,43 @@
71 c.Assert(result, gc.Equals, test.expected)75 c.Assert(result, gc.Equals, test.expected)
72 }76 }
73}77}
78
79func (*utilsSuite) TestReadSHA256AndReadFileSHA256(c *gc.C) {
80 sha256Tests := []struct {
81 content string
82 sha256 string
83 }{{
84 content: "",
85 sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
86 }, {
87 content: "some content",
88 sha256: "290f493c44f5d63d06b374d0a5abd292fae38b92cab2fae5efefe1b0e9347f56",
89 }, {
90 content: "foo",
91 sha256: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
92 }, {
93 content: "Foo",
94 sha256: "1cbec737f863e4922cee63cc2ebbfaafcd1cff8b790d8cfd2e6a5d550b648afa",
95 }, {
96 content: "multi\nline\ntext\nhere",
97 sha256: "c384f11c0294280792a44d9d6abb81f9fd991904cb7eb851a88311b04114231e",
98 }}
99
100 tempDir := c.MkDir()
101 for i, test := range sha256Tests {
102 c.Logf("test %d: %q -> %q", i, test.content, test.sha256)
103 buf := bytes.NewBufferString(test.content)
104 hash, size, err := utils.ReadSHA256(buf)
105 c.Check(err, gc.IsNil)
106 c.Check(hash, gc.Equals, test.sha256)
107 c.Check(int(size), gc.Equals, len(test.content))
108
109 tempFileName := filepath.Join(tempDir, fmt.Sprintf("sha256-%d", i))
110 err = ioutil.WriteFile(tempFileName, []byte(test.content), 0644)
111 c.Check(err, gc.IsNil)
112 fileHash, fileSize, err := utils.ReadFileSHA256(tempFileName)
113 c.Check(err, gc.IsNil)
114 c.Check(fileHash, gc.Equals, hash)
115 c.Check(fileSize, gc.Equals, size)
116 }
117}
74118
=== modified file 'worker/uniter/charm/charm.go'
--- worker/uniter/charm/charm.go 2013-10-03 13:13:25 +0000
+++ worker/uniter/charm/charm.go 2014-01-30 14:23:21 +0000
@@ -4,10 +4,7 @@
4package charm4package charm
55
6import (6import (
7 "crypto/sha256"
8 "encoding/hex"
9 "fmt"7 "fmt"
10 "io"
11 "os"8 "os"
12 "path"9 "path"
1310
@@ -75,11 +72,10 @@
75 }72 }
76 log.Infof("worker/uniter/charm: download complete")73 log.Infof("worker/uniter/charm: download complete")
77 defer st.File.Close()74 defer st.File.Close()
78 hash := sha256.New()75 actualSha256, _, err := utils.ReadSHA256(st.File)
79 if _, err = io.Copy(hash, st.File); err != nil {76 if err != nil {
80 return err77 return err
81 }78 }
82 actualSha256 := hex.EncodeToString(hash.Sum(nil))
83 archiveSha256, err := sch.ArchiveSha256()79 archiveSha256, err := sch.ArchiveSha256()
84 if err != nil {80 if err != nil {
85 return err81 return err
8682
=== modified file 'worker/uniter/uniter_test.go'
--- worker/uniter/uniter_test.go 2014-01-28 03:53:50 +0000
+++ worker/uniter/uniter_test.go 2014-01-30 14:23:21 +0000
@@ -5,10 +5,7 @@
55
6import (6import (
7 "bytes"7 "bytes"
8 "crypto/sha256"
9 "encoding/hex"
10 "fmt"8 "fmt"
11 "io"
12 "io/ioutil"9 "io/ioutil"
13 "net/rpc"10 "net/rpc"
14 "net/url"11 "net/url"
@@ -1199,10 +1196,8 @@
1199 err := s.dir.BundleTo(&buf)1196 err := s.dir.BundleTo(&buf)
1200 c.Assert(err, gc.IsNil)1197 c.Assert(err, gc.IsNil)
1201 body := buf.Bytes()1198 body := buf.Bytes()
1202 hasher := sha256.New()1199 hash, _, err := utils.ReadSHA256(&buf)
1203 _, err = io.Copy(hasher, &buf)
1204 c.Assert(err, gc.IsNil)1200 c.Assert(err, gc.IsNil)
1205 hash := hex.EncodeToString(hasher.Sum(nil))
1206 key := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision())1201 key := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision())
1207 hurl, err := url.Parse(coretesting.Server.URL + key)1202 hurl, err := url.Parse(coretesting.Server.URL + key)
1208 c.Assert(err, gc.IsNil)1203 c.Assert(err, gc.IsNil)

Subscribers

People subscribed via source and target branches

to status/vote changes: