Merge lp:~cmars/juju-core/resolve-cs-series into lp:~go-bot/juju-core/trunk

Proposed by Casey Marshall
Status: Merged
Approved by: Casey Marshall
Approved revision: no longer in the source branch.
Merged at revision: 2559
Proposed branch: lp:~cmars/juju-core/resolve-cs-series
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 1614 lines (+447/-151)
40 files modified
charm/charm.go (+5/-5)
charm/charm_test.go (+4/-4)
charm/url.go (+17/-2)
charm/url_test.go (+20/-1)
cmd/juju/addmachine.go (+2/-1)
cmd/juju/bootstrap_test.go (+33/-20)
cmd/juju/common.go (+39/-0)
cmd/juju/deploy.go (+14/-11)
cmd/juju/environment_test.go (+14/-4)
cmd/juju/publish.go (+1/-1)
cmd/juju/upgradecharm.go (+6/-11)
cmd/juju/upgradejuju_test.go (+10/-6)
cmd/plugins/juju-metadata/imagemetadata.go (+2/-2)
cmd/plugins/juju-metadata/imagemetadata_test.go (+4/-2)
environs/bootstrap/bootstrap_test.go (+16/-9)
environs/bootstrap/synctools.go (+8/-3)
environs/config/config.go (+59/-8)
environs/config/config_test.go (+7/-6)
environs/jujutest/livetests.go (+2/-2)
environs/testing/tools.go (+9/-8)
juju/apiconn_test.go (+1/-1)
juju/testing/conn.go (+15/-2)
juju/testing/instance.go (+2/-1)
provider/azure/environ_test.go (+1/-1)
provider/common/bootstrap.go (+1/-1)
provider/dummy/environs.go (+1/-1)
provider/ec2/ec2.go (+1/-1)
provider/ec2/live_test.go (+1/-1)
provider/ec2/local_test.go (+1/-1)
provider/joyent/environ.go (+1/-1)
provider/openstack/provider.go (+1/-1)
state/api/client.go (+18/-0)
state/api/params/params.go (+16/-0)
state/apiserver/client/client.go (+40/-13)
state/apiserver/client/client_test.go (+60/-5)
testing/environ.go (+3/-1)
worker/provisioner/container_initialisation_test.go (+4/-4)
worker/provisioner/kvm-broker_test.go (+3/-4)
worker/provisioner/lxc-broker_test.go (+3/-4)
worker/provisioner/provisioner_test.go (+2/-2)
To merge this branch: bzr merge lp:~cmars/juju-core/resolve-cs-series
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+212755@code.launchpad.net

Commit message

Resolve series with charm store in juju client.

For the deploy and upgradecharm commands, when a series is not provided, and a
default-series is not set in the environment config, the client will resolve
the series with the charm store, through the state server. If the client is
working with a 1.16 state server, it will resolve the series directly with the
charm store.

Existing environments will have a default-series, so they should have no
change in series selection.

New environments will continue to support the default-series config setting,
but it can be omitted, and is not set by default.

https://codereview.appspot.com/80280043/

Description of the change

Resolve series with charm store in juju client.

For the deploy and upgradecharm commands, when a series is not provided, and a
default-series is not set in the environment config, the client will resolve
the series with the charm store, through the state server. If the client is
working with a 1.16 state server, it will resolve the series directly with the
charm store.

Existing environments will have a default-series, so they should have no
change in series selection.

New environments will continue to support the default-series config setting,
but it can be omitted, and is not set by default.

https://codereview.appspot.com/80280043/

To post a comment you must log in.
Revision history for this message
Casey Marshall (cmars) wrote :

Reviewers: mp+212755_code.launchpad.net,

Message:
Please take a look.

Description:
Resolve series with charm store in juju client.

For the deploy and upgradecharm commands, when a series is not provided,
and a
default-series is not set in the environment config, the client will
resolve
the series with the charm store, through the state server. If the
client is
working with a 1.16 state server, it will resolve the series directly
with the
charm store.

Existing environments will have a default-series, so they should have no
change in series selection.

New environments will continue to support the default-series config
setting,
but it can be omitted, and is not set by default.

https://code.launchpad.net/~cmars/juju-core/resolve-cs-series/+merge/212755

(do not edit description out of merge proposal)

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

Affected files (+189, -32 lines):
   A [revision details]
   M charm/charm.go
   M charm/charm_test.go
   M cmd/juju/deploy.go
   M cmd/juju/publish.go
   M cmd/juju/upgradecharm.go
   M environs/bootstrap/synctools.go
   M environs/config/config.go
   M juju/testing/conn.go
   M state/api/client.go
   M state/apiserver/client/client.go
   M state/apiserver/client/client_test.go

Revision history for this message
Casey Marshall (cmars) wrote :

This is the client-side to support the LTS transition to trusty (LP:
#1290824). It can't land until the charm store update (LP: #1290828) is
deployed into production (expected tomorrow).

In the meantime, I'd much appreciate your feedback & review.

Thanks,
Casey

https://codereview.appspot.com/80280043/

Revision history for this message
John A Meinel (jameinel) wrote :

I think we have some common code that should be factored out, and I
think we need to consider how the code will operate with older Juju
server versions, but otherwise I think this looks pretty good.

https://codereview.appspot.com/80280043/diff/1/cmd/juju/deploy.go
File cmd/juju/deploy.go (right):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/deploy.go#newcode155
cmd/juju/deploy.go:155: repo, err := charm.InferRepository(ref,
ctx.AbsPath(c.RepoPath))
This feels like stuff that definitely needs to be done, but shouldn't be
done in "cmd/juju/deploy.go" which is a "main" sort of function.
Is there a reason this can't be part of more "library" code and tested
as such?

Specifically it feels like it should be a function that takes the
command line parameter (CharmName) a client and a conf, and returns a
fully qualified charm.URL (or error).

Especially given that you essentially implemented it 2 times in this
file. (at least the code you added below this looks a lot like the code
you added above this).

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (left):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go#oldcode208
cmd/juju/upgradecharm.go:208: }
And here, though this one seems to have a new SpecializeCharmRepo step
that I didn't see before.

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (right):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go#newcode148
cmd/juju/upgradecharm.go:148: }
And implemented again here?

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

https://codereview.appspot.com/80280043/diff/1/state/api/client.go#newcode573
state/api/client.go:573:
We need to also add code to handle the fallback case. What do we do if
you are using juju 1.18 to deploy against a 1.16 server that doesn't
have the ResolveCharm API.

I'm guessing the answer is to print an error of:
  "You must supply a series in your charm URL for juju < 1.18".

Either that, or we see that the ResolveCharm isn't available, and just
issue a warning with something like "server version is too old to
support ResolveCharm (juju <1.18) falling back to default series of
"precise""

To be clear, that compatibility code probably shouldn't be here. But
probably could be in the common helper I was outlining earlier.

https://codereview.appspot.com/80280043/

Revision history for this message
Casey Marshall (cmars) wrote :

PTAL. If everything looks ok, I'd still like to run a few more tests
against the deployed charm store update tomorrow, before landing.

Thanks,
Casey

https://codereview.appspot.com/80280043/diff/1/cmd/juju/deploy.go
File cmd/juju/deploy.go (right):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/deploy.go#newcode155
cmd/juju/deploy.go:155: repo, err := charm.InferRepository(ref,
ctx.AbsPath(c.RepoPath))
On 2014/03/26 07:11:17, jameinel wrote:
> This feels like stuff that definitely needs to be done, but shouldn't
be done in
> "cmd/juju/deploy.go" which is a "main" sort of function.
> Is there a reason this can't be part of more "library" code and tested
as such?

> Specifically it feels like it should be a function that takes the
command line
> parameter (CharmName) a client and a conf, and returns a fully
qualified
> charm.URL (or error).

> Especially given that you essentially implemented it 2 times in this
file. (at
> least the code you added below this looks a lot like the code you
added above
> this).

Good call, I've refactored functions to cmd/common.go, shared among
deploy and upgradecharm.

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (left):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go#oldcode208
cmd/juju/upgradecharm.go:208: }
On 2014/03/26 07:11:17, jameinel wrote:
> And here, though this one seems to have a new SpecializeCharmRepo step
that I
> didn't see before.

Done.

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (right):

https://codereview.appspot.com/80280043/diff/1/cmd/juju/upgradecharm.go#newcode148
cmd/juju/upgradecharm.go:148: }
On 2014/03/26 07:11:17, jameinel wrote:
> And implemented again here?

Done.

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

https://codereview.appspot.com/80280043/diff/1/state/api/client.go#newcode573
state/api/client.go:573:
On 2014/03/26 07:11:17, jameinel wrote:
> We need to also add code to handle the fallback case. What do we do if
you are
> using juju 1.18 to deploy against a 1.16 server that doesn't have the
> ResolveCharm API.

> I'm guessing the answer is to print an error of:
> "You must supply a series in your charm URL for juju < 1.18".

> Either that, or we see that the ResolveCharm isn't available, and just
issue a
> warning with something like "server version is too old to support
ResolveCharm
> (juju <1.18) falling back to default series of "precise""

> To be clear, that compatibility code probably shouldn't be here. But
probably
> could be in the common helper I was outlining earlier.

I decided to warn and fall back on "precise" for <1.18 state servers.
These existing environments will almost certainly already have a
default-series set by their bootstrapping.

I had originally intended to resolve the series directly with the charm
store for that case (since 1.16 hits the charm store directly for other
things), but it seems unlikely that such an environment would benefit
from it.

https://codereview.appspot.com/80280043/

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
Casey Marshall (cmars) wrote :

The necessary charm store updates to support this change have been
deployed to store.juju.ubuntu.com.

It is safe to land this branch now, waiting for your review.

https://codereview.appspot.com/80280043/

Revision history for this message
William Reade (fwereade) wrote :
Download full text (3.6 KiB)

A few issues -- don't think any of them should be too much hassle to
resolve, the DefaultSeries return value and the API bulk call thing
should both be pretty mechanical.

https://codereview.appspot.com/80280043/diff/20001/cmd/juju/common.go
File cmd/juju/common.go (right):

https://codereview.appspot.com/80280043/diff/20001/cmd/juju/common.go#newcode82
cmd/juju/common.go:82: logger.Warningf(`ResolveCharm not supported by
the API server, falling back to default series "precise".`)
We should in fact be guaranteed a value given a 1.16 state server, but I
heartily endorse this behaviour all the same.

https://codereview.appspot.com/80280043/diff/20001/environs/config/config.go
File environs/config/config.go (left):

https://codereview.appspot.com/80280043/diff/20001/environs/config/config.go#oldcode411
environs/config/config.go:411: func (c *Config) DefaultSeries() string {
In-band errors squick me out... now that it's possible to have no
default-series, please add a ,ok return value. And I guess return false
if it *is* set, but is set to "".

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go#newcode614
state/api/client.go:614: args := params.CharmURL{URL: ref.String()}
mmmmm I don't quite like that... can we make this a separate type?
params.CharmURL now means something different (although I'm surprised, a
little... I could have sworn that charm url fields marshalled to strings
without any hassle at all [0], and so I'd expect us to be using actual
*charm.URLs, and to have to add a new type for a reference anyway.)

[0] yeah, they should do, they have MarshalJSON and UnmarshalJSON.
Wonder why wedon't make use of it...

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go#newcode616
state/api/client.go:616: if err := c.st.Call("Client", "",
"ResolveCharm", args, result); err != nil {
Bulk calls please, they don't have to be exposed in api.Client but it's
reasonable to expect to be able to resolve a few urls in one go, and we
should accommodate that in the interface.

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

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client.go#newcode959
state/apiserver/client/client.go:959: ref, series, err :=
charm.ParseReference(args.URL)
yeah, calling it URL is jarring -- new type OK?

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

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client_test.go#newcode1977
state/apiserver/client/client_test.go:1977: store.DefaultSeries =
t.defaultSeries
set this outside loop?

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client_test.go#newcode1979
state/apiserver/client/client_test.go:1979: comment :=
gc.Commentf("defaultSeries:%s charmName:%s", t.defaultSeries,
t.charmName)
for i, test := range tests {
     c.Logf("test %d: %#v", i, test)

...is a reasonably compact way to make the t...

Read more...

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
Casey Marshall (cmars) wrote :
Download full text (3.6 KiB)

https://codereview.appspot.com/80280043/diff/20001/environs/config/config.go
File environs/config/config.go (left):

https://codereview.appspot.com/80280043/diff/20001/environs/config/config.go#oldcode411
environs/config/config.go:411: func (c *Config) DefaultSeries() string {
On 2014/03/28 10:43:11, fwereade wrote:
> In-band errors squick me out... now that it's possible to have no
> default-series, please add a ,ok return value. And I guess return
false if it
> *is* set, but is set to "".

Done.

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go#newcode614
state/api/client.go:614: args := params.CharmURL{URL: ref.String()}
On 2014/03/28 10:43:11, fwereade wrote:
> mmmmm I don't quite like that... can we make this a separate type?
> params.CharmURL now means something different (although I'm surprised,
a
> little... I could have sworn that charm url fields marshalled to
strings without
> any hassle at all [0], and so I'd expect us to be using actual
*charm.URLs, and
> to have to add a new type for a reference anyway.)

> [0] yeah, they should do, they have MarshalJSON and UnmarshalJSON.
Wonder why
> wedon't make use of it...

Done.

https://codereview.appspot.com/80280043/diff/20001/state/api/client.go#newcode616
state/api/client.go:616: if err := c.st.Call("Client", "",
"ResolveCharm", args, result); err != nil {
On 2014/03/28 10:43:11, fwereade wrote:
> Bulk calls please, they don't have to be exposed in api.Client but
it's
> reasonable to expect to be able to resolve a few urls in one go, and
we should
> accommodate that in the interface.

Done.

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

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client.go#newcode959
state/apiserver/client/client.go:959: ref, series, err :=
charm.ParseReference(args.URL)
On 2014/03/28 10:43:11, fwereade wrote:
> yeah, calling it URL is jarring -- new type OK?

Done.

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

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client_test.go#newcode1977
state/apiserver/client/client_test.go:1977: store.DefaultSeries =
t.defaultSeries
On 2014/03/28 10:43:11, fwereade wrote:
> set this outside loop?

Varying the default series in the mock charm store among the test
conditions helps ensure the value isn't being hard-coded anywhere in the
apiserver, and it's useful for simulating a failure to resolve.

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client_test.go#newcode1979
state/apiserver/client/client_test.go:1979: comment :=
gc.Commentf("defaultSeries:%s charmName:%s", t.defaultSeries,
t.charmName)
On 2014/03/28 10:43:11, fwereade wrote:
> for i, test := range tests {
> c.Logf("test %d: %#v", i, test)

> ...is a reasonably compact way to make the test logs somewhat useful.

> I do really like calling it "test", not "t", though :).

Done.

https...

Read more...

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
William Reade (fwereade) wrote :
Download full text (4.1 KiB)

Nearly there -- a quibble with the location of the PreferredSeries code,
and a bit of work on the API.

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

https://codereview.appspot.com/80280043/diff/20001/state/apiserver/client/client_test.go#newcode1977
state/apiserver/client/client_test.go:1977: store.DefaultSeries =
t.defaultSeries
On 2014/03/31 19:57:07, cmars wrote:
> On 2014/03/28 10:43:11, fwereade wrote:
> > set this outside loop?

> Varying the default series in the mock charm store among the test
conditions
> helps ensure the value isn't being hard-coded anywhere in the
apiserver, and
> it's useful for simulating a failure to resolve.

Ofc, thanks. I think my eye slipped over the `t.` -- but it's much
harder to miss the `test.`. Thanks :).

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go#newcode129
cmd/juju/addmachine.go:129: series = conf.PreferredSeries()
mm, I rather liked the PreferredSeries(conf) spelling, especially if it
were using an interface with just the DefaultSeries method. This doesn't
feel fundamental to a config -- does that seem sane?

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/common.go
File cmd/juju/common.go (right):

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/common.go#newcode82
cmd/juju/common.go:82: logger.Warningf(`ResolveCharm not supported by
the API server, falling back to default series "precise".`)
PreferredSeries should surely be guaranteed to return non-""?

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go
File environs/config/config.go (right):

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go#newcode230
environs/config/config.go:230: return DefaultSeries
How do we determine the value of this? I'm feeling like it really ought
to actually *be* the latest LTS, rather than just some global var set by
who-knows-who.

https://codereview.appspot.com/80280043/diff/60001/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/80280043/diff/60001/state/api/params/params.go#newcode338
state/api/params/params.go:338: URLs []charm.URL
I think we need an error per-result here, don't we?

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

https://codereview.appspot.com/80280043/diff/60001/state/apiserver/client/client.go#newcode963
state/apiserver/client/client.go:963: return
params.ResolveCharmResults{}, err
sorry for the hassle, but we should always return one result per
request, and that result should contain either the answer or the error.

Client is a dog's dinner in this regard, and can only gradually and
incrementally improve, but the internal APIs are generally written as I
want them, and should be used as a model. The core idea is that bulk
APIs can be used for single calls, but single APIs can't be used in
bulk; as humans we are bad at predicting the future, and I'd r...

Read more...

Revision history for this message
Casey Marshall (cmars) wrote :

Thanks for reviewing. Couple of questions:

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go#newcode129
cmd/juju/addmachine.go:129: series = conf.PreferredSeries()
On 2014/04/01 07:10:07, fwereade wrote:
> mm, I rather liked the PreferredSeries(conf) spelling, especially if
it were
> using an interface with just the DefaultSeries method. This doesn't
feel
> fundamental to a config -- does that seem sane?

Will do. Should this live in environs/config or a different package?

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go
File environs/config/config.go (right):

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go#newcode230
environs/config/config.go:230: return DefaultSeries
On 2014/04/01 07:10:07, fwereade wrote:
> How do we determine the value of this? I'm feeling like it really
ought to
> actually *be* the latest LTS, rather than just some global var set by
> who-knows-who.

I can try to get this from 'distro-info --lts'. If that fails to exec,
I'll use a hard-coded fallback (named as such). What do you think?

https://codereview.appspot.com/80280043/

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
Casey Marshall (cmars) wrote :
Download full text (4.7 KiB)

I've addressed most of the feedback, but there is a problem lurking.
When config.LatestLtsSeries() == "trusty", some of the tests are failing
due to tools availability. I was able to resolve some of them, but not
all. Could really use some advice.

To reproduce the failures, this will simulate a post-trusty release
scenario in the proposed branch:

go test -ldflags "-X
launchpad.net/juju-core/environs/config.latestLtsSeries trusty" ./...

These are the failures:

FAIL: bootstrap_test.go:506:
BootstrapSuite.TestAutoUploadAfterFailedSync
FAIL: bootstrap_test.go:555:
BootstrapSuite.TestMissingToolsUploadFailedError
FAIL: upgradejuju_test.go:301: UpgradeJujuSuite.TestUpgradeJuju
FAIL: imagemetadata_test.go:141:
ImageMetadataSuite.TestImageMetadataFilesDefaultSeries

Everything passes when the latest LTS is precise.

Please advise,
Casey

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/addmachine.go#newcode129
cmd/juju/addmachine.go:129: series = conf.PreferredSeries()
On 2014/04/01 07:10:07, fwereade wrote:
> mm, I rather liked the PreferredSeries(conf) spelling, especially if
it were
> using an interface with just the DefaultSeries method. This doesn't
feel
> fundamental to a config -- does that seem sane?

Done.

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/common.go
File cmd/juju/common.go (right):

https://codereview.appspot.com/80280043/diff/60001/cmd/juju/common.go#newcode82
cmd/juju/common.go:82: logger.Warningf(`ResolveCharm not supported by
the API server, falling back to default series "precise".`)
On 2014/04/01 07:10:07, fwereade wrote:
> PreferredSeries should surely be guaranteed to return non-""?

Done.

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go
File environs/config/config.go (right):

https://codereview.appspot.com/80280043/diff/60001/environs/config/config.go#newcode230
environs/config/config.go:230: return DefaultSeries
On 2014/04/01 15:05:12, cmars wrote:
> On 2014/04/01 07:10:07, fwereade wrote:
> > How do we determine the value of this? I'm feeling like it really
ought to
> > actually *be* the latest LTS, rather than just some global var set
by
> > who-knows-who.

> I can try to get this from 'distro-info --lts'. If that fails to exec,
I'll use
> a hard-coded fallback (named as such). What do you think?

Done.

https://codereview.appspot.com/80280043/diff/60001/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/80280043/diff/60001/state/api/params/params.go#newcode338
state/api/params/params.go:338: URLs []charm.URL
On 2014/04/01 07:10:07, fwereade wrote:
> I think we need an error per-result here, don't we?

Done.

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

https://codereview.appspot.com/80280043/diff/60001/state/apiserver/client/client.go#newcode963
state/apiserver/client/client.go:963: return
params.ResolveCharmResults{}, err
On 2014/04/01 07:10:07, fwereade wrote:
> sorry for the hassle, but we should always return ...

Read more...

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
Casey Marshall (cmars) wrote :

Tests affected by the LTS change wrt uploading tools are now all passing
when the latest LTS series is "trusty". To run the tests as if trusty
were already released, run with:

go test -ldflags "-X
launchpad.net/juju-core/environs/config.latestLtsSeries trusty"

(This var is normally initialized when not set, from `distro-info
--lts`)

Added wallyworld. I could use a review from a tools distribution
perspective, as this is an area I am only superficially familiar with.
They were affected by the LTS series change so I've tried to make fixes
where test cases and setup had "precise" hardcoded, did not anticipate
having to upload tools other than a DefaultSeries, etc.

Thanks!
-Casey

https://codereview.appspot.com/80280043/

Revision history for this message
Casey Marshall (cmars) wrote :
Revision history for this message
Ian Booth (wallyworld) wrote :

I think the tools changes are ok. What I like is that when we start
running the tests on a trusty host, the test set up will, where
relevant, continue to correctly use a series matching the host of which
the tests are running, hence replicating the current test behaviour.
Having said that, I still fear our test coverage of the various
permutations in this area is not complete, but that's outside the scope
of this branch. There's still the possibility of perhaps a subtle bug
being introduced with regard to series handling, as has been seen before
when this sort of this was tweaked, but it's hard to know for sure.

https://codereview.appspot.com/80280043/

Revision history for this message
William Reade (fwereade) wrote :
Download full text (4.5 KiB)

Looks good, would appreciate at least a short chat before landing -- in
particular, I'm not quite clear on the forces that lead us to use
FakeDefaultSeries sometimes, and LatestLts at others.

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/bootstrap_test.go
File cmd/juju/bootstrap_test.go (right):

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/bootstrap_test.go#newcode123
cmd/juju/bootstrap_test.go:123: useVersion :=
strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1)
not quite sure that config is the right package for these -- but I'm not
sure I can think of a better one. Unless you want to create a tiny,
hyper-focused, series package somewhere? I'm sure it'll grow excitingly
as we deal with other OSs...

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (right):

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/upgradecharm.go#newcode209
cmd/juju/upgradecharm.go:209: repo, err :=
charm.InferRepository(newURL.Reference, c.RepoPath)
We still need ctx.AbsPath, I think. Would you change the relevant test
to use a path relative to the working dir so we check this properly
please?

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go
File environs/config/config.go (right):

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go#newcode106
environs/config/config.go:106:
yeah, this feels a bit tacked-on here. Let's give it its own package if
we don't think of anywhere better.

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go#newcode456
environs/config/config.go:456: series := s.(string)
this is guaranteed to be a string if it's got this far, right?

https://codereview.appspot.com/80280043/diff/120001/provider/common/bootstrap.go
File provider/common/bootstrap.go (right):

https://codereview.appspot.com/80280043/diff/120001/provider/common/bootstrap.go#newcode45
provider/common/bootstrap.go:45: selectedTools, err :=
EnsureBootstrapTools(ctx, env, config.PreferredSeries(env.Config()),
cons.Arch)
quibble quibble, we should probably allow bootstrap on any series we can
find tools for. Out of scope today, but maybe worth a bug?

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/ec2.go
File provider/ec2/ec2.go (right):

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/ec2.go#newcode367
provider/ec2/ec2.go:367: Series:
config.PreferredSeries(e.ecfg()),
this seems a bit weird, but so did the original. let it stand.

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/live_test.go
File provider/ec2/live_test.go (right):

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/live_test.go#newcode104
provider/ec2/live_test.go:104: Series: coretesting.FakeDefaultSeries,
wondering if this should be LatestLts? I presume you have a good reason
it isn't, but it's not immediately clear ;)

https://codereview.appspot.com/80280043/diff/120001/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/80280043/diff/120001/state/api/client.go#newcode613
state/api/client.go:613: func (c *Client) Resolv...

Read more...

Revision history for this message
Casey Marshall (cmars) wrote :
Download full text (5.1 KiB)

PTAL

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/bootstrap_test.go
File cmd/juju/bootstrap_test.go (right):

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/bootstrap_test.go#newcode123
cmd/juju/bootstrap_test.go:123: useVersion :=
strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1)
On 2014/04/03 13:34:15, fwereade wrote:
> not quite sure that config is the right package for these -- but I'm
not sure I
> can think of a better one. Unless you want to create a tiny,
hyper-focused,
> series package somewhere? I'm sure it'll grow excitingly as we deal
with other
> OSs...

LP: #1301999

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/upgradecharm.go
File cmd/juju/upgradecharm.go (right):

https://codereview.appspot.com/80280043/diff/120001/cmd/juju/upgradecharm.go#newcode209
cmd/juju/upgradecharm.go:209: repo, err :=
charm.InferRepository(newURL.Reference, c.RepoPath)
On 2014/04/03 13:34:15, fwereade wrote:
> We still need ctx.AbsPath, I think. Would you change the relevant test
to use a
> path relative to the working dir so we check this properly please?

Ah, must have botched this in a prior merge. Restored it.

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go
File environs/config/config.go (right):

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go#newcode106
environs/config/config.go:106:
On 2014/04/03 13:34:15, fwereade wrote:
> yeah, this feels a bit tacked-on here. Let's give it its own package
if we don't
> think of anywhere better.

LP: #1301999

https://codereview.appspot.com/80280043/diff/120001/environs/config/config.go#newcode456
environs/config/config.go:456: series := s.(string)
On 2014/04/03 13:34:15, fwereade wrote:
> this is guaranteed to be a string if it's got this far, right?

Non-string values that make their way in here will be ignored with a
warning.

https://codereview.appspot.com/80280043/diff/120001/provider/common/bootstrap.go
File provider/common/bootstrap.go (right):

https://codereview.appspot.com/80280043/diff/120001/provider/common/bootstrap.go#newcode45
provider/common/bootstrap.go:45: selectedTools, err :=
EnsureBootstrapTools(ctx, env, config.PreferredSeries(env.Config()),
cons.Arch)
On 2014/04/03 13:34:15, fwereade wrote:
> quibble quibble, we should probably allow bootstrap on any series we
can find
> tools for. Out of scope today, but maybe worth a bug?

LP: #1302005

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/live_test.go
File provider/ec2/live_test.go (right):

https://codereview.appspot.com/80280043/diff/120001/provider/ec2/live_test.go#newcode104
provider/ec2/live_test.go:104: Series: coretesting.FakeDefaultSeries,
On 2014/04/03 13:34:15, fwereade wrote:
> wondering if this should be LatestLts? I presume you have a good
reason it
> isn't, but it's not immediately clear ;)

It's to separate what is used for default-series: in tests from the
preferred series selection. This allows us to test scenarios against
different configured series vs. what will be preferred.

Most of what's affected atm is the tools setup & bootstrapping for some
tests. There are still some areas ...

Read more...

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

LGTM with one trivial. Thanks for this, it's a monster diff -- let's
throw it at CI and see what we've missed ;).

https://codereview.appspot.com/80280043/diff/140001/charm/url_test.go
File charm/url_test.go (right):

https://codereview.appspot.com/80280043/diff/140001/charm/url_test.go#newcode292
charm/url_test.go:292: c.Check(parsed, gc.DeepEquals, ref)
add one for unmarshalling gibberish, just for safety's sake

https://codereview.appspot.com/80280043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charm/charm.go'
2--- charm/charm.go 2014-03-17 18:46:17 +0000
3+++ charm/charm.go 2014-04-03 17:37:27 +0000
4@@ -30,10 +30,10 @@
5 return ReadBundle(path)
6 }
7
8-// InferRepository returns a charm repository inferred from
9-// the provided URL. Local URLs will use the provided path.
10-func InferRepository(curl *URL, localRepoPath string) (repo Repository, err error) {
11- switch curl.Schema {
12+// InferRepository returns a charm repository inferred from the provided charm
13+// reference. Local references will use the provided path.
14+func InferRepository(ref Reference, localRepoPath string) (repo Repository, err error) {
15+ switch ref.Schema {
16 case "cs":
17 repo = Store
18 case "local":
19@@ -42,7 +42,7 @@
20 }
21 repo = &LocalRepository{Path: localRepoPath}
22 default:
23- return nil, fmt.Errorf("unknown schema for charm URL %q", curl)
24+ return nil, fmt.Errorf("unknown schema for charm reference %q", ref)
25 }
26 return
27 }
28
29=== modified file 'charm/charm_test.go'
30--- charm/charm_test.go 2014-03-18 05:08:25 +0000
31+++ charm/charm_test.go 2014-04-03 17:37:27 +0000
32@@ -48,7 +48,7 @@
33 c.Logf("test %d", i)
34 curl, err := charm.InferURL(t.url, "precise")
35 c.Assert(err, gc.IsNil)
36- repo, err := charm.InferRepository(curl, "/some/path")
37+ repo, err := charm.InferRepository(curl.Reference, "/some/path")
38 c.Assert(err, gc.IsNil)
39 switch repo := repo.(type) {
40 case *charm.LocalRepository:
41@@ -59,11 +59,11 @@
42 }
43 curl, err := charm.InferURL("local:whatever", "precise")
44 c.Assert(err, gc.IsNil)
45- _, err = charm.InferRepository(curl, "")
46+ _, err = charm.InferRepository(curl.Reference, "")
47 c.Assert(err, gc.ErrorMatches, "path to local repository not specified")
48 curl.Schema = "foo"
49- _, err = charm.InferRepository(curl, "")
50- c.Assert(err, gc.ErrorMatches, "unknown schema for charm URL.*")
51+ _, err = charm.InferRepository(curl.Reference, "")
52+ c.Assert(err, gc.ErrorMatches, "unknown schema for charm reference.*")
53 }
54
55 func checkDummy(c *gc.C, f charm.Charm, path string) {
56
57=== modified file 'charm/url.go'
58--- charm/url.go 2014-03-24 15:35:41 +0000
59+++ charm/url.go 2014-04-03 17:37:27 +0000
60@@ -284,8 +284,6 @@
61 return nil
62 }
63
64-var jsonNull = []byte("null")
65-
66 func (u *URL) MarshalJSON() ([]byte, error) {
67 if u == nil {
68 panic("cannot marshal nil *charm.URL")
69@@ -306,6 +304,23 @@
70 return nil
71 }
72
73+func (r *Reference) MarshalJSON() ([]byte, error) {
74+ return json.Marshal(r.String())
75+}
76+
77+func (r *Reference) UnmarshalJSON(b []byte) error {
78+ var s string
79+ if err := json.Unmarshal(b, &s); err != nil {
80+ return err
81+ }
82+ ref, _, err := ParseReference(s)
83+ if err != nil {
84+ return err
85+ }
86+ *r = ref
87+ return nil
88+}
89+
90 // Quote translates a charm url string into one which can be safely used
91 // in a file path. ASCII letters, ASCII digits, dot and dash stay the
92 // same; other characters are translated to their hex representation
93
94=== modified file 'charm/url_test.go'
95--- charm/url_test.go 2014-03-24 15:35:41 +0000
96+++ charm/url_test.go 2014-04-03 17:37:27 +0000
97@@ -258,7 +258,7 @@
98 Unmarshal: json.Unmarshal,
99 }}
100
101-func (s *URLSuite) TestCodecs(c *gc.C) {
102+func (s *URLSuite) TestURLCodecs(c *gc.C) {
103 for i, codec := range codecs {
104 c.Logf("codec %d", i)
105 type doc struct {
106@@ -279,6 +279,25 @@
107 }
108 }
109
110+func (s *URLSuite) TestReferenceJSON(c *gc.C) {
111+ ref, _, err := charm.ParseReference("cs:series/name")
112+ c.Assert(err, gc.IsNil)
113+ data, err := json.Marshal(&ref)
114+ c.Assert(err, gc.IsNil)
115+ c.Check(string(data), gc.Equals, `"cs:name"`)
116+
117+ var parsed charm.Reference
118+ err = json.Unmarshal(data, &parsed)
119+ c.Assert(err, gc.IsNil)
120+ c.Check(parsed, gc.DeepEquals, ref)
121+
122+ // unmarshalling json gibberish and invalid charm reference strings
123+ for _, value := range []string{":{", `"cs:{}+<"`, `"cs:~_~/f00^^&^/baaaar$%-?"`} {
124+ err = json.Unmarshal([]byte(value), &parsed)
125+ c.Check(err, gc.NotNil)
126+ }
127+}
128+
129 type QuoteSuite struct{}
130
131 var _ = gc.Suite(&QuoteSuite{})
132
133=== modified file 'cmd/juju/addmachine.go'
134--- cmd/juju/addmachine.go 2014-04-01 04:53:43 +0000
135+++ cmd/juju/addmachine.go 2014-04-03 17:37:27 +0000
136@@ -12,6 +12,7 @@
137 "launchpad.net/juju-core/cmd"
138 "launchpad.net/juju-core/cmd/envcmd"
139 "launchpad.net/juju-core/constraints"
140+ "launchpad.net/juju-core/environs/config"
141 "launchpad.net/juju-core/environs/manual"
142 "launchpad.net/juju-core/instance"
143 "launchpad.net/juju-core/juju"
144@@ -131,7 +132,7 @@
145 if err != nil {
146 return "", err
147 }
148- series = conf.DefaultSeries()
149+ series = config.PreferredSeries(conf)
150 }
151 template := state.MachineTemplate{
152 Series: series,
153
154=== modified file 'cmd/juju/bootstrap_test.go'
155--- cmd/juju/bootstrap_test.go 2014-04-01 03:11:16 +0000
156+++ cmd/juju/bootstrap_test.go 2014-04-03 17:37:27 +0000
157@@ -15,6 +15,7 @@
158 "launchpad.net/juju-core/cmd"
159 "launchpad.net/juju-core/constraints"
160 "launchpad.net/juju-core/environs"
161+ "launchpad.net/juju-core/environs/config"
162 "launchpad.net/juju-core/environs/configstore"
163 "launchpad.net/juju-core/environs/filestorage"
164 "launchpad.net/juju-core/environs/imagemetadata"
165@@ -119,7 +120,8 @@
166 func (s *BootstrapSuite) runAllowRetriesTest(c *gc.C, test bootstrapRetryTest) {
167 toolsVersions := envtesting.VAll
168 if test.version != "" {
169- testVersion := version.MustParseBinary(test.version)
170+ useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1)
171+ testVersion := version.MustParseBinary(useVersion)
172 s.PatchValue(&version.Current, testVersion)
173 if test.addVersionToSource {
174 toolsVersions = append([]version.Binary{}, toolsVersions...)
175@@ -201,8 +203,9 @@
176 defer fake.Restore()
177
178 if test.version != "" {
179+ useVersion := strings.Replace(test.version, "%LTS%", config.LatestLtsSeries(), 1)
180 origVersion := version.Current
181- version.Current = version.MustParseBinary(test.version)
182+ version.Current = version.MustParseBinary(useVersion)
183 defer func() { version.Current = origVersion }()
184 }
185
186@@ -217,7 +220,7 @@
187 uploadCount := len(test.uploads)
188 if uploadCount == 0 {
189 usefulVersion := version.Current
190- usefulVersion.Series = env.Config().DefaultSeries()
191+ usefulVersion.Series = config.PreferredSeries(env.Config())
192 envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion)
193 }
194
195@@ -245,6 +248,7 @@
196 urls := list.URLs()
197 c.Check(urls, gc.HasLen, len(test.uploads))
198 for _, v := range test.uploads {
199+ v := strings.Replace(v, "%LTS%", config.LatestLtsSeries(), 1)
200 c.Logf("seeking: " + v)
201 vers := version.MustParseBinary(v)
202 _, found := urls[vers]
203@@ -299,10 +303,9 @@
204 args: []string{"--series", "fine"},
205 err: `--series requires --upload-tools`,
206 }, {
207- info: "bad environment",
208- version: "1.2.3-precise-amd64",
209- args: []string{"-e", "brokenenv"},
210- err: `dummy.Bootstrap is broken`,
211+ info: "bad environment",
212+ args: []string{"-e", "brokenenv"},
213+ err: `dummy.Bootstrap is broken`,
214 }, {
215 info: "constraints",
216 args: []string{"--constraints", "mem=4G cpu-cores=4"},
217@@ -312,9 +315,9 @@
218 version: "1.2.3-saucy-amd64",
219 args: []string{"--upload-tools"},
220 uploads: []string{
221- "1.2.3.1-saucy-amd64", // from version.Current
222- "1.2.3.1-raring-amd64", // from env.Config().DefaultSeries()
223- "1.2.3.1-precise-amd64", // from environs/config.DefaultSeries
224+ "1.2.3.1-saucy-amd64", // from version.Current
225+ "1.2.3.1-raring-amd64", // from env.Config().DefaultSeries()
226+ "1.2.3.1-%LTS%-amd64", // from environs/config.DefaultSeries
227 },
228 }, {
229 info: "--upload-tools uses arch from constraint if it matches current version",
230@@ -322,18 +325,18 @@
231 hostArch: "ppc64",
232 args: []string{"--upload-tools", "--constraints", "arch=ppc64"},
233 uploads: []string{
234- "1.3.3.1-saucy-ppc64", // from version.Current
235- "1.3.3.1-raring-ppc64", // from env.Config().DefaultSeries()
236- "1.3.3.1-precise-ppc64", // from environs/config.DefaultSeries
237+ "1.3.3.1-saucy-ppc64", // from version.Current
238+ "1.3.3.1-raring-ppc64", // from env.Config().DefaultSeries()
239+ "1.3.3.1-%LTS%-ppc64", // from environs/config.DefaultSeries
240 },
241 constraints: constraints.MustParse("arch=ppc64"),
242 }, {
243 info: "--upload-tools only uploads each file once",
244- version: "1.2.3-precise-amd64",
245+ version: "1.2.3-%LTS%-amd64",
246 args: []string{"--upload-tools"},
247 uploads: []string{
248 "1.2.3.1-raring-amd64",
249- "1.2.3.1-precise-amd64",
250+ "1.2.3.1-%LTS%-amd64",
251 },
252 }, {
253 info: "--upload-tools rejects invalid series",
254@@ -357,7 +360,7 @@
255 args: []string{"--upload-tools"},
256 uploads: []string{
257 "1.2.3.5-raring-amd64",
258- "1.2.3.5-precise-amd64",
259+ "1.2.3.5-%LTS%-amd64",
260 },
261 }}
262
263@@ -365,7 +368,7 @@
264 env, fake := makeEmptyFakeHome(c)
265 defer fake.Restore()
266 defaultSeriesVersion := version.Current
267- defaultSeriesVersion.Series = env.Config().DefaultSeries()
268+ defaultSeriesVersion.Series = config.PreferredSeries(env.Config())
269
270 ctx := coretesting.Context(c)
271 code := cmd.Main(&BootstrapCommand{}, ctx, nil)
272@@ -383,7 +386,7 @@
273 env, fake := makeEmptyFakeHome(c)
274 defer fake.Restore()
275 defaultSeriesVersion := version.Current
276- defaultSeriesVersion.Series = env.Config().DefaultSeries()
277+ defaultSeriesVersion.Series = config.PreferredSeries(env.Config())
278
279 store, err := configstore.Default()
280 c.Assert(err, gc.IsNil)
281@@ -515,7 +518,13 @@
282 c.Assert(err, gc.IsNil)
283 c.Logf("found: " + list.String())
284 urls := list.URLs()
285- c.Assert(urls, gc.HasLen, 2)
286+ expectedUrlCount := 2
287+
288+ // There will be distinct tools for each of these if they are different
289+ if config.LatestLtsSeries() != coretesting.FakeDefaultSeries {
290+ expectedUrlCount++
291+ }
292+ c.Assert(urls, gc.HasLen, expectedUrlCount)
293 expectedVers := []version.Binary{
294 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", otherSeries, version.Current.Arch)),
295 version.MustParseBinary(fmt.Sprintf("1.7.3.1-%s-%s", version.Current.Series, version.Current.Arch)),
296@@ -556,7 +565,11 @@
297 code := cmd.Main(&BootstrapCommand{}, context, nil)
298 c.Assert(code, gc.Equals, 1)
299 errText := context.Stderr.(*bytes.Buffer).String()
300- expectedErrText := "uploading tools for series \\[precise raring\\]\n"
301+ expectedErrText := "uploading tools for series \\[precise "
302+ if config.LatestLtsSeries() != coretesting.FakeDefaultSeries {
303+ expectedErrText += config.LatestLtsSeries() + " "
304+ }
305+ expectedErrText += "raring\\]\n"
306 expectedErrText += "error: cannot upload bootstrap tools: an error\n"
307 c.Assert(errText, gc.Matches, expectedErrText)
308 }
309
310=== modified file 'cmd/juju/common.go'
311--- cmd/juju/common.go 2014-03-26 04:36:17 +0000
312+++ cmd/juju/common.go 2014-04-03 17:37:27 +0000
313@@ -4,10 +4,13 @@
314 package main
315
316 import (
317+ "launchpad.net/juju-core/charm"
318 "launchpad.net/juju-core/cmd"
319 "launchpad.net/juju-core/environs"
320+ "launchpad.net/juju-core/environs/config"
321 "launchpad.net/juju-core/environs/configstore"
322 "launchpad.net/juju-core/errors"
323+ "launchpad.net/juju-core/state/api"
324 )
325
326 // destroyPreparedEnviron destroys the environment and logs an error if it fails.
327@@ -45,3 +48,39 @@
328 }
329 return environ, cleanup, nil
330 }
331+
332+// resolveCharmURL returns a resolved charm URL, given a charm location string.
333+// If the series is not resolved, the environment default-series is used, or if
334+// not set, the series is resolved with the state server.
335+func resolveCharmURL(url string, client *api.Client, conf *config.Config) (*charm.URL, error) {
336+ ref, series, err := charm.ParseReference(url)
337+ if err != nil {
338+ return nil, err
339+ }
340+ // If series is not set, use configured default series
341+ if series == "" {
342+ if defaultSeries, ok := conf.DefaultSeries(); ok {
343+ series = defaultSeries
344+ }
345+ }
346+ // Otherwise, look up the best supported series for this charm
347+ if series == "" {
348+ return client.ResolveCharm(ref)
349+ }
350+ return &charm.URL{Reference: ref, Series: series}, nil
351+}
352+
353+// resolveCharmURL1dot16 returns a resolved charm URL for older state servers
354+// that do not support ResolveCharm. The default series "precise" is
355+// appropriate for these environments.
356+func resolveCharmURL1dot16(url string, conf *config.Config) (*charm.URL, error) {
357+ ref, series, err := charm.ParseReference(url)
358+ if err != nil {
359+ return nil, err
360+ }
361+
362+ if series == "" {
363+ series = config.PreferredSeries(conf)
364+ }
365+ return &charm.URL{Reference: ref, Series: series}, err
366+}
367
368=== modified file 'cmd/juju/deploy.go'
369--- cmd/juju/deploy.go 2014-04-01 14:55:36 +0000
370+++ cmd/juju/deploy.go 2014-04-03 17:37:27 +0000
371@@ -154,11 +154,13 @@
372 if err != nil {
373 return err
374 }
375- curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries())
376+
377+ curl, err := resolveCharmURL(c.CharmName, client, conf)
378 if err != nil {
379 return err
380 }
381- repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath))
382+
383+ repo, err := charm.InferRepository(curl.Reference, ctx.AbsPath(c.RepoPath))
384 if err != nil {
385 return err
386 }
387@@ -260,15 +262,16 @@
388 if err != nil {
389 return err
390 }
391- curl, err := charm.InferURL(c.CharmName, conf.DefaultSeries())
392- if err != nil {
393- return err
394- }
395- repo, err := charm.InferRepository(curl, ctx.AbsPath(c.RepoPath))
396- if err != nil {
397- return err
398- }
399-
400+
401+ curl, err := resolveCharmURL1dot16(c.CharmName, conf)
402+ if err != nil {
403+ return err
404+ }
405+
406+ repo, err := charm.InferRepository(curl.Reference, c.RepoPath)
407+ if err != nil {
408+ return err
409+ }
410 repo = config.SpecializeCharmRepo(repo, conf)
411
412 // TODO(fwereade) it's annoying to roundtrip the bytes through the client
413
414=== modified file 'cmd/juju/environment_test.go'
415--- cmd/juju/environment_test.go 2014-03-26 05:44:41 +0000
416+++ cmd/juju/environment_test.go 2014-04-03 17:37:27 +0000
417@@ -124,12 +124,22 @@
418 }
419
420 func (s *SetEnvironmentSuite) TestChangeDefaultSeries(c *gc.C) {
421- _, err := testing.RunCommand(c, &SetEnvironmentCommand{}, []string{"default-series=raring"})
422- c.Assert(err, gc.IsNil)
423-
424+ // default-series not set
425 stateConfig, err := s.State.EnvironConfig()
426 c.Assert(err, gc.IsNil)
427- c.Assert(stateConfig.DefaultSeries(), gc.Equals, "raring")
428+ series, ok := stateConfig.DefaultSeries()
429+ c.Assert(ok, gc.Equals, true)
430+ c.Assert(series, gc.Equals, "precise") // default-series set in RepoSuite.SetUpTest
431+
432+ _, err = testing.RunCommand(c, &SetEnvironmentCommand{}, []string{"default-series=raring"})
433+ c.Assert(err, gc.IsNil)
434+
435+ stateConfig, err = s.State.EnvironConfig()
436+ c.Assert(err, gc.IsNil)
437+ series, ok = stateConfig.DefaultSeries()
438+ c.Assert(ok, gc.Equals, true)
439+ c.Assert(series, gc.Equals, "raring")
440+ c.Assert(config.PreferredSeries(stateConfig), gc.Equals, "raring")
441 }
442
443 func (s *SetEnvironmentSuite) TestChangeBooleanAttribute(c *gc.C) {
444
445=== modified file 'cmd/juju/publish.go'
446--- cmd/juju/publish.go 2014-04-01 04:53:43 +0000
447+++ cmd/juju/publish.go 2014-04-03 17:37:27 +0000
448@@ -115,7 +115,7 @@
449 pushLocation = c.changePushLocation(pushLocation)
450 }
451
452- repo, err := charm.InferRepository(curl, "/not/important")
453+ repo, err := charm.InferRepository(curl.Reference, "/not/important")
454 if err != nil {
455 return err
456 }
457
458=== modified file 'cmd/juju/upgradecharm.go'
459--- cmd/juju/upgradecharm.go 2014-04-01 04:53:43 +0000
460+++ cmd/juju/upgradecharm.go 2014-04-03 17:37:27 +0000
461@@ -134,8 +134,7 @@
462
463 var newURL *charm.URL
464 if c.SwitchURL != "" {
465- // A new charm URL was explicitly specified.
466- newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries())
467+ newURL, err = resolveCharmURL(c.SwitchURL, client, conf)
468 if err != nil {
469 return err
470 }
471@@ -143,11 +142,11 @@
472 // No new URL specified, but revision might have been.
473 newURL = oldURL.WithRevision(c.Revision)
474 }
475- repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath))
476+
477+ repo, err := charm.InferRepository(newURL.Reference, ctx.AbsPath(c.RepoPath))
478 if err != nil {
479 return err
480 }
481-
482 repo = config.SpecializeCharmRepo(repo, conf)
483
484 // If no explicit revision was set with either SwitchURL
485@@ -203,11 +202,7 @@
486 var newURL *charm.URL
487 if c.SwitchURL != "" {
488 // A new charm URL was explicitly specified.
489- conf, err := conn.State.EnvironConfig()
490- if err != nil {
491- return err
492- }
493- newURL, err = charm.InferURL(c.SwitchURL, conf.DefaultSeries())
494+ newURL, err = resolveCharmURL1dot16(c.SwitchURL, conf)
495 if err != nil {
496 return err
497 }
498@@ -215,11 +210,11 @@
499 // No new URL specified, but revision might have been.
500 newURL = oldURL.WithRevision(c.Revision)
501 }
502- repo, err := charm.InferRepository(newURL, ctx.AbsPath(c.RepoPath))
503+
504+ repo, err := charm.InferRepository(newURL.Reference, ctx.AbsPath(c.RepoPath))
505 if err != nil {
506 return err
507 }
508-
509 repo = config.SpecializeCharmRepo(repo, conf)
510
511 // If no explicit revision was set with either SwitchURL
512
513=== modified file 'cmd/juju/upgradejuju_test.go'
514--- cmd/juju/upgradejuju_test.go 2014-03-17 06:05:54 +0000
515+++ cmd/juju/upgradejuju_test.go 2014-04-03 17:37:27 +0000
516@@ -14,6 +14,7 @@
517 jc "github.com/juju/testing/checkers"
518 gc "launchpad.net/gocheck"
519
520+ "launchpad.net/juju-core/environs/config"
521 "launchpad.net/juju-core/environs/filestorage"
522 "launchpad.net/juju-core/environs/storage"
523 "launchpad.net/juju-core/environs/sync"
524@@ -225,14 +226,14 @@
525 agentVersion: "2.0.0",
526 args: []string{"--upload-tools"},
527 expectVersion: "2.2.0.1",
528- expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-precise-amd64", "2.2.0.1-raring-amd64"},
529+ expectUploaded: []string{"2.2.0.1-quantal-amd64", "2.2.0.1-%LTS%-amd64", "2.2.0.1-raring-amd64"},
530 }, {
531 about: "upload with explicit version",
532 currentVersion: "2.2.0-quantal-amd64",
533 agentVersion: "2.0.0",
534 args: []string{"--upload-tools", "--version", "2.7.3"},
535 expectVersion: "2.7.3.1",
536- expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-precise-amd64", "2.7.3.1-raring-amd64"},
537+ expectUploaded: []string{"2.7.3.1-quantal-amd64", "2.7.3.1-%LTS%-amd64", "2.7.3.1-raring-amd64"},
538 }, {
539 about: "upload with explicit series",
540 currentVersion: "2.2.0-quantal-amd64",
541@@ -246,7 +247,7 @@
542 agentVersion: "2.0.0",
543 args: []string{"--upload-tools"},
544 expectVersion: "2.1.0.1",
545- expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-precise-amd64", "2.1.0.1-raring-amd64"},
546+ expectUploaded: []string{"2.1.0.1-quantal-amd64", "2.1.0.1-%LTS%-amd64", "2.1.0.1-raring-amd64"},
547 }, {
548 about: "upload bumps version when necessary",
549 tools: []string{"2.4.6-quantal-amd64", "2.4.8-quantal-amd64"},
550@@ -254,7 +255,7 @@
551 agentVersion: "2.4.0",
552 args: []string{"--upload-tools"},
553 expectVersion: "2.4.6.1",
554- expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-precise-amd64", "2.4.6.1-raring-amd64"},
555+ expectUploaded: []string{"2.4.6.1-quantal-amd64", "2.4.6.1-%LTS%-amd64", "2.4.6.1-raring-amd64"},
556 }, {
557 about: "upload re-bumps version when necessary",
558 tools: []string{"2.4.6-quantal-amd64", "2.4.6.2-saucy-i386", "2.4.8-quantal-amd64"},
559@@ -262,7 +263,7 @@
560 agentVersion: "2.4.6.2",
561 args: []string{"--upload-tools"},
562 expectVersion: "2.4.6.3",
563- expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-precise-amd64", "2.4.6.3-raring-amd64"},
564+ expectUploaded: []string{"2.4.6.3-quantal-amd64", "2.4.6.3-%LTS%-amd64", "2.4.6.3-raring-amd64"},
565 }, {
566 about: "upload with explicit version bumps when necessary",
567 currentVersion: "2.2.0-quantal-amd64",
568@@ -270,7 +271,7 @@
569 agentVersion: "2.0.0",
570 args: []string{"--upload-tools", "--version", "2.7.3"},
571 expectVersion: "2.7.3.2",
572- expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-precise-amd64", "2.7.3.2-raring-amd64"},
573+ expectUploaded: []string{"2.7.3.2-quantal-amd64", "2.7.3.2-%LTS%-amd64", "2.7.3.2-raring-amd64"},
574 }}
575
576 // getMockBuildTools returns a sync.BuildToolsTarballFunc implementation which generates
577@@ -356,6 +357,9 @@
578 c.Check(agentVersion, gc.Equals, version.MustParse(test.expectVersion))
579
580 for _, uploaded := range test.expectUploaded {
581+ // Substitute latest LTS for placeholder in expected series for uploaded tools
582+ uploaded = strings.Replace(uploaded, "%LTS%", config.LatestLtsSeries(), 1)
583+
584 vers := version.MustParseBinary(uploaded)
585 r, err := storage.Get(s.Conn.Environ.Storage(), envtools.StorageName(vers))
586 if !c.Check(err, gc.IsNil) {
587
588=== modified file 'cmd/plugins/juju-metadata/imagemetadata.go'
589--- cmd/plugins/juju-metadata/imagemetadata.go 2014-03-28 12:28:30 +0000
590+++ cmd/plugins/juju-metadata/imagemetadata.go 2014-04-03 17:37:27 +0000
591@@ -97,7 +97,7 @@
592 }
593 cfg := environ.Config()
594 if c.Series == "" {
595- c.Series = cfg.DefaultSeries()
596+ c.Series = config.PreferredSeries(cfg)
597 }
598 if v, ok := cfg.AllAttrs()["control-bucket"]; ok {
599 c.privateStorage = v.(string)
600@@ -110,7 +110,7 @@
601 logger.Infof("no environment found, creating image metadata using user supplied data")
602 }
603 if c.Series == "" {
604- c.Series = config.DefaultSeries
605+ c.Series = config.LatestLtsSeries()
606 }
607 if c.ImageId == "" {
608 return fmt.Errorf("image id must be specified")
609
610=== modified file 'cmd/plugins/juju-metadata/imagemetadata_test.go'
611--- cmd/plugins/juju-metadata/imagemetadata_test.go 2014-03-13 07:54:56 +0000
612+++ cmd/plugins/juju-metadata/imagemetadata_test.go 2014-04-03 17:37:27 +0000
613@@ -15,6 +15,7 @@
614 gc "launchpad.net/gocheck"
615
616 "launchpad.net/juju-core/cmd"
617+ "launchpad.net/juju-core/environs/config"
618 "launchpad.net/juju-core/testing"
619 "launchpad.net/juju-core/testing/testbase"
620 )
621@@ -61,6 +62,7 @@
622 var seriesVersions map[string]string = map[string]string{
623 "precise": "12.04",
624 "raring": "13.04",
625+ "trusty": "14.04",
626 }
627
628 type expectedMetadata struct {
629@@ -138,7 +140,7 @@
630 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
631 }
632
633-func (s *ImageMetadataSuite) TestImageMetadataFilesDefaultSeries(c *gc.C) {
634+func (s *ImageMetadataSuite) TestImageMetadataFilesLatestLts(c *gc.C) {
635 ctx := testing.Context(c)
636 code := cmd.Main(
637 &ImageMetadataCommand{}, ctx, []string{
638@@ -146,7 +148,7 @@
639 c.Assert(code, gc.Equals, 0)
640 out := testing.Stdout(ctx)
641 expected := expectedMetadata{
642- series: "precise",
643+ series: config.LatestLtsSeries(),
644 arch: "arch",
645 }
646 s.assertCommandOutput(c, expected, out, defaultIndexFileName, defaultImageFileName)
647
648=== modified file 'environs/bootstrap/bootstrap_test.go'
649--- environs/bootstrap/bootstrap_test.go 2014-03-28 13:27:45 +0000
650+++ environs/bootstrap/bootstrap_test.go 2014-04-03 17:37:27 +0000
651@@ -88,7 +88,7 @@
652
653 func uploadTools(c *gc.C, env environs.Environ) {
654 usefulVersion := version.Current
655- usefulVersion.Series = env.Config().DefaultSeries()
656+ usefulVersion.Series = config.PreferredSeries(env.Config())
657 envtesting.AssertUploadFakeToolsVersions(c, env.Storage(), usefulVersion)
658 }
659
660@@ -217,7 +217,7 @@
661 s.setDummyStorage(c, env)
662 envtesting.RemoveFakeTools(c, env.Storage())
663 arch := "ppc64"
664- _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), &arch)
665+ _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, config.PreferredSeries(env.Config()), &arch)
666 c.Assert(err, gc.NotNil)
667 stripped := strings.Replace(err.Error(), "\n", "", -1)
668 c.Assert(stripped,
669@@ -233,7 +233,7 @@
670 env := newEnviron("foo", useDefaultKeys, nil)
671 s.setDummyStorage(c, env)
672 envtesting.RemoveFakeTools(c, env.Storage())
673- _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil)
674+ _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, config.PreferredSeries(env.Config()), nil)
675 c.Assert(err, gc.NotNil)
676 stripped := strings.Replace(err.Error(), "\n", "", -1)
677 c.Assert(stripped,
678@@ -246,7 +246,7 @@
679 env := newEnviron("foo", useDefaultKeys, map[string]interface{}{"agent-version": "1.16.0"})
680 s.setDummyStorage(c, env)
681 envtesting.RemoveFakeTools(c, env.Storage())
682- _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil)
683+ _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, config.PreferredSeries(env.Config()), nil)
684 c.Assert(err, gc.NotNil)
685 stripped := strings.Replace(err.Error(), "\n", "", -1)
686 c.Assert(stripped,
687@@ -260,7 +260,7 @@
688 env := newEnviron("foo", useDefaultKeys, nil)
689 s.setDummyStorage(c, env)
690 envtesting.RemoveFakeTools(c, env.Storage())
691- _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil)
692+ _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, config.PreferredSeries(env.Config()), nil)
693 c.Assert(err, gc.NotNil)
694 stripped := strings.Replace(err.Error(), "\n", "", -1)
695 c.Assert(stripped,
696@@ -310,12 +310,12 @@
697 return "arm64"
698 })
699 arch := "arm64"
700- agentTools, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), &arch)
701+ agentTools, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, config.PreferredSeries(env.Config()), &arch)
702 c.Assert(err, gc.IsNil)
703 c.Assert(agentTools, gc.HasLen, 1)
704 expectedVers := version.Current
705 expectedVers.Number.Build++
706- expectedVers.Series = env.Config().DefaultSeries()
707+ expectedVers.Series = config.PreferredSeries(env.Config())
708 c.Assert(agentTools[0].Version, gc.DeepEquals, expectedVers)
709 }
710
711@@ -325,11 +325,18 @@
712 s.PatchValue(&version.Current, vers)
713 env := newEnviron("foo", useDefaultKeys, nil)
714 cfg := env.Config()
715- c.Assert(bootstrap.SeriesToUpload(cfg, nil), jc.SameContents, []string{"quantal", "precise"})
716+
717+ prefSeries := config.PreferredSeries(cfg)
718+ expect := []string{"quantal", prefSeries}
719+ if prefSeries != config.LatestLtsSeries() {
720+ expect = append(expect, config.LatestLtsSeries())
721+ }
722+ c.Assert(bootstrap.SeriesToUpload(cfg, nil), jc.SameContents, expect)
723+
724 c.Assert(bootstrap.SeriesToUpload(cfg, []string{"quantal"}), jc.SameContents, []string{"quantal"})
725 env = newEnviron("foo", useDefaultKeys, map[string]interface{}{"default-series": "lucid"})
726 cfg = env.Config()
727- c.Assert(bootstrap.SeriesToUpload(cfg, nil), jc.SameContents, []string{"quantal", "precise", "lucid"})
728+ c.Assert(bootstrap.SeriesToUpload(cfg, nil), jc.SameContents, []string{"quantal", config.LatestLtsSeries(), "lucid"})
729 }
730
731 func (s *bootstrapSuite) assertUploadTools(c *gc.C, vers version.Binary, allowRelease bool,
732
733=== modified file 'environs/bootstrap/synctools.go'
734--- environs/bootstrap/synctools.go 2014-03-26 03:30:35 +0000
735+++ environs/bootstrap/synctools.go 2014-04-03 17:37:27 +0000
736@@ -100,8 +100,10 @@
737 unique := set.NewStrings(series...)
738 if unique.IsEmpty() {
739 unique.Add(version.Current.Series)
740- unique.Add(config.DefaultSeries)
741- unique.Add(cfg.DefaultSeries())
742+ unique.Add(config.LatestLtsSeries())
743+ if series, ok := cfg.DefaultSeries(); ok {
744+ unique.Add(series)
745+ }
746 }
747 return unique.Values()
748 }
749@@ -175,7 +177,10 @@
750 // No tools available so our only hope is to build locally and upload.
751 logger.Warningf("no prepackaged tools available")
752 uploadSeries := SeriesToUpload(cfg, nil)
753- if err := UploadTools(ctx, env, toolsArch, false, append(uploadSeries, series)...); err != nil {
754+ if series != "" {
755+ uploadSeries = append(uploadSeries, series)
756+ }
757+ if err := UploadTools(ctx, env, toolsArch, false, uploadSeries...); err != nil {
758 logger.Errorf("%s", noToolsMessage)
759 return nil, fmt.Errorf("cannot upload bootstrap tools: %v", err)
760 }
761
762=== modified file 'environs/config/config.go'
763--- environs/config/config.go 2014-04-02 06:15:57 +0000
764+++ environs/config/config.go 2014-04-03 17:37:27 +0000
765@@ -7,6 +7,7 @@
766 "fmt"
767 "io/ioutil"
768 "os"
769+ "os/exec"
770 "path/filepath"
771 "regexp"
772 "strings"
773@@ -34,9 +35,6 @@
774 // port opened.
775 FwGlobal = "global"
776
777- // DefaultSeries returns the most recent Ubuntu LTS release name.
778- DefaultSeries string = "precise"
779-
780 // DefaultStatePort is the default port the state server is listening on.
781 DefaultStatePort int = 37017
782
783@@ -59,8 +57,53 @@
784 // refreshing the addresses, in seconds. Not too frequent, as we
785 // refresh addresses from the provider each time.
786 DefaultBootstrapSSHAddressesDelay int = 10
787+
788+ // fallbackLtsSeries is the latest LTS series we'll use, if we fail to
789+ // obtain this information from the system.
790+ fallbackLtsSeries string = "precise"
791 )
792
793+var latestLtsSeries string
794+
795+type HasDefaultSeries interface {
796+ DefaultSeries() (string, bool)
797+}
798+
799+// PreferredSeries returns the preferred series to use when a charm does not
800+// explicitly specify a series.
801+func PreferredSeries(cfg HasDefaultSeries) string {
802+ if series, ok := cfg.DefaultSeries(); ok {
803+ return series
804+ }
805+ return LatestLtsSeries()
806+}
807+
808+func LatestLtsSeries() string {
809+ if latestLtsSeries == "" {
810+ series, err := distroLtsSeries()
811+ if err != nil {
812+ latestLtsSeries = fallbackLtsSeries
813+ } else {
814+ latestLtsSeries = series
815+ }
816+ }
817+ return latestLtsSeries
818+}
819+
820+// distroLtsSeries returns the latest LTS series, if this information is
821+// available on this system.
822+func distroLtsSeries() (string, error) {
823+ out, err := exec.Command("distro-info", "--lts").Output()
824+ if err != nil {
825+ return "", err
826+ }
827+ series := strings.TrimSpace(string(out))
828+ if !charm.IsValidSeries(series) {
829+ return "", fmt.Errorf("not a valid LTS series: %q", series)
830+ }
831+ return series, nil
832+}
833+
834 // Config holds an immutable environment configuration.
835 type Config struct {
836 // defined holds the attributes that are defined for Config.
837@@ -162,7 +205,6 @@
838 // For backward compatibility purposes, we treat as unset string
839 // valued attributes that are set to the empty string, and fill
840 // out their defaults accordingly.
841- c.fillInStringDefault("default-series")
842 c.fillInStringDefault("firewall-mode")
843
844 // Load authorized-keys-path into authorized-keys if necessary.
845@@ -407,9 +449,17 @@
846 return c.mustString("name")
847 }
848
849-// DefaultSeries returns the default Ubuntu series for the environment.
850-func (c *Config) DefaultSeries() string {
851- return c.mustString("default-series")
852+// DefaultSeries returns the configured default Ubuntu series for the environment,
853+// and whether the default series was explicitly configured on the environment.
854+func (c *Config) DefaultSeries() (string, bool) {
855+ if s, ok := c.defined["default-series"]; ok {
856+ if series, ok := s.(string); ok && series != "" {
857+ return series, true
858+ } else if !ok {
859+ logger.Warningf("invalid default-series: %q", s)
860+ }
861+ }
862+ return "", false
863 }
864
865 // StatePort returns the state server port for the environment.
866@@ -771,6 +821,8 @@
867 "image-metadata-url": "", // TODO(rog) omit
868 "tools-metadata-url": "", // TODO(rog) omit
869
870+ "default-series": "",
871+
872 // For backward compatibility only - default ports were
873 // not filled out in previous versions of the configuration.
874 "state-port": DefaultStatePort,
875@@ -795,7 +847,6 @@
876 // UseDefaults.
877 func allDefaults() schema.Defaults {
878 d := schema.Defaults{
879- "default-series": DefaultSeries,
880 "firewall-mode": FwInstance,
881 "development": false,
882 "ssl-hostname-verification": true,
883
884=== modified file 'environs/config/config_test.go'
885--- environs/config/config_test.go 2014-03-26 08:14:32 +0000
886+++ environs/config/config_test.go 2014-04-03 17:37:27 +0000
887@@ -652,7 +652,6 @@
888 authTokenConfigTest("token=value, =z", false),
889 authTokenConfigTest("token=value =z", false),
890 authTokenConfigTest("\t", false),
891- missingAttributeNoDefault("default-series"),
892 missingAttributeNoDefault("firewall-mode"),
893 missingAttributeNoDefault("development"),
894 missingAttributeNoDefault("ssl-hostname-verification"),
895@@ -861,11 +860,14 @@
896 testmode, _ := test.attrs["test-mode"].(bool)
897 c.Assert(cfg.TestMode(), gc.Equals, testmode)
898
899- if series, _ := test.attrs["default-series"].(string); series != "" {
900- c.Assert(cfg.DefaultSeries(), gc.Equals, series)
901+ series, _ := test.attrs["default-series"].(string)
902+ if defaultSeries, ok := cfg.DefaultSeries(); ok {
903+ c.Assert(defaultSeries, gc.Equals, series)
904 } else {
905- c.Assert(cfg.DefaultSeries(), gc.Equals, config.DefaultSeries)
906+ c.Assert(series, gc.Equals, "")
907+ c.Assert(defaultSeries, gc.Equals, "")
908 }
909+
910 if m, _ := test.attrs["firewall-mode"].(string); m != "" {
911 c.Assert(cfg.FirewallMode(), gc.Equals, m)
912 }
913@@ -1010,7 +1012,7 @@
914 "bootstrap-timeout": 3600,
915 "bootstrap-retry-delay": 30,
916 "bootstrap-addresses-delay": 10,
917- "default-series": "precise",
918+ "default-series": testing.FakeDefaultSeries,
919 "charm-store-auth": "token=auth",
920 "test-mode": false,
921 }
922@@ -1019,7 +1021,6 @@
923
924 // These attributes are added if not set.
925 attrs["development"] = false
926- attrs["default-series"] = config.DefaultSeries
927 attrs["logging-config"] = "<root>=WARNING;unit=DEBUG"
928 attrs["ca-private-key"] = ""
929 attrs["image-metadata-url"] = ""
930
931=== modified file 'environs/jujutest/livetests.go'
932--- environs/jujutest/livetests.go 2014-04-03 02:57:11 +0000
933+++ environs/jujutest/livetests.go 2014-04-03 17:37:27 +0000
934@@ -133,7 +133,7 @@
935 // we could connect to (actual live tests, rather than local-only)
936 cons := constraints.MustParse("mem=2G")
937 if t.CanOpenState {
938- _, err := sync.Upload(t.Env.Storage(), nil, config.DefaultSeries)
939+ _, err := sync.Upload(t.Env.Storage(), nil, coretesting.FakeDefaultSeries)
940 c.Assert(err, gc.IsNil)
941 }
942 envtesting.UploadFakeTools(c, t.Env.Storage())
943@@ -442,7 +442,7 @@
944
945 // If the series has not been specified, we expect the most recent Ubuntu LTS release to be used.
946 expectedVersion := version.Current
947- expectedVersion.Series = config.DefaultSeries
948+ expectedVersion.Series = config.LatestLtsSeries()
949
950 mtools0 := waitAgentTools(c, mw0, expectedVersion)
951
952
953=== modified file 'environs/testing/tools.go'
954--- environs/testing/tools.go 2014-03-27 00:42:27 +0000
955+++ environs/testing/tools.go 2014-04-03 17:37:27 +0000
956@@ -13,7 +13,6 @@
957
958 agenttools "launchpad.net/juju-core/agent/tools"
959 "launchpad.net/juju-core/environs"
960- "launchpad.net/juju-core/environs/config"
961 "launchpad.net/juju-core/environs/simplestreams"
962 "launchpad.net/juju-core/environs/storage"
963 envtools "launchpad.net/juju-core/environs/tools"
964@@ -163,8 +162,9 @@
965 func uploadFakeTools(stor storage.Storage) error {
966 versions := []version.Binary{version.Current}
967 toolsVersion := version.Current
968- if toolsVersion.Series != config.DefaultSeries {
969- toolsVersion.Series = config.DefaultSeries
970+ latestLts := coretesting.FakeDefaultSeries
971+ if toolsVersion.Series != latestLts {
972+ toolsVersion.Series = latestLts
973 versions = append(versions, toolsVersion)
974 }
975 if _, err := UploadFakeToolsVersions(stor, versions...); err != nil {
976@@ -175,9 +175,9 @@
977
978 // UploadFakeTools puts fake tools into the supplied storage with a binary
979 // version matching version.Current; if version.Current's series is different
980-// to config.DefaultSeries, matching fake tools will be uploaded for that series.
981-// This is useful for tests that are kinda casual about specifying their
982-// environment.
983+// to coretesting.FakeDefaultSeries, matching fake tools will be uploaded for that
984+// series. This is useful for tests that are kinda casual about specifying
985+// their environment.
986 func UploadFakeTools(c *gc.C, stor storage.Storage) {
987 c.Assert(uploadFakeTools(stor), gc.IsNil)
988 }
989@@ -196,8 +196,9 @@
990 name := envtools.StorageName(toolsVersion)
991 err := stor.Remove(name)
992 c.Check(err, gc.IsNil)
993- if version.Current.Series != config.DefaultSeries {
994- toolsVersion.Series = config.DefaultSeries
995+ defaultSeries := coretesting.FakeDefaultSeries
996+ if version.Current.Series != defaultSeries {
997+ toolsVersion.Series = defaultSeries
998 name := envtools.StorageName(toolsVersion)
999 err := stor.Remove(name)
1000 c.Check(err, gc.IsNil)
1001
1002=== modified file 'juju/apiconn_test.go'
1003--- juju/apiconn_test.go 2014-04-02 00:18:34 +0000
1004+++ juju/apiconn_test.go 2014-04-03 17:37:27 +0000
1005@@ -175,7 +175,7 @@
1006 "name": "myenv",
1007 "state-server": true,
1008 "authorized-keys": "i-am-a-key",
1009- "default-series": config.DefaultSeries,
1010+ "default-series": config.LatestLtsSeries(),
1011 "firewall-mode": config.FwInstance,
1012 "development": false,
1013 "ssl-hostname-verification": true,
1014
1015=== modified file 'juju/testing/conn.go'
1016--- juju/testing/conn.go 2014-03-25 15:06:31 +0000
1017+++ juju/testing/conn.go 2014-04-03 17:37:27 +0000
1018@@ -17,6 +17,7 @@
1019 "launchpad.net/juju-core/constraints"
1020 "launchpad.net/juju-core/environs"
1021 "launchpad.net/juju-core/environs/bootstrap"
1022+ "launchpad.net/juju-core/environs/config"
1023 "launchpad.net/juju-core/environs/configstore"
1024 envtesting "launchpad.net/juju-core/environs/testing"
1025 "launchpad.net/juju-core/juju"
1026@@ -164,6 +165,14 @@
1027 return s.openAPIAs(c, machine.Tag(), password, "fake_nonce"), machine
1028 }
1029
1030+func PreferredDefaultVersions(conf *config.Config, template version.Binary) []version.Binary {
1031+ prefVersion := template
1032+ prefVersion.Series = config.PreferredSeries(conf)
1033+ defaultVersion := template
1034+ defaultVersion.Series = testing.FakeDefaultSeries
1035+ return []version.Binary{prefVersion, defaultVersion}
1036+}
1037+
1038 func (s *JujuConnSuite) setUpConn(c *gc.C) {
1039 if s.RootDir != "" {
1040 panic("JujuConnSuite.setUpConn without teardown")
1041@@ -203,7 +212,11 @@
1042 c.Assert(environ.Name(), gc.Equals, "dummyenv")
1043 s.PatchValue(&dummy.DataDir, s.DataDir())
1044
1045- envtesting.MustUploadFakeTools(environ.Storage())
1046+ versions := PreferredDefaultVersions(environ.Config(), version.Current)
1047+ versions = append(versions, version.Current)
1048+
1049+ // Upload tools for both preferred and fake default series
1050+ envtesting.MustUploadFakeToolsVersions(environ.Storage(), versions...)
1051 c.Assert(bootstrap.Bootstrap(ctx, environ, constraints.Value{}), gc.IsNil)
1052
1053 s.BackingState = environ.(GetStater).GetStateInAPIServer()
1054@@ -285,7 +298,7 @@
1055 ch := testing.Charms.Dir(name)
1056 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
1057 curl := charm.MustParseURL("local:quantal/" + ident)
1058- repo, err := charm.InferRepository(curl, testing.Charms.Path())
1059+ repo, err := charm.InferRepository(curl.Reference, testing.Charms.Path())
1060 c.Assert(err, gc.IsNil)
1061 sch, err := s.Conn.PutCharm(curl, repo, false)
1062 c.Assert(err, gc.IsNil)
1063
1064=== modified file 'juju/testing/instance.go'
1065--- juju/testing/instance.go 2014-04-01 16:27:22 +0000
1066+++ juju/testing/instance.go 2014-04-03 17:37:27 +0000
1067@@ -10,6 +10,7 @@
1068
1069 "launchpad.net/juju-core/constraints"
1070 "launchpad.net/juju-core/environs"
1071+ "launchpad.net/juju-core/environs/config"
1072 "launchpad.net/juju-core/environs/tools"
1073 "launchpad.net/juju-core/instance"
1074 "launchpad.net/juju-core/names"
1075@@ -112,7 +113,7 @@
1076 ) (
1077 instance.Instance, *instance.HardwareCharacteristics, error,
1078 ) {
1079- series := env.Config().DefaultSeries()
1080+ series := config.PreferredSeries(env.Config())
1081 agentVersion, ok := env.Config().AgentVersion()
1082 if !ok {
1083 return nil, nil, fmt.Errorf("missing agent version in environment config")
1084
1085=== modified file 'provider/azure/environ_test.go'
1086--- provider/azure/environ_test.go 2014-04-03 09:07:57 +0000
1087+++ provider/azure/environ_test.go 2014-04-03 17:37:27 +0000
1088@@ -448,7 +448,7 @@
1089 err = env.SetConfig(cfg)
1090 c.Assert(err, gc.IsNil)
1091
1092- c.Check(env.ecfg.Config.DefaultSeries(), gc.Equals, "feisty")
1093+ c.Check(config.PreferredSeries(env.ecfg.Config), gc.Equals, "feisty")
1094 }
1095
1096 func (*environSuite) TestSetConfigLocksEnviron(c *gc.C) {
1097
1098=== modified file 'provider/common/bootstrap.go'
1099--- provider/common/bootstrap.go 2014-03-28 22:31:08 +0000
1100+++ provider/common/bootstrap.go 2014-04-03 17:37:27 +0000
1101@@ -42,7 +42,7 @@
1102 defer func() { handleBootstrapError(err, ctx, inst, env) }()
1103
1104 // First thing, ensure we have tools otherwise there's no point.
1105- selectedTools, err := EnsureBootstrapTools(ctx, env, env.Config().DefaultSeries(), cons.Arch)
1106+ selectedTools, err := EnsureBootstrapTools(ctx, env, config.PreferredSeries(env.Config()), cons.Arch)
1107 if err != nil {
1108 return err
1109 }
1110
1111=== modified file 'provider/dummy/environs.go'
1112--- provider/dummy/environs.go 2014-04-03 03:37:45 +0000
1113+++ provider/dummy/environs.go 2014-04-03 17:37:27 +0000
1114@@ -545,7 +545,7 @@
1115 }
1116
1117 func (e *environ) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error {
1118- selectedTools, err := common.EnsureBootstrapTools(ctx, e, e.Config().DefaultSeries(), cons.Arch)
1119+ selectedTools, err := common.EnsureBootstrapTools(ctx, e, config.PreferredSeries(e.Config()), cons.Arch)
1120 if err != nil {
1121 return err
1122 }
1123
1124=== modified file 'provider/ec2/ec2.go'
1125--- provider/ec2/ec2.go 2014-04-03 03:37:45 +0000
1126+++ provider/ec2/ec2.go 2014-04-03 17:37:27 +0000
1127@@ -367,7 +367,7 @@
1128 return nil, err
1129 }
1130 return &simplestreams.MetadataLookupParams{
1131- Series: e.ecfg().DefaultSeries(),
1132+ Series: config.PreferredSeries(e.ecfg()),
1133 Region: cloudSpec.Region,
1134 Endpoint: cloudSpec.Endpoint,
1135 Architectures: arch.AllSupportedArches,
1136
1137=== modified file 'provider/ec2/live_test.go'
1138--- provider/ec2/live_test.go 2014-03-20 03:06:57 +0000
1139+++ provider/ec2/live_test.go 2014-04-03 17:37:27 +0000
1140@@ -101,7 +101,7 @@
1141 t.LiveTests.SetUpTest(c)
1142 t.PatchValue(&version.Current, version.Binary{
1143 Number: version.Current.Number,
1144- Series: config.DefaultSeries,
1145+ Series: coretesting.FakeDefaultSeries,
1146 Arch: arch.AMD64,
1147 })
1148
1149
1150=== modified file 'provider/ec2/local_test.go'
1151--- provider/ec2/local_test.go 2014-04-02 11:35:49 +0000
1152+++ provider/ec2/local_test.go 2014-04-03 17:37:27 +0000
1153@@ -205,7 +205,7 @@
1154 t.Tests.SetUpTest(c)
1155 t.PatchValue(&version.Current, version.Binary{
1156 Number: version.Current.Number,
1157- Series: config.DefaultSeries,
1158+ Series: coretesting.FakeDefaultSeries,
1159 Arch: arch.AMD64,
1160 })
1161 }
1162
1163=== modified file 'provider/joyent/environ.go'
1164--- provider/joyent/environ.go 2014-04-03 03:37:45 +0000
1165+++ provider/joyent/environ.go 2014-04-03 17:37:27 +0000
1166@@ -154,7 +154,7 @@
1167 region = env.Ecfg().Region()
1168 }
1169 return &simplestreams.MetadataLookupParams{
1170- Series: env.Ecfg().DefaultSeries(),
1171+ Series: config.PreferredSeries(env.Ecfg()),
1172 Region: region,
1173 Endpoint: env.Ecfg().sdcUrl(),
1174 Architectures: []string{"amd64", "arm"},
1175
1176=== modified file 'provider/openstack/provider.go'
1177--- provider/openstack/provider.go 2014-04-03 03:37:45 +0000
1178+++ provider/openstack/provider.go 2014-04-03 17:37:27 +0000
1179@@ -1236,7 +1236,7 @@
1180 return nil, err
1181 }
1182 return &simplestreams.MetadataLookupParams{
1183- Series: e.ecfg().DefaultSeries(),
1184+ Series: config.PreferredSeries(e.ecfg()),
1185 Region: cloudSpec.Region,
1186 Endpoint: cloudSpec.Endpoint,
1187 Architectures: arch.AllSupportedArches,
1188
1189=== modified file 'state/api/client.go'
1190--- state/api/client.go 2014-03-28 13:31:26 +0000
1191+++ state/api/client.go 2014-04-03 17:37:27 +0000
1192@@ -608,6 +608,24 @@
1193 return c.call("AddCharm", args, nil)
1194 }
1195
1196+// ResolveCharms resolves the best available charm URLs with series, for charm
1197+// locations without a series specified.
1198+func (c *Client) ResolveCharm(ref charm.Reference) (*charm.URL, error) {
1199+ args := params.ResolveCharms{References: []charm.Reference{ref}}
1200+ result := new(params.ResolveCharmResults)
1201+ if err := c.st.Call("Client", "", "ResolveCharms", args, result); err != nil {
1202+ return nil, err
1203+ }
1204+ if len(result.URLs) == 0 {
1205+ return nil, fmt.Errorf("unexpected empty response")
1206+ }
1207+ urlInfo := result.URLs[0]
1208+ if urlInfo.Error != "" {
1209+ return nil, fmt.Errorf("%v", urlInfo.Error)
1210+ }
1211+ return urlInfo.URL, nil
1212+}
1213+
1214 func (c *Client) UploadTools(
1215 toolsFilename string, vers version.Binary, fakeSeries ...string,
1216 ) (
1217
1218=== modified file 'state/api/params/params.go'
1219--- state/api/params/params.go 2014-04-03 02:57:11 +0000
1220+++ state/api/params/params.go 2014-04-03 17:37:27 +0000
1221@@ -330,6 +330,22 @@
1222 CharmURL string
1223 }
1224
1225+// ResolveCharms stores charm references for a ResolveCharms call.
1226+type ResolveCharms struct {
1227+ References []charm.Reference
1228+}
1229+
1230+// ResolveCharmResult holds the result of resolving a charm reference to a URL, or any error that occurred.
1231+type ResolveCharmResult struct {
1232+ URL *charm.URL `json:",omitempty"`
1233+ Error string `json:",omitempty"`
1234+}
1235+
1236+// ResolveCharmResults holds results of the ResolveCharms call.
1237+type ResolveCharmResults struct {
1238+ URLs []ResolveCharmResult
1239+}
1240+
1241 // AllWatcherId holds the id of an AllWatcher.
1242 type AllWatcherId struct {
1243 AllWatcherId string
1244
1245=== modified file 'state/apiserver/client/client.go'
1246--- state/apiserver/client/client.go 2014-03-28 13:31:26 +0000
1247+++ state/apiserver/client/client.go 2014-04-03 17:37:27 +0000
1248@@ -567,16 +567,8 @@
1249 results := params.AddMachinesResults{
1250 Machines: make([]params.AddMachinesResult, len(args.MachineParams)),
1251 }
1252-
1253- var defaultSeries string
1254- conf, err := c.api.state.EnvironConfig()
1255- if err != nil {
1256- return results, err
1257- }
1258- defaultSeries = conf.DefaultSeries()
1259-
1260 for i, p := range args.MachineParams {
1261- m, err := c.addOneMachine(p, defaultSeries)
1262+ m, err := c.addOneMachine(p)
1263 results.Machines[i].Error = common.ServerError(err)
1264 if err == nil {
1265 results.Machines[i].Machine = m.Id()
1266@@ -590,9 +582,13 @@
1267 return c.AddMachines(args)
1268 }
1269
1270-func (c *Client) addOneMachine(p params.AddMachineParams, defaultSeries string) (*state.Machine, error) {
1271+func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) {
1272 if p.Series == "" {
1273- p.Series = defaultSeries
1274+ conf, err := c.api.state.EnvironConfig()
1275+ if err != nil {
1276+ return nil, err
1277+ }
1278+ p.Series = config.PreferredSeries(conf)
1279 }
1280 if p.ContainerType != "" {
1281 // Guard against dubious client by making sure that
1282@@ -709,7 +705,7 @@
1283 }
1284
1285 info := api.EnvironmentInfo{
1286- DefaultSeries: conf.DefaultSeries(),
1287+ DefaultSeries: config.PreferredSeries(conf),
1288 ProviderType: conf.Type(),
1289 Name: conf.Name(),
1290 UUID: env.UUID(),
1291@@ -955,6 +951,37 @@
1292 return err
1293 }
1294
1295+func (c *Client) ResolveCharms(args params.ResolveCharms) (params.ResolveCharmResults, error) {
1296+ var results params.ResolveCharmResults
1297+
1298+ envConfig, err := c.api.state.EnvironConfig()
1299+ if err != nil {
1300+ return params.ResolveCharmResults{}, err
1301+ }
1302+ repo := config.SpecializeCharmRepo(CharmStore, envConfig)
1303+
1304+ for _, ref := range args.References {
1305+ result := params.ResolveCharmResult{}
1306+ curl, err := c.resolveCharm(ref, repo)
1307+ if err != nil {
1308+ result.Error = err.Error()
1309+ } else {
1310+ result.URL = curl
1311+ }
1312+ results.URLs = append(results.URLs, result)
1313+ }
1314+ return results, nil
1315+}
1316+
1317+func (c *Client) resolveCharm(ref charm.Reference, repo charm.Repository) (*charm.URL, error) {
1318+ if ref.Schema != "cs" {
1319+ return nil, fmt.Errorf("only charm store charm references are supported, with cs: schema")
1320+ }
1321+
1322+ // Resolve the charm location with the repository.
1323+ return repo.Resolve(ref)
1324+}
1325+
1326 // CharmArchiveName returns a string that is suitable as a file name
1327 // in a storage URL. It is constructed from the charm name, revision
1328 // and a random UUID string.
1329@@ -993,7 +1020,7 @@
1330 if err != nil {
1331 return err
1332 }
1333- series = cfg.DefaultSeries()
1334+ series = config.PreferredSeries(cfg)
1335 }
1336 return c.api.state.EnsureAvailability(args.NumStateServers, args.Constraints, series)
1337 }
1338
1339=== modified file 'state/apiserver/client/client_test.go'
1340--- state/apiserver/client/client_test.go 2014-04-01 03:37:13 +0000
1341+++ state/apiserver/client/client_test.go 2014-04-03 17:37:27 +0000
1342@@ -261,7 +261,7 @@
1343 c.Assert(err, gc.IsNil)
1344 env, err := s.State.Environment()
1345 c.Assert(err, gc.IsNil)
1346- c.Assert(info.DefaultSeries, gc.Equals, conf.DefaultSeries())
1347+ c.Assert(info.DefaultSeries, gc.Equals, config.PreferredSeries(conf))
1348 c.Assert(info.ProviderType, gc.Equals, conf.Type())
1349 c.Assert(info.Name, gc.Equals, conf.Name())
1350 c.Assert(info.UUID, gc.Equals, env.UUID())
1351@@ -1119,8 +1119,12 @@
1352 }
1353
1354 func addCharm(c *gc.C, store *coretesting.MockCharmStore, name string) (*charm.URL, charm.Charm) {
1355+ return addSeriesCharm(c, store, "precise", name)
1356+}
1357+
1358+func addSeriesCharm(c *gc.C, store *coretesting.MockCharmStore, series, name string) (*charm.URL, charm.Charm) {
1359 bundle := coretesting.Charms.Bundle(c.MkDir(), name)
1360- scurl := fmt.Sprintf("cs:precise/%s-%d", name, bundle.Revision())
1361+ scurl := fmt.Sprintf("cs:%s/%s-%d", series, name, bundle.Revision())
1362 curl := charm.MustParseURL(scurl)
1363 err := store.SetCharm(curl, bundle)
1364 c.Assert(err, gc.IsNil)
1365@@ -1634,7 +1638,7 @@
1366 c.Assert(len(machines), gc.Equals, 3)
1367 for i, machineResult := range machines {
1368 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
1369- s.checkMachine(c, machineResult.Machine, config.DefaultSeries, apiParams[i].Constraints.String())
1370+ s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
1371 }
1372 }
1373
1374@@ -1684,7 +1688,7 @@
1375 c.Assert(len(machines), gc.Equals, 3)
1376 for i, machineResult := range machines {
1377 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
1378- s.checkMachine(c, machineResult.Machine, config.DefaultSeries, apiParams[i].Constraints.String())
1379+ s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
1380 }
1381 }
1382
1383@@ -1755,7 +1759,7 @@
1384 c.Assert(machineResult.Error, gc.ErrorMatches, "cannot add a new machine: cannot add a machine with an instance id and no nonce")
1385 } else {
1386 c.Assert(machineResult.Machine, gc.DeepEquals, strconv.Itoa(i))
1387- s.checkMachine(c, machineResult.Machine, config.DefaultSeries, apiParams[i].Constraints.String())
1388+ s.checkMachine(c, machineResult.Machine, coretesting.FakeDefaultSeries, apiParams[i].Constraints.String())
1389 instanceId := fmt.Sprintf("1234-%d", i)
1390 s.checkInstance(c, machineResult.Machine, instanceId, "foo", hc, addrs)
1391 }
1392@@ -1943,6 +1947,57 @@
1393 s.assertUploaded(c, storage, sch.BundleURL(), sch.BundleSha256())
1394 }
1395
1396+var resolveCharmCases = []struct {
1397+ schema, defaultSeries, charmName string
1398+ parseErr string
1399+ resolveErr string
1400+}{
1401+ {"cs", "precise", "wordpress", "", ""},
1402+ {"cs", "trusty", "wordpress", "", ""},
1403+ {"cs", "", "wordpress", "", `missing default series, cannot resolve charm url: "cs:wordpress"`},
1404+ {"cs", "trusty", "", `charm URL has invalid charm name: "cs:"`, ""},
1405+ {"local", "trusty", "wordpress", "", `only charm store charm references are supported, with cs: schema`},
1406+ {"cs", "precise", "hl3", "", ""},
1407+ {"cs", "trusty", "hl3", "", ""},
1408+ {"cs", "", "hl3", "", `missing default series, cannot resolve charm url: \"cs:hl3\"`},
1409+}
1410+
1411+func (s *clientSuite) TestResolveCharm(c *gc.C) {
1412+ store, restore := makeMockCharmStore()
1413+ defer restore()
1414+
1415+ for i, test := range resolveCharmCases {
1416+ c.Logf("test %d: %#v", i, test)
1417+ // Mock charm store will use this to resolve a charm reference.
1418+ store.DefaultSeries = test.defaultSeries
1419+
1420+ client := s.APIState.Client()
1421+ ref, series, err := charm.ParseReference(fmt.Sprintf("%s:%s", test.schema, test.charmName))
1422+ if test.parseErr == "" {
1423+ if !c.Check(err, gc.IsNil) {
1424+ continue
1425+ }
1426+ } else {
1427+ c.Assert(err, gc.NotNil)
1428+ c.Check(err, gc.ErrorMatches, test.parseErr)
1429+ continue
1430+ }
1431+ c.Check(series, gc.Equals, "")
1432+ c.Check(ref.String(), gc.Equals, fmt.Sprintf("%s:%s", test.schema, test.charmName))
1433+
1434+ curl, err := client.ResolveCharm(ref)
1435+ if err == nil {
1436+ c.Assert(curl, gc.NotNil)
1437+ // Only cs: schema should make it through here
1438+ c.Check(curl.String(), gc.Equals, fmt.Sprintf("cs:%s/%s", test.defaultSeries, test.charmName))
1439+ c.Check(test.resolveErr, gc.Equals, "")
1440+ } else {
1441+ c.Check(curl, gc.IsNil)
1442+ c.Check(err, gc.ErrorMatches, test.resolveErr)
1443+ }
1444+ }
1445+}
1446+
1447 func (s *clientSuite) TestAddCharmConcurrently(c *gc.C) {
1448 store, restore := makeMockCharmStore()
1449 defer restore()
1450
1451=== modified file 'testing/environ.go'
1452--- testing/environ.go 2014-04-01 08:20:44 +0000
1453+++ testing/environ.go 2014-04-03 17:37:27 +0000
1454@@ -28,6 +28,8 @@
1455 }
1456 }
1457
1458+const FakeDefaultSeries = "precise"
1459+
1460 // FakeConfig() returns an environment configuration for a
1461 // fake provider with all required attributes set.
1462 func FakeConfig() Attrs {
1463@@ -43,7 +45,7 @@
1464 "development": false,
1465 "state-port": 19034,
1466 "api-port": 17777,
1467- "default-series": config.DefaultSeries,
1468+ "default-series": FakeDefaultSeries,
1469 }
1470 }
1471
1472
1473=== modified file 'worker/provisioner/container_initialisation_test.go'
1474--- worker/provisioner/container_initialisation_test.go 2014-03-19 03:48:12 +0000
1475+++ worker/provisioner/container_initialisation_test.go 2014-04-03 17:37:27 +0000
1476@@ -12,10 +12,10 @@
1477
1478 "launchpad.net/juju-core/agent"
1479 "launchpad.net/juju-core/environs"
1480- "launchpad.net/juju-core/environs/config"
1481 "launchpad.net/juju-core/instance"
1482 "launchpad.net/juju-core/state"
1483 apiprovisioner "launchpad.net/juju-core/state/api/provisioner"
1484+ coretesting "launchpad.net/juju-core/testing"
1485 "launchpad.net/juju-core/utils"
1486 "launchpad.net/juju-core/version"
1487 "launchpad.net/juju-core/worker"
1488@@ -88,7 +88,7 @@
1489
1490 // make a container on the host machine
1491 template := state.MachineTemplate{
1492- Series: config.DefaultSeries,
1493+ Series: coretesting.FakeDefaultSeries,
1494 Jobs: []state.MachineJob{state.JobHostUnits},
1495 }
1496 container, err := s.State.AddMachineInsideMachine(template, host.Id(), ctype)
1497@@ -131,7 +131,7 @@
1498 for _, ctype := range instance.ContainerTypes {
1499 // create a machine to host the container.
1500 m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
1501- Series: config.DefaultSeries,
1502+ Series: coretesting.FakeDefaultSeries,
1503 Jobs: []state.MachineJob{state.JobHostUnits},
1504 Constraints: s.defaultConstraints,
1505 })
1506@@ -154,7 +154,7 @@
1507
1508 // create a machine to host the container.
1509 m, err := s.BackingState.AddOneMachine(state.MachineTemplate{
1510- Series: config.DefaultSeries,
1511+ Series: coretesting.FakeDefaultSeries,
1512 Jobs: []state.MachineJob{state.JobHostUnits},
1513 Constraints: s.defaultConstraints,
1514 })
1515
1516=== modified file 'worker/provisioner/kvm-broker_test.go'
1517--- worker/provisioner/kvm-broker_test.go 2014-04-02 11:35:49 +0000
1518+++ worker/provisioner/kvm-broker_test.go 2014-04-03 17:37:27 +0000
1519@@ -16,7 +16,6 @@
1520 "launchpad.net/juju-core/container/kvm/mock"
1521 kvmtesting "launchpad.net/juju-core/container/kvm/testing"
1522 "launchpad.net/juju-core/environs"
1523- "launchpad.net/juju-core/environs/config"
1524 "launchpad.net/juju-core/errors"
1525 "launchpad.net/juju-core/instance"
1526 instancetest "launchpad.net/juju-core/instance/testing"
1527@@ -162,7 +161,7 @@
1528
1529 // The kvm provisioner actually needs the machine it is being created on
1530 // to be in state, in order to get the watcher.
1531- m, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits, state.JobManageEnviron)
1532+ m, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits, state.JobManageEnviron)
1533 c.Assert(err, gc.IsNil)
1534 err = m.SetAddresses(instance.NewAddress("0.1.2.3", instance.NetworkUnknown))
1535 c.Assert(err, gc.IsNil)
1536@@ -227,7 +226,7 @@
1537 defer stop(c, p)
1538
1539 // Check that an instance is not provisioned when the machine is created.
1540- _, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
1541+ _, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
1542 c.Assert(err, gc.IsNil)
1543
1544 s.expectNoEvents(c)
1545@@ -244,7 +243,7 @@
1546
1547 func (s *kvmProvisionerSuite) addContainer(c *gc.C) *state.Machine {
1548 template := state.MachineTemplate{
1549- Series: config.DefaultSeries,
1550+ Series: coretesting.FakeDefaultSeries,
1551 Jobs: []state.MachineJob{state.JobHostUnits},
1552 }
1553 container, err := s.State.AddMachineInsideMachine(template, s.machineId, instance.KVM)
1554
1555=== modified file 'worker/provisioner/lxc-broker_test.go'
1556--- worker/provisioner/lxc-broker_test.go 2014-04-02 11:35:49 +0000
1557+++ worker/provisioner/lxc-broker_test.go 2014-04-03 17:37:27 +0000
1558@@ -17,7 +17,6 @@
1559 "launchpad.net/juju-core/container/lxc/mock"
1560 lxctesting "launchpad.net/juju-core/container/lxc/testing"
1561 "launchpad.net/juju-core/environs"
1562- "launchpad.net/juju-core/environs/config"
1563 "launchpad.net/juju-core/errors"
1564 "launchpad.net/juju-core/instance"
1565 instancetest "launchpad.net/juju-core/instance/testing"
1566@@ -191,7 +190,7 @@
1567
1568 // The lxc provisioner actually needs the machine it is being created on
1569 // to be in state, in order to get the watcher.
1570- m, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits, state.JobManageEnviron)
1571+ m, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits, state.JobManageEnviron)
1572 c.Assert(err, gc.IsNil)
1573 err = m.SetAddresses(instance.NewAddress("0.1.2.3", instance.NetworkUnknown))
1574 c.Assert(err, gc.IsNil)
1575@@ -260,7 +259,7 @@
1576 defer stop(c, p)
1577
1578 // Check that an instance is not provisioned when the machine is created.
1579- _, err := s.State.AddMachine(config.DefaultSeries, state.JobHostUnits)
1580+ _, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
1581 c.Assert(err, gc.IsNil)
1582
1583 s.expectNoEvents(c)
1584@@ -277,7 +276,7 @@
1585
1586 func (s *lxcProvisionerSuite) addContainer(c *gc.C) *state.Machine {
1587 template := state.MachineTemplate{
1588- Series: config.DefaultSeries,
1589+ Series: coretesting.FakeDefaultSeries,
1590 Jobs: []state.MachineJob{state.JobHostUnits},
1591 }
1592 container, err := s.State.AddMachineInsideMachine(template, s.parentMachineId, instance.LXC)
1593
1594=== modified file 'worker/provisioner/provisioner_test.go'
1595--- worker/provisioner/provisioner_test.go 2014-04-02 07:50:15 +0000
1596+++ worker/provisioner/provisioner_test.go 2014-04-03 17:37:27 +0000
1597@@ -349,7 +349,7 @@
1598
1599 func (s *CommonProvisionerSuite) addMachineWithNetworks(includeNetworks, excludeNetworks []string) (*state.Machine, error) {
1600 return s.BackingState.AddOneMachine(state.MachineTemplate{
1601- Series: config.DefaultSeries,
1602+ Series: coretesting.FakeDefaultSeries,
1603 Jobs: []state.MachineJob{state.JobHostUnits},
1604 Constraints: s.defaultConstraints,
1605 IncludeNetworks: includeNetworks,
1606@@ -442,7 +442,7 @@
1607
1608 // make a container on the machine we just created
1609 template := state.MachineTemplate{
1610- Series: config.DefaultSeries,
1611+ Series: coretesting.FakeDefaultSeries,
1612 Jobs: []state.MachineJob{state.JobHostUnits},
1613 }
1614 container, err := s.State.AddMachineInsideMachine(template, m.Id(), instance.LXC)

Subscribers

People subscribed via source and target branches

to status/vote changes: