Merge lp:~axwalk/juju-core/null-provider-customsources into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 1920
Proposed branch: lp:~axwalk/juju-core/null-provider-customsources
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 300 lines (+98/-37)
8 files modified
cmd/juju/bootstrap.go (+2/-14)
environs/httpstorage/storage.go (+1/-0)
environs/interface.go (+10/-7)
environs/manual/bootstrap.go (+1/-6)
environs/manual/bootstrap_test.go (+4/-4)
environs/sshstorage/storage.go (+1/-1)
provider/null/environ.go (+36/-5)
provider/null/environ_test.go (+43/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/null-provider-customsources
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+187978@code.launchpad.net

Commit message

provider/null: define simplestreams tools source

This is somewhat involved, as there's some fixes
required (and one drive-by in sshstorage). The
change to use GetToolsSources is simple enough,
and matches what the local provider does.

The Environ override struct in cmd/juju/bootstrap.go
had to so that the optional Environ interfaces were
still implemented. Overriding like that loses the
additional interfaces.

To do the above, environs.BootstrapStorager has been
changed to have a method that tells the environ to
use bootstrap storage, rather than returning a
bootstrap storage. The null provider has changed to
match, of course, as well as cmd/juju.

https://codereview.appspot.com/14011046/

Description of the change

provider/null: define simplestreams tools source

This is somewhat involved, as there's some fixes
required (and one drive-by in sshstorage). The
change to use GetToolsSources is simple enough,
and matches what the local provider does.

The Environ override struct in cmd/juju/bootstrap.go
had to so that the optional Environ interfaces were
still implemented. Overriding like that loses the
additional interfaces.

To do the above, environs.BootstrapStorager has been
changed to have a method that tells the environ to
use bootstrap storage, rather than returning a
bootstrap storage. The null provider has changed to
match, of course, as well as cmd/juju.

https://codereview.appspot.com/14011046/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (9.2 KiB)

Reviewers: mp+187978_code.launchpad.net,

Message:
Please take a look.

Description:
provider/null: define simplestreams tools source

This is somewhat involved, as there's some fixes
required (and one drive-by in sshstorage). The
change to use GetToolsSources is simple enough,
and matches what the local provider does.

The Environ override struct in cmd/juju/bootstrap.go
had to so that the optional Environ interfaces were
still implemented. Overriding like that loses the
additional interfaces.

To do the above, environs.BootstrapStorager has been
changed to have a method that tells the environ to
use bootstrap storage, rather than returning a
bootstrap storage. The null provider has changed to
match, of course, as well as cmd/juju.

https://code.launchpad.net/~axwalk/juju-core/null-provider-customsources/+merge/187978

(do not edit description out of merge proposal)

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

Affected files (+66, -32 lines):
   A [revision details]
   M cmd/juju/bootstrap.go
   M environs/httpstorage/storage.go
   M environs/interface.go
   M environs/manual/bootstrap.go
   M environs/sshstorage/storage.go
   M provider/null/environ.go
   M provider/null/environ_test.go

Index: [revision details]
=== added file '[revision details]'
--- [revision details] 2012-01-01 00:00:00 +0000
+++ [revision details] 2012-01-01 00:00:00 +0000
@@ -0,0 +1,2 @@
+Old revision: tarmac-20130927021656-yolgm6vq7sjrlf6b
+New revision: <email address hidden>

Index: environs/interface.go
=== modified file 'environs/interface.go'
--- environs/interface.go 2013-09-26 09:18:36 +0000
+++ environs/interface.go 2013-09-27 05:19:30 +0000
@@ -69,15 +69,18 @@
   PublicStorage() storage.StorageReader
  }

-// BootstrapStorager is an interface that returns a environs.Storage that
may
-// be used before the bootstrap machine agent has been provisioned.
+// BootstrapStorager is an interface through which an Environ may be
+// instructed to use a special "bootstrap storage". Bootstrap storage
+// is one that may be used before the bootstrap machine agent has been
+// provisioned.
  //
-// This is useful for environments where the storage is managed by the
machine
-// agent once bootstrapped.
+// This is useful for environments where the storage is managed by the
+// machine agent once bootstrapped.
  type BootstrapStorager interface {
- // BootstrapStorager returns an environs.Storage that may be used while
- // bootstrapping a machine.
- BootstrapStorage() (storage.Storage, error)
+ // EnableBootstrapStorage enables or disables bootstrap storage,
+ // depending on the flag, and returns the previous state and an
+ // error if enablement failed.
+ EnableBootstrapStorage(enable bool) (wasEnabled bool, err error)
  }

  // ConfigGetter implements access to an environments configuration.

Index: cmd/juju/bootstrap.go
=== modified file 'cmd/juju/bootstrap.go'
--- cmd/juju/bootstrap.go 2013-09-25 06:22:54 +0000
+++ cmd/juju/bootstrap.go 2013-09-27 05:19:30 +0000
@@ -18,7 +18,6 @@
   "launchpad.net/juju-core/environs/bootstrap"
   "launchpad.net/juju-core/environs/config"
   "launchpad.net/juju-core/envir...

Read more...

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

Good direction, but a few notes:

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go
File provider/null/environ.go (left):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#oldcode43
provider/null/environ.go:43: }
It's quite nice to do "var _" type asserts by the definition of the
type, given that we seem to be growing optional interfaces at a
reasonable rate. Compiled documentation, if you like, that stops you
having to read all the methods.

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go
File provider/null/environ.go (right):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#newcode126
provider/null/environ.go:126: func (e *nullEnviron)
EnableBootstrapStorage(enable bool) (wasEnabled bool, err error) {
We should be locking here, shouldn't we? Even if it makes no practical
difference today, environs should be goroutine-safe.

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#newcode153
provider/null/environ.go:153:
storage.NewStorageSimpleStreamsDataSource(e.Storage(),
storage.BaseToolsPath),
This is starting to look awfully familiar. No action obviously right
today, but it feels like a bit of a smell.

https://codereview.appspot.com/14011046/diff/1/provider/null/environ_test.go
File provider/null/environ_test.go (right):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ_test.go#newcode96
provider/null/environ_test.go:96: }
I don't see any tests for EnableBootstrapStorage.

https://codereview.appspot.com/14011046/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

I never did like the optional Environ interfaces, and now I have another
reason - they break encapsulation.
My preferred solution here would be to remove all those interfaces so we
have One True Environ Interface, and leave the code as it was before.

Perhaps there's another way of solving this issue without adding the
fairly icky EnableBootstrapStorage. Could we make the provider use its
bootstrap storage automatically if it detects that it has not been
bootstrapped? Then we could lose all the externally visible
BootstrapStorage stuff, perhaps.

Looks reasonable in general; some comments and suggestions below (the
sshstorage ones should be addressed in a separate CL).

https://codereview.appspot.com/14011046/diff/1/environs/manual/bootstrap.go
File environs/manual/bootstrap.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/manual/bootstrap.go#newcode67
environs/manual/bootstrap.go:67: bootstrapStorage :=
args.Environ.Storage()
move this down to just before it's used, perhaps?

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go
File environs/sshstorage/storage.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode263
environs/sshstorage/storage.go:263: if _, err := io.ReadFull(r, buf);
err != nil {
I'm not very happy about this. Why are we reading in
the entire input when we can stream it?

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode267
environs/sshstorage/storage.go:267: tmpdir := utils.ShQuote(s.tmpdir)
Given that we're using these paths unquoted in many places,
I'd like to see a sanity check that they don't contain
any dubious characters.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode272
environs/sshstorage/storage.go:272: "export TMPDIR=%s &&
TMPFILE=`mktemp` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
What's TMPDIR used for?

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go
File provider/null/environ.go (right):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#newcode126
provider/null/environ.go:126: func (e *nullEnviron)
EnableBootstrapStorage(enable bool) (wasEnabled bool, err error) {
On 2013/09/27 09:47:33, fwereade wrote:
> We should be locking here, shouldn't we? Even if it makes no practical
> difference today, environs should be goroutine-safe.

+1

But also, why even have the option to disable bootstrap storage?
Will we ever need to do that? This method is used in one quite
specialised way - I don't see that we need to clutter its interface
too much. I'd lose the enable arg and the wasEnabled return value.

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#newcode133
provider/null/environ.go:133: return false, errors.New("bootstrap
storage already disabled")
Can't we just make this method idempotent?

https://codereview.appspot.com/14011046/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (4.7 KiB)

Thanks for the reviews.

rog: agreed, it would be *much* nicer if this could be determined
automatically. I don't have a solution to that, though.

I'll send another CL to fix some sshstorage things.

https://codereview.appspot.com/14011046/diff/1/environs/manual/bootstrap.go
File environs/manual/bootstrap.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/manual/bootstrap.go#newcode67
environs/manual/bootstrap.go:67: bootstrapStorage :=
args.Environ.Storage()
On 2013/09/27 10:35:25, rog wrote:
> move this down to just before it's used, perhaps?

Done.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go
File environs/sshstorage/storage.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode263
environs/sshstorage/storage.go:263: if _, err := io.ReadFull(r, buf);
err != nil {
On 2013/09/27 10:35:25, rog wrote:
> I'm not very happy about this. Why are we reading in
> the entire input when we can stream it?

Because we can't? There are multiple input streams to a long-lived bash
process, where the data input is interleaved with commands. If there's a
better way to do it, I'm happy to make changes.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode267
environs/sshstorage/storage.go:267: tmpdir := utils.ShQuote(s.tmpdir)
On 2013/09/27 10:35:25, rog wrote:
> Given that we're using these paths unquoted in many places,
> I'd like to see a sanity check that they don't contain
> any dubious characters.

Where are we using them unquoted? I found one occurrence of s.remotepath
after adding a test (good call), but that doesn't equate to "in many
places". AFAICS, all other cases are ShQuoted.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode272
environs/sshstorage/storage.go:272: "export TMPDIR=%s &&
TMPFILE=`mktemp` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
On 2013/09/27 10:35:25, rog wrote:
> What's TMPDIR used for?

Just to influence mktemp. It was previously set elsewhere, and I just
failed to notice the obvious simplification. I'll update this to call
`mktemp --tmpdir=...`

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go
File provider/null/environ.go (left):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#oldcode43
provider/null/environ.go:43: }
On 2013/09/27 09:47:33, fwereade wrote:
> It's quite nice to do "var _" type asserts by the definition of the
type, given
> that we seem to be growing optional interfaces at a reasonable rate.
Compiled
> documentation, if you like, that stops you having to read all the
methods.

Yeah, I was aware of the convention. As an experiment, I put the
assertion in a test, as it feels wrong to impose a package dependency
just for the assertion. Binary size won't change, but compilation speed
will take a hit.

But, point taken about documentation. I've added in here, and dropped
the assertion test. A short lived experiment ;)

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go
File provider/null/environ.go (right):

https://codereview.appspot.com/14011046/diff/1/provider/null/environ.go#...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Roger Peppe (rogpeppe) wrote :
Download full text (3.5 KiB)

> rog: agreed, it would be *much* nicer if this could be determined
automatically.
> I don't have a solution to that, though.

One possibility might be to make the null provider storage fall back to
bootstrap storage if the regular storage is not available. We could make
the null provider Prepare ssh to the bootstrap host and start a stub
server (in some portable language, so we don't need to push tools
across) that simply returns an error, say "awaiting bootstrap" to any
request; then at bootstrap time we'd replace that by the real storage
server.

This would all be more simple, of course, if we stipulated that the null
provider always bootstrapped using the current machine for the bootstrap
machine. I imagine that's off the cards though.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go
File environs/sshstorage/storage.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode263
environs/sshstorage/storage.go:263: if _, err := io.ReadFull(r, buf);
err != nil {
On 2013/09/29 16:05:25, axw1 wrote:
> On 2013/09/27 10:35:25, rog wrote:
> > I'm not very happy about this. Why are we reading in
> > the entire input when we can stream it?

> Because we can't? There are multiple input streams to a long-lived
bash process,
> where the data input is interleaved with commands. If there's a better
way to do
> it, I'm happy to make changes.

I'm probably missing something, but it seems to me that
the buffer is passed to s.run, then encoded to base64
(line 145), the result of which is used for its length (line 148),
and then written with Write (line 157).

It seems to me that you could pass in the reader and length directly,
create a base64.Encoder use base64.EncodedLen and io.Copy to
the encoder to stream the input.

The down side is if there happen to be less bytes than
advertised in the stream, your remote script will get out of sync.
You could use a more robust input reader than
"head -q -c 999". How about using a here document instead?
"base64 -d << '@EOF' | (%s)"

Then you could ignore the expected length and stream
the input, relying on the shell to find the end of it.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode267
environs/sshstorage/storage.go:267: tmpdir := utils.ShQuote(s.tmpdir)
On 2013/09/29 16:05:25, axw1 wrote:
> On 2013/09/27 10:35:25, rog wrote:
> > Given that we're using these paths unquoted in many places,
> > I'd like to see a sanity check that they don't contain
> > any dubious characters.

> Where are we using them unquoted? I found one occurrence of
s.remotepath after
> adding a test (good call), but that doesn't equate to "in many
places". AFAICS,
> all other cases are ShQuoted.

I'm not sure what I was thinking when I wrote that comment :)

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode272
environs/sshstorage/storage.go:272: "export TMPDIR=%s &&
TMPFILE=`mktemp` && ((%s && mv $TMPFILE %s) || rm -f $TMPFILE)",
On 2013/09/29 16:05:25, axw1 wrote:
> On 2013/09/27 10:35:25, rog wrote:
> > What's TMPDIR used for?

> Just to influence mktemp. It was previously set elsewhere, and I just...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (4.5 KiB)

On 2013/09/30 13:31:08, rog wrote:
> > rog: agreed, it would be *much* nicer if this could be determined
> automatically.
> > I don't have a solution to that, though.

> One possibility might be to make the null provider storage fall back
to
> bootstrap storage if the regular storage is not available. We could
make the
> null provider Prepare ssh to the bootstrap host and start a stub
server (in some
> portable language, so we don't need to push tools across) that simply
returns an
> error, say "awaiting bootstrap" to any request; then at bootstrap time
we'd
> replace that by the real storage server.

I'll have to have a think about that some. I don't really like the idea
of requiring additional VMs/interpreters/etc. to support this, as we may
want to expand to other target OSs. Anyway, can we deal with this
outside of this CL? I can open a tech-debt bug to investigate it after
1.16.0 is out.

> This would all be more simple, of course, if we stipulated that the
null
> provider always bootstrapped using the current machine for the
bootstrap
> machine. I imagine that's off the cards though.

I think so. Having to connect to the bootstrap machine is (if only a
little) more onerous than necessary.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go
> File environs/sshstorage/storage.go (right):

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode263
> environs/sshstorage/storage.go:263: if _, err := io.ReadFull(r, buf);
err != nil
> {
> On 2013/09/29 16:05:25, axw1 wrote:
> > On 2013/09/27 10:35:25, rog wrote:
> > > I'm not very happy about this. Why are we reading in
> > > the entire input when we can stream it?
> >
> > Because we can't? There are multiple input streams to a long-lived
bash
> process,
> > where the data input is interleaved with commands. If there's a
better way to
> do
> > it, I'm happy to make changes.

> I'm probably missing something, but it seems to me that
> the buffer is passed to s.run, then encoded to base64
> (line 145), the result of which is used for its length (line 148),
> and then written with Write (line 157).

Now we're even; I don't know what *I* was thinking when I wrote what I
did ;)
Yes, I think you're right and we could just Write with a chained reader.
I will handle this in an update to
https://codereview.appspot.com/14102043/

> It seems to me that you could pass in the reader and length directly,
> create a base64.Encoder use base64.EncodedLen and io.Copy to
> the encoder to stream the input.

> The down side is if there happen to be less bytes than
> advertised in the stream, your remote script will get out of sync.
> You could use a more robust input reader than
> "head -q -c 999". How about using a here document instead?
> "base64 -d << '@EOF' | (%s)"

> Then you could ignore the expected length and stream
> the input, relying on the shell to find the end of it.

This is pretty similar to how I did it originally, though the input was
in a here-doc on the command line ;) I'm not familiar with that @EOF
syntax - will have to read up.

https://codereview.appspot.com/14011046/diff/1/environs/sshstorage/storage.go#newcode267
> environs/sshst...

Read more...

Revision history for this message
Mark Ramm (mark-ramm) wrote :

> I'll have to have a think about that some. I don't really like the idea
> of requiring additional VMs/interpreters/etc. to support this, as we may
> want to expand to other target OSs. Anyway, can we deal with this
> outside of this CL? I can open a tech-debt bug to investigate it after
> 1.16.0 is out.

I don't think supporting bootstrap nodes running anything other than Ubuntu is an important feature at this time. Of course someday that may not be true -- but unlike unit machines, I don't see any compelling market forces for being OS agnostic on the bootstrap node.

That said, I'm generally a proponent of getting this landed, and creating a follow up task to handle any kind of "one true environment interface" style cleanup later. Just my opinion, and I will bow to the feelings of the group.

Side note -- have we considered using MongoDB and GridFS to provide a standard fallback storage provider everywhere that there's not "object storage" in the cloud already?

Revision history for this message
Andrew Wilkins (axwalk) wrote :

> I don't think supporting bootstrap nodes running anything other than Ubuntu is
> an important feature at this time. Of course someday that may not be true --
> but unlike unit machines, I don't see any compelling market forces for being
> OS agnostic on the bootstrap node.

Fair enough, I'll bear that in mind.

> That said, I'm generally a proponent of getting this landed, and creating a
> follow up task to handle any kind of "one true environment interface" style
> cleanup later. Just my opinion, and I will bow to the feelings of the group.
>
> Side note -- have we considered using MongoDB and GridFS to provide a standard
> fallback storage provider everywhere that there's not "object storage" in the
> cloud already?

I haven't, though it's known that the current storage solution is a bit weak, and won't stand up in an HA environment. This is just a first cut - we can do better. GridFS sounds like a pretty reasonable option.

Revision history for this message
Tim Penhey (thumper) wrote :

LGTM with one comment on base test suite.

https://codereview.appspot.com/14011046/diff/11001/provider/null/environ_test.go
File provider/null/environ_test.go (right):

https://codereview.appspot.com/14011046/diff/11001/provider/null/environ_test.go#newcode24
provider/null/environ_test.go:24: testbase.CleanupSuite
I'd recommend using the LoggingSuite just because it catches the logging
that may happen anywhere, and puts it in the test log, so it is visible
in the test failures.

https://codereview.appspot.com/14011046/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

Thanks for the review.

https://codereview.appspot.com/14011046/diff/11001/provider/null/environ_test.go
File provider/null/environ_test.go (right):

https://codereview.appspot.com/14011046/diff/11001/provider/null/environ_test.go#newcode24
provider/null/environ_test.go:24: testbase.CleanupSuite
On 2013/10/02 04:26:49, thumper wrote:
> I'd recommend using the LoggingSuite just because it catches the
logging that
> may happen anywhere, and puts it in the test log, so it is visible in
the test
> failures.

Done.

https://codereview.appspot.com/14011046/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/juju/bootstrap.go'
2--- cmd/juju/bootstrap.go 2013-09-30 16:23:40 +0000
3+++ cmd/juju/bootstrap.go 2013-10-02 05:19:29 +0000
4@@ -18,7 +18,6 @@
5 "launchpad.net/juju-core/environs/bootstrap"
6 "launchpad.net/juju-core/environs/config"
7 "launchpad.net/juju-core/environs/configstore"
8- "launchpad.net/juju-core/environs/storage"
9 "launchpad.net/juju-core/environs/sync"
10 envtools "launchpad.net/juju-core/environs/tools"
11 "launchpad.net/juju-core/errors"
12@@ -75,11 +74,9 @@
13 // If the environment has a special bootstrap Storage, use it wherever
14 // we'd otherwise use environ.Storage.
15 if bs, ok := environ.(environs.BootstrapStorager); ok {
16- bootstrapStorage, err := bs.BootstrapStorage()
17- if err != nil {
18- return fmt.Errorf("failed to acquire bootstrap storage: %v", err)
19+ if err := bs.EnableBootstrapStorage(); err != nil {
20+ return fmt.Errorf("failed to enable bootstrap storage: %v", err)
21 }
22- environ = &bootstrapStorageEnviron{environ, bootstrapStorage}
23 }
24 // TODO (wallyworld): 2013-09-20 bug 1227931
25 // We can set a custom tools data source instead of doing an
26@@ -230,12 +227,3 @@
27 }
28 return unique.Values()
29 }
30-
31-type bootstrapStorageEnviron struct {
32- environs.Environ
33- bootstrapStorage storage.Storage
34-}
35-
36-func (b *bootstrapStorageEnviron) Storage() storage.Storage {
37- return b.bootstrapStorage
38-}
39
40=== modified file 'environs/httpstorage/storage.go'
41--- environs/httpstorage/storage.go 2013-09-23 10:05:53 +0000
42+++ environs/httpstorage/storage.go 2013-10-02 05:19:29 +0000
43@@ -141,6 +141,7 @@
44 if err != nil {
45 return err
46 }
47+
48 // Here we wrap up the reader. For some freaky unexplainable reason, the
49 // http library will call Close on the reader if it has a Close method
50 // available. Since we sometimes reuse the reader, especially when
51
52=== modified file 'environs/interface.go'
53--- environs/interface.go 2013-09-27 08:14:36 +0000
54+++ environs/interface.go 2013-10-02 05:19:29 +0000
55@@ -64,15 +64,18 @@
56 PublicStorage() storage.StorageReader
57 }
58
59-// BootstrapStorager is an interface that returns a environs.Storage that may
60-// be used before the bootstrap machine agent has been provisioned.
61+// BootstrapStorager is an interface through which an Environ may be
62+// instructed to use a special "bootstrap storage". Bootstrap storage
63+// is one that may be used before the bootstrap machine agent has been
64+// provisioned.
65 //
66-// This is useful for environments where the storage is managed by the machine
67-// agent once bootstrapped.
68+// This is useful for environments where the storage is managed by the
69+// machine agent once bootstrapped.
70 type BootstrapStorager interface {
71- // BootstrapStorager returns an environs.Storage that may be used while
72- // bootstrapping a machine.
73- BootstrapStorage() (storage.Storage, error)
74+ // EnableBootstrapStorage enables bootstrap storage, returning an
75+ // error if enablement failed. If nil is returned, then calling
76+ // this again will have no effect and will return nil.
77+ EnableBootstrapStorage() error
78 }
79
80 // ConfigGetter implements access to an environments configuration.
81
82=== modified file 'environs/manual/bootstrap.go'
83--- environs/manual/bootstrap.go 2013-09-30 19:40:06 +0000
84+++ environs/manual/bootstrap.go 2013-10-02 05:19:29 +0000
85@@ -24,7 +24,6 @@
86 // manages its own local storage.
87 type LocalStorageEnviron interface {
88 environs.Environ
89- environs.BootstrapStorager
90 localstorage.LocalStorageConfig
91 }
92
93@@ -65,11 +64,6 @@
94 return ErrProvisioned
95 }
96
97- bootstrapStorage, err := args.Environ.BootstrapStorage()
98- if err != nil {
99- return err
100- }
101-
102 hc, series, err := detectSeriesAndHardwareCharacteristics(args.Host)
103 if err != nil {
104 return fmt.Errorf("error detecting hardware characteristics: %v", err)
105@@ -87,6 +81,7 @@
106
107 // Store the state file. If provisioning fails, we'll remove the file.
108 logger.Infof("Saving bootstrap state file to bootstrap storage")
109+ bootstrapStorage := args.Environ.Storage()
110 err = common.SaveState(
111 bootstrapStorage,
112 &common.BootstrapState{
113
114=== modified file 'environs/manual/bootstrap_test.go'
115--- environs/manual/bootstrap_test.go 2013-09-30 19:40:06 +0000
116+++ environs/manual/bootstrap_test.go 2013-10-02 05:19:29 +0000
117@@ -33,8 +33,8 @@
118 sharedStorageDir string
119 }
120
121-func (e *localStorageEnviron) BootstrapStorage() (storage.Storage, error) {
122- return e.storage, nil
123+func (e *localStorageEnviron) Storage() storage.Storage {
124+ return e.storage
125 }
126
127 func (e *localStorageEnviron) StorageAddr() string {
128@@ -86,7 +86,7 @@
129 err := Bootstrap(args)
130 c.Assert(err, gc.IsNil)
131
132- bootstrapState, err := common.LoadState(s.env.storage)
133+ bootstrapState, err := common.LoadState(s.env.Storage())
134 c.Assert(err, gc.IsNil)
135 c.Assert(
136 bootstrapState.StateInstances,
137@@ -117,7 +117,7 @@
138
139 // Since the script failed, the state file should have been
140 // removed from storage.
141- _, err = common.LoadState(s.env.storage)
142+ _, err = common.LoadState(s.env.Storage())
143 c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
144 }
145
146
147=== modified file 'environs/sshstorage/storage.go'
148--- environs/sshstorage/storage.go 2013-09-24 01:55:30 +0000
149+++ environs/sshstorage/storage.go 2013-10-02 05:19:29 +0000
150@@ -260,7 +260,7 @@
151 return err
152 }
153 buf := make([]byte, length)
154- if _, err := r.Read(buf); err != nil {
155+ if _, err := io.ReadFull(r, buf); err != nil {
156 return err
157 }
158 path = utils.ShQuote(path)
159
160=== modified file 'provider/null/environ.go'
161--- provider/null/environ.go 2013-09-30 19:40:06 +0000
162+++ provider/null/environ.go 2013-10-02 05:19:29 +0000
163@@ -14,8 +14,10 @@
164 "launchpad.net/juju-core/environs/config"
165 "launchpad.net/juju-core/environs/httpstorage"
166 "launchpad.net/juju-core/environs/manual"
167+ "launchpad.net/juju-core/environs/simplestreams"
168 "launchpad.net/juju-core/environs/sshstorage"
169 "launchpad.net/juju-core/environs/storage"
170+ envtools "launchpad.net/juju-core/environs/tools"
171 "launchpad.net/juju-core/instance"
172 "launchpad.net/juju-core/provider/common"
173 "launchpad.net/juju-core/state"
174@@ -38,10 +40,15 @@
175 )
176
177 type nullEnviron struct {
178- cfg *environConfig
179- cfgmutex sync.Mutex
180+ cfg *environConfig
181+ cfgmutex sync.Mutex
182+ bootstrapStorage *sshstorage.SSHStorage
183+ bootstrapStorageMutex sync.Mutex
184 }
185
186+var _ environs.BootstrapStorager = (*nullEnviron)(nil)
187+var _ envtools.SupportsCustomSources = (*nullEnviron)(nil)
188+
189 var errNoStartInstance = errors.New("null provider cannot start instances")
190 var errNoStopInstance = errors.New("null provider cannot stop instances")
191
192@@ -120,15 +127,39 @@
193 return instances, err
194 }
195
196-// Implements environs/bootstrap.BootstrapStorage.
197-func (e *nullEnviron) BootstrapStorage() (storage.Storage, error) {
198+// Implements environs.BootstrapStorager.
199+func (e *nullEnviron) EnableBootstrapStorage() error {
200+ e.bootstrapStorageMutex.Lock()
201+ defer e.bootstrapStorageMutex.Unlock()
202+ if e.bootstrapStorage != nil {
203+ return nil
204+ }
205 cfg := e.envConfig()
206 storageDir := e.StorageDir()
207 storageTmpdir := path.Join(dataDir, storageTmpSubdir)
208- return sshstorage.NewSSHStorage(cfg.sshHost(), storageDir, storageTmpdir)
209+ bootstrapStorage, err := sshstorage.NewSSHStorage(cfg.sshHost(), storageDir, storageTmpdir)
210+ if err != nil {
211+ return err
212+ }
213+ e.bootstrapStorage = bootstrapStorage
214+ return nil
215+}
216+
217+// GetToolsSources returns a list of sources which are
218+// used to search for simplestreams tools metadata.
219+func (e *nullEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
220+ // Add the simplestreams source off private storage.
221+ return []simplestreams.DataSource{
222+ storage.NewStorageSimpleStreamsDataSource(e.Storage(), storage.BaseToolsPath),
223+ }, nil
224 }
225
226 func (e *nullEnviron) Storage() storage.Storage {
227+ e.bootstrapStorageMutex.Lock()
228+ defer e.bootstrapStorageMutex.Unlock()
229+ if e.bootstrapStorage != nil {
230+ return e.bootstrapStorage
231+ }
232 return httpstorage.Client(e.envConfig().storageAddr())
233 }
234
235
236=== modified file 'provider/null/environ_test.go'
237--- provider/null/environ_test.go 2013-09-26 09:02:09 +0000
238+++ provider/null/environ_test.go 2013-10-02 05:19:29 +0000
239@@ -4,14 +4,24 @@
240 package null
241
242 import (
243+ "io/ioutil"
244+ "os"
245+ "path/filepath"
246+ "strings"
247+
248 gc "launchpad.net/gocheck"
249
250 "launchpad.net/juju-core/environs"
251 "launchpad.net/juju-core/environs/manual"
252+ "launchpad.net/juju-core/environs/sshstorage"
253+ "launchpad.net/juju-core/environs/tools"
254 "launchpad.net/juju-core/instance"
255+ jc "launchpad.net/juju-core/testing/checkers"
256+ "launchpad.net/juju-core/testing/testbase"
257 )
258
259 type environSuite struct {
260+ testbase.LoggingSuite
261 env *nullEnviron
262 }
263
264@@ -79,3 +89,36 @@
265 c.Assert(s.env.SharedStorageAddr(), gc.Equals, "")
266 c.Assert(s.env.SharedStorageDir(), gc.Equals, "")
267 }
268+
269+func (s *environSuite) TestEnvironSupportsCustomSources(c *gc.C) {
270+ sources, err := tools.GetMetadataSources(s.env)
271+ c.Assert(err, gc.IsNil)
272+ c.Assert(len(sources), gc.Equals, 2)
273+ url, err := sources[0].URL("")
274+ c.Assert(err, gc.IsNil)
275+ c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
276+}
277+
278+func (s *environSuite) TestEnvironBootstrapStorager(c *gc.C) {
279+ const sshScript = "#!/bin/sh\necho JUJU-RC: $RC"
280+ bin := c.MkDir()
281+ ssh := filepath.Join(bin, "ssh")
282+ err := ioutil.WriteFile(ssh, []byte(sshScript), 0755)
283+ c.Assert(err, gc.IsNil)
284+ s.PatchEnvironment("PATH", bin+":"+os.Getenv("PATH"))
285+
286+ s.PatchEnvironment("RC", "99") // simulate ssh failure
287+ err = s.env.EnableBootstrapStorage()
288+ c.Assert(err, gc.ErrorMatches, "exit code 99")
289+ c.Assert(s.env.Storage(), gc.Not(gc.FitsTypeOf), new(sshstorage.SSHStorage))
290+
291+ s.PatchEnvironment("RC", "0")
292+ err = s.env.EnableBootstrapStorage()
293+ c.Assert(err, gc.IsNil)
294+ c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))
295+
296+ // Check idempotency
297+ err = s.env.EnableBootstrapStorage()
298+ c.Assert(err, gc.IsNil)
299+ c.Assert(s.env.Storage(), gc.FitsTypeOf, new(sshstorage.SSHStorage))
300+}

Subscribers

People subscribed via source and target branches

to status/vote changes: