Merge lp:~thumper/juju-core/lxc-container into lp:~go-bot/juju-core/trunk
- lxc-container
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1328 |
Proposed branch: | lp:~thumper/juju-core/lxc-container |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
740 lines (+689/-2) 7 files modified
container/lxc/export_test.go (+19/-0) container/lxc/instance.go (+46/-0) container/lxc/lxc.go (+283/-0) container/lxc/lxc_test.go (+167/-0) container/lxc/mock-lxc_test.go (+147/-0) environs/cloudinit/cloudinit.go (+2/-2) testing/instance.go (+25/-0) |
To merge this branch: | bzr merge lp:~thumper/juju-core/lxc-container |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email:
|
Commit message
Implement lxc containers
This branch adds a containers interface, and an lxc
implementation of them. A Container is-a Instance, and
uses the interface embedding to copy across the Instance
methods.
At this stage, the containers do nothing special with
networking, and just use the default set up.
/var/log/juju is mounted from the host to the inside of
the container for ease of access to log files.
Some new checks have been added, along with some testing
functions for files.
Description of the change
Implement lxc containers
This branch adds a containers interface, and an lxc
implementation of them. A Container is-a Instance, and
uses the interface embedding to copy across the Instance
methods.
At this stage, the containers do nothing special with
networking, and just use the default set up.
/var/log/juju is mounted from the host to the inside of
the container for ease of access to log files.
Some new checks have been added, along with some testing
functions for files.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ian Booth (wallyworld) wrote : | # |
Looks nice but I have some questions.
https:/
File container/
https:/
container/
It could just be my warped thinking, but I'm conceptually having an
issue with a Create() method being on the Container itself. Shouldn't a
foo factory create a foo? And then once created, the container returned
by the factory is started/stopped etc.
If the above is valid, perhaps a comment explaining the method would
help. All exported methods would ideally be commented, especially ones
like this that represent the API contract.
https:/
File container/
https:/
container/
Exported interfaces/structs should have some comments for doc purposes
https:/
container/
I don't see the need for the lxc attribute here. I think the container
factory can just be embedded like so:
type lxcFactory struct {
golxc.
}
Then the code that uses it will be:
factory.New(...)
instead of factory.
and the former reads much nicer IMHO. The extra lxc bit just confuses
it.
https:/
container/
Is there a bug for this behaviour? If so reference it here. If not, do
we know why it's expected behaviour? Can we put the definitive reason
instead of speculation?
https:/
container/
string {
Does this need to be exported? I would think not.
https:/
container/
(string, error) {
Does this need to be exported? I would think not.
https:/
container/
Does this need to be exported? I would think not.
https:/
File container/
https:/
container/
Where are the tests for Start() and Stop()?
https:/
container/
Is there anything more that we can check besides just err is nil? I'd
ideally like to see checks using the params passed to Create. Can we
test that the cloud init stuff/user data is correctly generated etc.
Otherwise I can't see where that stuff is tested.
Typically in this case, the pattern in use it to call the method
assertContainer
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Frank Mueller (themue) wrote : | # |
Some more comments plus what written in
https:/
https:/
File container/
https:/
container/
On 2013/06/18 05:29:55, wallyworld wrote:
> It could just be my warped thinking, but I'm conceptually having an
issue with a
> Create() method being on the Container itself. Shouldn't a foo factory
create a
> foo? And then once created, the container returned by the factory is
> started/stopped etc.
> If the above is valid, perhaps a comment explaining the method would
help. All
> exported methods would ideally be commented, especially ones like this
that
> represent the API contract.
Yes, looks like a problem with the naming. I would like
- ContainerManager as the interface to create or open containers
- lxc.ContainerMa
- Container representing container instances to start/stop/destroy them
(again an interface)
- lxc.Container/
https:/
File container/
https:/
container/
On 2013/06/18 05:29:55, wallyworld wrote:
> Exported interfaces/structs should have some comments for doc purposes
+1
https:/
File testing/
https:/
testing/
On 2013/06/18 05:29:55, wallyworld wrote:
> I know we could do Not(IsTrue), but how much beer for IsFalse as well?
+1, but as already mentioned, extra CL for testing extensions please.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
https:/
File container/
https:/
container/
On 2013/06/18 12:47:04, mue wrote:
> On 2013/06/18 05:29:55, wallyworld wrote:
> > It could just be my warped thinking, but I'm conceptually having an
issue with
> a
> > Create() method being on the Container itself. Shouldn't a foo
factory create
> a
> > foo? And then once created, the container returned by the factory is
> > started/stopped etc.
> >
> > If the above is valid, perhaps a comment explaining the method would
help. All
> > exported methods would ideally be commented, especially ones like
this that
> > represent the API contract.
> Yes, looks like a problem with the naming. I would like
> - ContainerManager as the interface to create or open containers
> - lxc.ContainerMa
interface
> - Container representing container instances to start/stop/destroy
them (again
> an interface)
> - lxc.Container/
Actually, after talking with william, I think this entire interface is
going to die :-)
https:/
File container/
https:/
container/
On 2013/06/18 12:47:04, mue wrote:
> On 2013/06/18 05:29:55, wallyworld wrote:
> > Exported interfaces/structs should have some comments for doc
purposes
> +1
Yeah, agreed. I have a feeling that this is going to change somewhat :)
https:/
container/
On 2013/06/18 05:29:55, wallyworld wrote:
> I don't see the need for the lxc attribute here. I think the container
factory
> can just be embedded like so:
> type lxcFactory struct {
> golxc.Container
> }
> Then the code that uses it will be:
> factory.New(...)
> instead of factory.
> and the former reads much nicer IMHO. The extra lxc bit just confuses
it.
*nod*
https:/
container/
On 2013/06/18 05:29:55, wallyworld wrote:
> Is there a bug for this behaviour? If so reference it here. If not, do
we know
> why it's expected behaviour? Can we put the definitive reason instead
of
> speculation?
It is due to the definition of the rootfs, as serge mentioned in an
email. Not quite sure how to fix it, or if it is a real problem. But I
can extend the comment.
https:/
container/
string {
On 2013/06/18 05:29:55, wallyworld wrote:
> Does this need to be exported? I would think not.
It needs to be exported to test it. And since no-one should really have
access to it, it *shouldn't* matter.
https:/
File te...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
On 2013/06/19 04:01:42, thumper wrote:
> Please take a look.
Now also contains changes from https:/
as I couldn't create the dependent branch after it already exists (LP
can...)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ian Booth (wallyworld) wrote : | # |
In summary, looking pretty good but I have a couple of concerns.
https:/
File container/
https:/
container/
trunk now also defines a Metadata() method but this is being removed
asap. But for now, lxcInstance does not implement Instance
https:/
File container/
https:/
container/
object that can start and stop lxc
an manager -> a manager
https:/
container/
This confuses me. Can we change this to
container := manager.Get(name)
The above is much more aligned with expected semantics of how New() and
Get() would behave. The reader would see the above and reasonably expect
a new container instance to be created which doesn't make sense.
https:/
container/
can we use the ContainerType const LXC here please
https:/
File container/
https:/
container/
filepath.
I do think we need to check the contents of the cloud init file like we
do in other cloud init related tests
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
On 2013/06/20 02:01:23, wallyworld wrote:
> In summary, looking pretty good but I have a couple of concerns.
https:/
> File container/
https:/
> container/
> trunk now also defines a Metadata() method but this is being removed
asap. But
> for now, lxcInstance does not implement Instance
Why is it being removed? And what is replacing it?
https:/
> File container/
https:/
> container/
object that
> can start and stop lxc
> an manager -> a manager
Fixed.
https:/
> container/
> This confuses me. Can we change this to
> container := manager.Get(name)
This is in golxc and out of the scope of this branch. The go idiom is
that New will
return you a well defined object, and that it does. It does not however
actually
create you an lxc container.
https:/
> container/
> can we use the ContainerType const LXC here please
https:/
> File container/
https:/
> container/
> filepath.
> I do think we need to check the contents of the cloud init file like
we do in
> other cloud init related tests
The contents of cloud-init is covered by the cloud-init tests. I didn't
feel any
need to do additional checks here over and above the actual generation
of the file.
This isn't really a cloud-init test, just a test that a file is written.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
Please take a look.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ian Booth (wallyworld) wrote : | # |
On 2013/06/20 22:20:24, thumper wrote:
> On 2013/06/20 02:01:23, wallyworld wrote:
> > In summary, looking pretty good but I have a couple of concerns.
> >
> >
https:/
> > File container/
> >
> >
https:/
> > container/
> > trunk now also defines a Metadata() method but this is being removed
asap. But
> > for now, lxcInstance does not implement Instance
> Why is it being removed? And what is replacing it?
William didn't like it because it didn't always provide access to the
metadata. For Instance objects returned from startInstance, the metadata
is available (and this is the case where the metadata is actually
required). However, Instance objects returned from querying the running
instances eg via AllInstances() did not have the metadata available
since an extra RPC call for each distinct instancetype/
required. And since the metadata is not needed here, I deferred having
to maike the API calls. But William didn't like the inconsistency. So
what is happening instead is that startInstance returns the Instance and
Metadata as separate result objects.
> >
https:/
> > container/
> > This confuses me. Can we change this to
> > container := manager.Get(name)
> This is in golxc and out of the scope of this branch. The go idiom is
that New
> will
> return you a well defined object, and that it does. It does not
however
> actually
> create you an lxc container.
Which is very, very, very unintuitive :-(
Can we at least add a comment so the next person reading the code
understands what is going on.
> >
https:/
> > container/
> > can we use the ContainerType const LXC here please
> >
> >
https:/
> > File container/
> >
> >
https:/
> > container/
> > filepath.
> > I do think we need to check the contents of the cloud init file like
we do in
> > other cloud init related tests
> The contents of cloud-init is covered by the cloud-init tests. I
didn't feel any
> need to do additional checks here over and above the actual generation
of the
> file.
> This isn't really a cloud-init test, just a test that a file is
written.
Understood. But there's a big assumption that the correct calls to the
cloud init apis have been made. What's to say that an empty or corrupt
file hasn't been written. Can we just add a sanity check that a key line
or two exists in the file contents to ensure the correct parameters have
been passed to cloudinit.
etc?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Roger Peppe (rogpeppe) wrote : | # |
looks good, with some trivial issues and one significant concern around
the uniqueDirectory code.
https:/
File container/
https:/
container/
On 2013/06/18 23:50:16, thumper wrote:
> On 2013/06/18 05:29:55, wallyworld wrote:
> > I don't see the need for the lxc attribute here. I think the
container factory
> > can just be embedded like so:
> >
> > type lxcFactory struct {
> > golxc.Container
> > }
> >
> > Then the code that uses it will be:
> >
> > factory.New(...)
> >
> > instead of factory.
> >
> > and the former reads much nicer IMHO. The extra lxc bit just
confuses it.
> *nod*
depends if you actually want to export New as a method on the returned
ContainerFactory. i'd suggest you probably don't, and that using a field
to hide it is actually appropriate, but given that it can only be got to
by dynamic type conversion, it probably doesn't matter *that* much.
https:/
container/
this has enough arguments that i might consider making it take a struct
argument.
https:/
container/
removedDir); err != nil {
can the Destroy method be called concurrently?
if so, i think you should try the rename each time
you come up with a new name, otherwise someone else
might be racing for the same name.
alternatively just use ioutil.TempDir and move into a known
name inside a directory created with that.
https:/
container/
string {
On 2013/06/18 23:50:16, thumper wrote:
> On 2013/06/18 05:29:55, wallyworld wrote:
> > Does this need to be exported? I would think not.
> It needs to be exported to test it. And since no-one should really
have access
> to it, it *shouldn't* matter.
you could always define it in export_test.go. i think it's nice
to export only those methods that actually need exporting, otherwise
the reader is left wondering whether there's a particular reason
for exporting them. ditto below, and +1 to wallyworld's queries.
https:/
container/
for the Instance.
If the following methods are exported in order to satisfy an interface,
I'd suggest simply documenting as
// Id implements sompackage.
(potentially with extra comments that are specific to
this particular implementation). The method documentation
can be left to the interface definition, where it can
be seen with godoc.
If they're only exported for tests, I'd suggest exporting
them only in export_test.go.
https:/
container/
i'm concerned that if there's some filesystem
...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
William Reade (fwereade) wrote : | # |
I still think it's a little bit smelly to be mixing Instance methods
into the lxc Container type, but I'm ok to explore this direction and
see how it works out. LGTM with the various other comments addressed,
acknowledged, or explained away, but please check with the other
reviewers before you land it.
https:/
File container/
https:/
container/
On 2013/06/21 07:13:52, rog wrote:
> this has enough arguments that i might consider making it take a
struct
> argument.
I think there's a little bit of settling still to do here. There's
definitely some sort of common params struct that'll be useful when
starting instances of whatever stripe... consider something similar to
MachineConfig, perhaps?
https:/
container/
On 2013/06/18 23:50:16, thumper wrote:
> On 2013/06/18 05:29:55, wallyworld wrote:
> > Is there a bug for this behaviour? If so reference it here. If not,
do we know
> > why it's expected behaviour? Can we put the definitive reason
instead of
> > speculation?
> It is due to the definition of the rootfs, as serge mentioned in an
email. Not
> quite sure how to fix it, or if it is a real problem. But I can
extend the
> comment.
+1
https:/
container/
removedDir); err != nil {
On 2013/06/21 07:13:52, rog wrote:
> can the Destroy method be called concurrently?
> if so, i think you should try the rename each time
> you come up with a new name, otherwise someone else
> might be racing for the same name.
> alternatively just use ioutil.TempDir and move into a known
> name inside a directory created with that.
In theory, I think, it can't be; but, well, probably one day it should
be possible to remove N containers in parallel if they're all unwanted.
Seems smart to consider the concurrent case today.
https:/
File container/
https:/
container/
fwiw, I find `x,y = y, z` quite readable in these situations. YMMV ofc
https:/
File container/
https:/
container/
var _ Instance = (*lxcInstance)
https:/
container/
identifier for the Instance.
On 2013/06/21 07:13:52, rog wrote:
> // Id implements instance.
> and etc below.
+1
https:/
container/
OpenP...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Tim Penhey (thumper) wrote : | # |
Please take a look.
https:/
File container/
https:/
container/
On 2013/06/21 10:01:00, fwereade wrote:
> On 2013/06/21 07:13:52, rog wrote:
> > this has enough arguments that i might consider making it take a
struct
> > argument.
> I think there's a little bit of settling still to do here. There's
definitely
> some sort of common params struct that'll be useful when starting
instances of
> whatever stripe... consider something similar to MachineConfig,
perhaps?
I'd rather wait and let the dust settle first.
https:/
container/
removedDir); err != nil {
On 2013/06/21 10:01:00, fwereade wrote:
> On 2013/06/21 07:13:52, rog wrote:
> > can the Destroy method be called concurrently?
> >
> > if so, i think you should try the rename each time
> > you come up with a new name, otherwise someone else
> > might be racing for the same name.
> >
> > alternatively just use ioutil.TempDir and move into a known
> > name inside a directory created with that.
> In theory, I think, it can't be; but, well, probably one day it should
be
> possible to remove N containers in parallel if they're all unwanted.
Seems smart
> to consider the concurrent case today.
Even if we were going to remove N containers in parallel, the name is
still unique for any running container. The reason we have the .n
suffix is for when we had container 0, then destroyed it, then created
another then destroyed that one.
https:/
container/
string {
On 2013/06/21 07:13:52, rog wrote:
> On 2013/06/18 23:50:16, thumper wrote:
> > On 2013/06/18 05:29:55, wallyworld wrote:
> > > Does this need to be exported? I would think not.
> >
> > It needs to be exported to test it. And since no-one should really
have
> access
> > to it, it *shouldn't* matter.
> you could always define it in export_test.go. i think it's nice
> to export only those methods that actually need exporting, otherwise
> the reader is left wondering whether there's a particular reason
> for exporting them. ditto below, and +1 to wallyworld's queries.
understood, and not needed in this case.
https:/
container/
for the Instance.
On 2013/06/21 07:13:52, rog wrote:
> If the following methods are exported in order to satisfy an
interface, I'd
> suggest simply documenting as
> // Id implements sompackage.
> (potentially with extra comments that are specific to
> this particular implementation). The method documentation
> can be left to the interface definition, where it can
> be seen with godoc.
> If they're only exported for tests, I'd suggest exporting
> them only in export_test.go.
These were moved to a different file in a later change set attache...
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ian Booth (wallyworld) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/lxc-container into lp:juju-core failed. Below is the output from the failed tests.
container/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
John A Meinel (jameinel) wrote : | # |
I added 'launchpad.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~thumper/juju-core/lxc-container into lp:juju-core failed. Below is the output from the failed tests.
# launchpad.
container/
Preview Diff
1 | === added directory 'container' |
2 | === added directory 'container/lxc' |
3 | === added file 'container/lxc/export_test.go' |
4 | --- container/lxc/export_test.go 1970-01-01 00:00:00 +0000 |
5 | +++ container/lxc/export_test.go 2013-06-25 21:33:28 +0000 |
6 | @@ -0,0 +1,19 @@ |
7 | +// Copyright 2013 Canonical Ltd. |
8 | +// Licensed under the AGPLv3, see LICENCE file for details. |
9 | + |
10 | +package lxc |
11 | + |
12 | +func SetContainerDir(dir string) (old string) { |
13 | + old, containerDir = containerDir, dir |
14 | + return |
15 | +} |
16 | + |
17 | +func SetLxcContainerDir(dir string) (old string) { |
18 | + old, lxcContainerDir = lxcContainerDir, dir |
19 | + return |
20 | +} |
21 | + |
22 | +func SetRemovedContainerDir(dir string) (old string) { |
23 | + old, removedContainerDir = removedContainerDir, dir |
24 | + return |
25 | +} |
26 | |
27 | === added file 'container/lxc/instance.go' |
28 | --- container/lxc/instance.go 1970-01-01 00:00:00 +0000 |
29 | +++ container/lxc/instance.go 2013-06-25 21:33:28 +0000 |
30 | @@ -0,0 +1,46 @@ |
31 | +// Copyright 2013 Canonical Ltd. |
32 | +// Licensed under the AGPLv3, see LICENCE file for details. |
33 | + |
34 | +package lxc |
35 | + |
36 | +import ( |
37 | + "fmt" |
38 | + |
39 | + "launchpad.net/juju-core/instance" |
40 | +) |
41 | + |
42 | +type lxcInstance struct { |
43 | + id string |
44 | +} |
45 | + |
46 | +var _ instance.Instance = (*lxcInstance)(nil) |
47 | + |
48 | +// Id implements instance.Instance.Id. |
49 | +func (lxc *lxcInstance) Id() instance.Id { |
50 | + return instance.Id(lxc.id) |
51 | +} |
52 | + |
53 | +// DNSName implements instance.Instance.DNSName. |
54 | +func (lxc *lxcInstance) DNSName() (string, error) { |
55 | + return "", instance.ErrNoDNSName |
56 | +} |
57 | + |
58 | +// WaitDNSName implements instance.Instance.WaitDNSName. |
59 | +func (lxc *lxcInstance) WaitDNSName() (string, error) { |
60 | + return "", instance.ErrNoDNSName |
61 | +} |
62 | + |
63 | +// OpenPorts implements instance.Instance.OpenPorts. |
64 | +func (lxc *lxcInstance) OpenPorts(machineId string, ports []instance.Port) error { |
65 | + return fmt.Errorf("not implemented") |
66 | +} |
67 | + |
68 | +// ClosePorts implements instance.Instance.ClosePorts. |
69 | +func (lxc *lxcInstance) ClosePorts(machineId string, ports []instance.Port) error { |
70 | + return fmt.Errorf("not implemented") |
71 | +} |
72 | + |
73 | +// Ports implements instance.Instance.Ports. |
74 | +func (lxc *lxcInstance) Ports(machineId string) ([]instance.Port, error) { |
75 | + return nil, fmt.Errorf("not implemented") |
76 | +} |
77 | |
78 | === added file 'container/lxc/lxc.go' |
79 | --- container/lxc/lxc.go 1970-01-01 00:00:00 +0000 |
80 | +++ container/lxc/lxc.go 2013-06-25 21:33:28 +0000 |
81 | @@ -0,0 +1,283 @@ |
82 | +// Copyright 2013 Canonical Ltd. |
83 | +// Licensed under the AGPLv3, see LICENCE file for details. |
84 | + |
85 | +package lxc |
86 | + |
87 | +import ( |
88 | + "fmt" |
89 | + "io/ioutil" |
90 | + "os" |
91 | + "path/filepath" |
92 | + "strings" |
93 | + |
94 | + "launchpad.net/golxc" |
95 | + "launchpad.net/juju-core/constraints" |
96 | + "launchpad.net/juju-core/environs" |
97 | + "launchpad.net/juju-core/environs/cloudinit" |
98 | + "launchpad.net/juju-core/environs/config" |
99 | + "launchpad.net/juju-core/instance" |
100 | + "launchpad.net/juju-core/state" |
101 | + "launchpad.net/juju-core/state/api" |
102 | + "launchpad.net/loggo" |
103 | +) |
104 | + |
105 | +var logger = loggo.GetLogger("juju.container.lxc") |
106 | + |
107 | +var ( |
108 | + defaultTemplate = "ubuntu-cloud" |
109 | + containerDir = "/var/lib/juju/containers" |
110 | + removedContainerDir = "/var/lib/juju/removed-containers" |
111 | + lxcContainerDir = "/var/lib/lxc" |
112 | +) |
113 | + |
114 | +// ContainerManager is responsible for starting containers, and stopping and |
115 | +// listing containers that it has started. The name of the manager is used to |
116 | +// namespace the lxc containers on the machine. |
117 | +type ContainerManager interface { |
118 | + // StartContainer creates and starts a new lxc container for the specified machine. |
119 | + StartContainer( |
120 | + machineId, series, nonce string, |
121 | + tools *state.Tools, |
122 | + environConfig *config.Config, |
123 | + stateInfo *state.Info, |
124 | + apiInfo *api.Info) (instance.Instance, error) |
125 | + // StopContainer stops and destroyes the lxc container identified by Instance. |
126 | + StopContainer(instance.Instance) error |
127 | + // ListContainers return a list of containers that have been started by |
128 | + // this manager. |
129 | + ListContainers() ([]instance.Instance, error) |
130 | +} |
131 | + |
132 | +type containerManager struct { |
133 | + lxcObjectFactory golxc.ContainerFactory |
134 | + name string |
135 | +} |
136 | + |
137 | +// NewContainerManager returns a manager object that can start and stop lxc |
138 | +// containers. The containers that are created are namespaced by the name |
139 | +// parameter. |
140 | +func NewContainerManager(factory golxc.ContainerFactory, name string) ContainerManager { |
141 | + return &containerManager{factory, name} |
142 | +} |
143 | + |
144 | +func (manager *containerManager) StartContainer( |
145 | + machineId, series, nonce string, |
146 | + tools *state.Tools, |
147 | + environConfig *config.Config, |
148 | + stateInfo *state.Info, |
149 | + apiInfo *api.Info) (instance.Instance, error) { |
150 | + |
151 | + name := state.MachineTag(machineId) |
152 | + if manager.name != "" { |
153 | + name = fmt.Sprintf("%s-%s", manager.name, name) |
154 | + } |
155 | + // Note here that the lxcObjectFacotry only returns a valid container |
156 | + // object, and doesn't actually construct the underlying lxc container on |
157 | + // disk. |
158 | + container := manager.lxcObjectFactory.New(name) |
159 | + |
160 | + // Create the cloud-init. |
161 | + directory := jujuContainerDirectory(name) |
162 | + logger.Tracef("create directory: %s", directory) |
163 | + if err := os.MkdirAll(directory, 0755); err != nil { |
164 | + logger.Errorf("failed to create container directory: %v", err) |
165 | + return nil, err |
166 | + } |
167 | + logger.Tracef("write cloud-init") |
168 | + userDataFilename, err := writeUserData(directory, machineId, nonce, tools, environConfig, stateInfo, apiInfo) |
169 | + if err != nil { |
170 | + logger.Errorf("failed to write user data: %v", err) |
171 | + return nil, err |
172 | + } |
173 | + logger.Tracef("write the lxc.conf file") |
174 | + configFile, err := writeLxcConfig(directory) |
175 | + if err != nil { |
176 | + logger.Errorf("failed to write config file: %v", err) |
177 | + return nil, err |
178 | + } |
179 | + templateParams := []string{ |
180 | + "--debug", // Debug errors in the cloud image |
181 | + "--userdata", userDataFilename, // Our groovey cloud-init |
182 | + "--hostid", name, // Use the container name as the hostid |
183 | + "-r", series, |
184 | + } |
185 | + // Create the container. |
186 | + logger.Tracef("create the container") |
187 | + if err := container.Create(configFile, defaultTemplate, templateParams...); err != nil { |
188 | + logger.Errorf("lxc container creation failed: %v", err) |
189 | + return nil, err |
190 | + } |
191 | + // Make sure that the mount dir has been created. |
192 | + logger.Tracef("make the mount dir for the shard logs") |
193 | + if err := os.MkdirAll(internalLogDir(name), 0755); err != nil { |
194 | + logger.Errorf("failed to create internal /var/log/juju mount dir: %v", err) |
195 | + return nil, err |
196 | + } |
197 | + logger.Tracef("lxc container created") |
198 | + |
199 | + // Start the lxc container with the appropriate settings for grabbing the |
200 | + // console output and a log file. |
201 | + consoleFile := filepath.Join(directory, "console.log") |
202 | + container.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug) |
203 | + logger.Tracef("start the container") |
204 | + // We explicitly don't pass through the config file to the container.Start |
205 | + // method as we have passed it through at container creation time. This |
206 | + // is necessary to get the appropriate rootfs reference without explicitly |
207 | + // setting it ourselves. |
208 | + if err = container.Start("", consoleFile); err != nil { |
209 | + logger.Errorf("container failed to start: %v", err) |
210 | + return nil, err |
211 | + } |
212 | + logger.Tracef("container started") |
213 | + return &lxcInstance{name}, nil |
214 | +} |
215 | + |
216 | +func (manager *containerManager) StopContainer(instance instance.Instance) error { |
217 | + name := string(instance.Id()) |
218 | + container := manager.lxcObjectFactory.New(name) |
219 | + if err := container.Stop(); err != nil { |
220 | + logger.Errorf("failed to stop lxc container: %v", err) |
221 | + return err |
222 | + } |
223 | + if err := container.Destroy(); err != nil { |
224 | + logger.Errorf("failed to destroy lxc container: %v", err) |
225 | + return err |
226 | + } |
227 | + // Move the directory. |
228 | + logger.Tracef("create old container dir: %s", removedContainerDir) |
229 | + if err := os.MkdirAll(removedContainerDir, 0755); err != nil { |
230 | + logger.Errorf("failed to create removed container directory: %v", err) |
231 | + return err |
232 | + } |
233 | + removedDir, err := uniqueDirectory(removedContainerDir, name) |
234 | + if err != nil { |
235 | + logger.Errorf("was not able to generate a unique directory: %v", err) |
236 | + return err |
237 | + } |
238 | + if err := os.Rename(jujuContainerDirectory(name), removedDir); err != nil { |
239 | + logger.Errorf("failed to rename container directory: %v", err) |
240 | + return err |
241 | + } |
242 | + return nil |
243 | +} |
244 | + |
245 | +func (manager *containerManager) ListContainers() (result []instance.Instance, err error) { |
246 | + containers, err := manager.lxcObjectFactory.List() |
247 | + if err != nil { |
248 | + logger.Errorf("failed getting all instances: %v", err) |
249 | + return |
250 | + } |
251 | + managerPrefix := "" |
252 | + if manager.name != "" { |
253 | + managerPrefix = fmt.Sprintf("%s-", manager.name) |
254 | + } |
255 | + |
256 | + for _, container := range containers { |
257 | + // Filter out those not starting with our name. |
258 | + name := container.Name() |
259 | + if !strings.HasPrefix(name, managerPrefix) { |
260 | + continue |
261 | + } |
262 | + if container.IsRunning() { |
263 | + result = append(result, &lxcInstance{name}) |
264 | + } |
265 | + } |
266 | + return |
267 | +} |
268 | + |
269 | +func jujuContainerDirectory(containerName string) string { |
270 | + return filepath.Join(containerDir, containerName) |
271 | +} |
272 | + |
273 | +const internalLogDirTemplate = "%s/%s/rootfs/var/log/juju" |
274 | + |
275 | +func internalLogDir(containerName string) string { |
276 | + return fmt.Sprintf(internalLogDirTemplate, lxcContainerDir, containerName) |
277 | +} |
278 | + |
279 | +const localConfig = ` |
280 | +lxc.network.type = veth |
281 | +lxc.network.link = lxcbr0 |
282 | +lxc.network.flags = up |
283 | + |
284 | +lxc.mount.entry=/var/log/juju var/log/juju none defaults,bind 0 0 |
285 | +` |
286 | + |
287 | +func writeLxcConfig(directory string) (string, error) { |
288 | + // TODO(thumper): support different network settings. |
289 | + configFilename := filepath.Join(directory, "lxc.conf") |
290 | + if err := ioutil.WriteFile(configFilename, []byte(localConfig), 0644); err != nil { |
291 | + return "", err |
292 | + } |
293 | + return configFilename, nil |
294 | +} |
295 | + |
296 | +func writeUserData( |
297 | + directory, machineId, nonce string, |
298 | + tools *state.Tools, |
299 | + environConfig *config.Config, |
300 | + stateInfo *state.Info, |
301 | + apiInfo *api.Info, |
302 | +) (string, error) { |
303 | + userData, err := cloudInitUserData(machineId, nonce, tools, environConfig, stateInfo, apiInfo) |
304 | + if err != nil { |
305 | + logger.Errorf("failed to create user data: %v", err) |
306 | + return "", err |
307 | + } |
308 | + userDataFilename := filepath.Join(directory, "cloud-init") |
309 | + if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil { |
310 | + logger.Errorf("failed to write user data: %v", err) |
311 | + return "", err |
312 | + } |
313 | + return userDataFilename, nil |
314 | +} |
315 | + |
316 | +func cloudInitUserData( |
317 | + machineId, nonce string, |
318 | + tools *state.Tools, |
319 | + environConfig *config.Config, |
320 | + stateInfo *state.Info, |
321 | + apiInfo *api.Info, |
322 | +) ([]byte, error) { |
323 | + machineConfig := &cloudinit.MachineConfig{ |
324 | + MachineId: machineId, |
325 | + MachineNonce: nonce, |
326 | + MachineContainerType: state.LXC, |
327 | + StateInfo: stateInfo, |
328 | + APIInfo: apiInfo, |
329 | + DataDir: "/var/lib/juju", |
330 | + Tools: tools, |
331 | + } |
332 | + if err := environs.FinishMachineConfig(machineConfig, environConfig, constraints.Value{}); err != nil { |
333 | + return nil, err |
334 | + } |
335 | + cloudConfig, err := cloudinit.New(machineConfig) |
336 | + if err != nil { |
337 | + return nil, err |
338 | + } |
339 | + data, err := cloudConfig.Render() |
340 | + if err != nil { |
341 | + return nil, err |
342 | + } |
343 | + return data, nil |
344 | +} |
345 | + |
346 | +// uniqueDirectory returns "path/name" if that directory doesn't exist. If it |
347 | +// does, the method starts appending .1, .2, etc until a unique name is found. |
348 | +func uniqueDirectory(path, name string) (string, error) { |
349 | + dir := filepath.Join(path, name) |
350 | + _, err := os.Stat(dir) |
351 | + if os.IsNotExist(err) { |
352 | + return dir, nil |
353 | + } |
354 | + for i := 1; ; i++ { |
355 | + dir := filepath.Join(path, fmt.Sprintf("%s.%d", name, i)) |
356 | + _, err := os.Stat(dir) |
357 | + if os.IsNotExist(err) { |
358 | + return dir, nil |
359 | + } else if err != nil { |
360 | + return "", err |
361 | + } |
362 | + } |
363 | + panic("unreachable") |
364 | +} |
365 | |
366 | === added file 'container/lxc/lxc_test.go' |
367 | --- container/lxc/lxc_test.go 1970-01-01 00:00:00 +0000 |
368 | +++ container/lxc/lxc_test.go 2013-06-25 21:33:28 +0000 |
369 | @@ -0,0 +1,167 @@ |
370 | +// Copyright 2013 Canonical Ltd. |
371 | +// Licensed under the AGPLv3, see LICENCE file for details. |
372 | + |
373 | +package lxc_test |
374 | + |
375 | +import ( |
376 | + "fmt" |
377 | + "io/ioutil" |
378 | + "os" |
379 | + "path/filepath" |
380 | + stdtesting "testing" |
381 | + |
382 | + . "launchpad.net/gocheck" |
383 | + "launchpad.net/goyaml" |
384 | + "launchpad.net/juju-core/container/lxc" |
385 | + "launchpad.net/juju-core/instance" |
386 | + jujutesting "launchpad.net/juju-core/juju/testing" |
387 | + "launchpad.net/juju-core/state" |
388 | + "launchpad.net/juju-core/testing" |
389 | + . "launchpad.net/juju-core/testing/checkers" |
390 | + "launchpad.net/juju-core/version" |
391 | +) |
392 | + |
393 | +func Test(t *stdtesting.T) { |
394 | + TestingT(t) |
395 | +} |
396 | + |
397 | +type LxcSuite struct { |
398 | + testing.LoggingSuite |
399 | + containerDir string |
400 | + removedDir string |
401 | + lxcDir string |
402 | + oldContainerDir string |
403 | + oldRemovedDir string |
404 | + oldLxcContainerDir string |
405 | +} |
406 | + |
407 | +var _ = Suite(&LxcSuite{}) |
408 | + |
409 | +func (s *LxcSuite) SetUpSuite(c *C) { |
410 | + s.LoggingSuite.SetUpSuite(c) |
411 | +} |
412 | + |
413 | +func (s *LxcSuite) TearDownSuite(c *C) { |
414 | + s.LoggingSuite.TearDownSuite(c) |
415 | +} |
416 | + |
417 | +func (s *LxcSuite) SetUpTest(c *C) { |
418 | + s.LoggingSuite.SetUpTest(c) |
419 | + s.containerDir = c.MkDir() |
420 | + s.oldContainerDir = lxc.SetContainerDir(s.containerDir) |
421 | + s.removedDir = c.MkDir() |
422 | + s.oldRemovedDir = lxc.SetRemovedContainerDir(s.removedDir) |
423 | + s.lxcDir = c.MkDir() |
424 | + s.oldLxcContainerDir = lxc.SetLxcContainerDir(s.lxcDir) |
425 | +} |
426 | + |
427 | +func (s *LxcSuite) TearDownTest(c *C) { |
428 | + lxc.SetContainerDir(s.oldContainerDir) |
429 | + lxc.SetLxcContainerDir(s.oldLxcContainerDir) |
430 | + lxc.SetRemovedContainerDir(s.oldRemovedDir) |
431 | + s.LoggingSuite.TearDownTest(c) |
432 | +} |
433 | + |
434 | +func StartContainer(c *C, manager lxc.ContainerManager, machineId string) instance.Instance { |
435 | + config := testing.EnvironConfig(c) |
436 | + stateInfo := jujutesting.FakeStateInfo(machineId) |
437 | + apiInfo := jujutesting.FakeAPIInfo(machineId) |
438 | + |
439 | + series := "series" |
440 | + nonce := "fake-nonce" |
441 | + tools := &state.Tools{ |
442 | + Binary: version.MustParseBinary("2.3.4-foo-bar"), |
443 | + URL: "http://tools.example.com/2.3.4-foo-bar.tgz", |
444 | + } |
445 | + |
446 | + inst, err := manager.StartContainer(machineId, series, nonce, tools, config, stateInfo, apiInfo) |
447 | + c.Assert(err, IsNil) |
448 | + return inst |
449 | +} |
450 | + |
451 | +func (s *LxcSuite) TestStartContainer(c *C) { |
452 | + manager := lxc.NewContainerManager(MockFactory(), "") |
453 | + instance := StartContainer(c, manager, "1/lxc/0") |
454 | + |
455 | + name := string(instance.Id()) |
456 | + // Check our container config files. |
457 | + c.Assert(filepath.Join(s.containerDir, name, "lxc.conf"), IsNonEmptyFile) |
458 | + cloudInitFilename := filepath.Join(s.containerDir, name, "cloud-init") |
459 | + c.Assert(cloudInitFilename, IsNonEmptyFile) |
460 | + data, err := ioutil.ReadFile(cloudInitFilename) |
461 | + c.Assert(err, IsNil) |
462 | + c.Assert(string(data), HasPrefix, "#cloud-config\n") |
463 | + |
464 | + x := make(map[interface{}]interface{}) |
465 | + err = goyaml.Unmarshal(data, &x) |
466 | + c.Assert(err, IsNil) |
467 | + |
468 | + var scripts []string |
469 | + for _, s := range x["runcmd"].([]interface{}) { |
470 | + scripts = append(scripts, s.(string)) |
471 | + } |
472 | + |
473 | + c.Assert(scripts[len(scripts)-1], Equals, "start jujud-machine-1-lxc-0") |
474 | + |
475 | + // Check the mount point has been created inside the container. |
476 | + c.Assert(filepath.Join(s.lxcDir, name, "rootfs/var/log/juju"), IsDirectory) |
477 | +} |
478 | + |
479 | +func (s *LxcSuite) TestStopContainer(c *C) { |
480 | + manager := lxc.NewContainerManager(MockFactory(), "") |
481 | + instance := StartContainer(c, manager, "1/lxc/0") |
482 | + |
483 | + err := manager.StopContainer(instance) |
484 | + c.Assert(err, IsNil) |
485 | + |
486 | + name := string(instance.Id()) |
487 | + // Check that the container dir is no longer in the container dir |
488 | + c.Assert(filepath.Join(s.containerDir, name), DoesNotExist) |
489 | + // but instead, in the removed container dir |
490 | + c.Assert(filepath.Join(s.removedDir, name), IsDirectory) |
491 | +} |
492 | + |
493 | +func (s *LxcSuite) TestStopContainerNameClash(c *C) { |
494 | + manager := lxc.NewContainerManager(MockFactory(), "") |
495 | + instance := StartContainer(c, manager, "1/lxc/0") |
496 | + |
497 | + name := string(instance.Id()) |
498 | + targetDir := filepath.Join(s.removedDir, name) |
499 | + err := os.MkdirAll(targetDir, 0755) |
500 | + c.Assert(err, IsNil) |
501 | + |
502 | + err = manager.StopContainer(instance) |
503 | + c.Assert(err, IsNil) |
504 | + |
505 | + // Check that the container dir is no longer in the container dir |
506 | + c.Assert(filepath.Join(s.containerDir, name), DoesNotExist) |
507 | + // but instead, in the removed container dir with a ".1" suffix as there was already a directory there. |
508 | + c.Assert(filepath.Join(s.removedDir, fmt.Sprintf("%s.1", name)), IsDirectory) |
509 | +} |
510 | + |
511 | +func (s *LxcSuite) TestNamedManagerPrefix(c *C) { |
512 | + manager := lxc.NewContainerManager(MockFactory(), "eric") |
513 | + instance := StartContainer(c, manager, "1/lxc/0") |
514 | + c.Assert(string(instance.Id()), Equals, "eric-machine-1-lxc-0") |
515 | +} |
516 | + |
517 | +func (s *LxcSuite) TestListContainers(c *C) { |
518 | + factory := MockFactory() |
519 | + foo := lxc.NewContainerManager(factory, "foo") |
520 | + bar := lxc.NewContainerManager(factory, "bar") |
521 | + |
522 | + foo1 := StartContainer(c, foo, "1/lxc/0") |
523 | + foo2 := StartContainer(c, foo, "1/lxc/1") |
524 | + foo3 := StartContainer(c, foo, "1/lxc/2") |
525 | + |
526 | + bar1 := StartContainer(c, bar, "1/lxc/0") |
527 | + bar2 := StartContainer(c, bar, "1/lxc/1") |
528 | + |
529 | + result, err := foo.ListContainers() |
530 | + c.Assert(err, IsNil) |
531 | + testing.MatchInstances(c, result, foo1, foo2, foo3) |
532 | + |
533 | + result, err = bar.ListContainers() |
534 | + c.Assert(err, IsNil) |
535 | + testing.MatchInstances(c, result, bar1, bar2) |
536 | +} |
537 | |
538 | === added file 'container/lxc/mock-lxc_test.go' |
539 | --- container/lxc/mock-lxc_test.go 1970-01-01 00:00:00 +0000 |
540 | +++ container/lxc/mock-lxc_test.go 2013-06-25 21:33:28 +0000 |
541 | @@ -0,0 +1,147 @@ |
542 | +// Copyright 2013 Canonical Ltd. |
543 | +// Licensed under the AGPLv3, see LICENCE file for details. |
544 | + |
545 | +package lxc_test |
546 | + |
547 | +import ( |
548 | + "fmt" |
549 | + |
550 | + "launchpad.net/golxc" |
551 | +) |
552 | + |
553 | +// This file provides a mock implementation of the golxc interfaces |
554 | +// ContainerFactory and Container. |
555 | + |
556 | +type mockFactory struct { |
557 | + instances map[string]golxc.Container |
558 | +} |
559 | + |
560 | +func MockFactory() golxc.ContainerFactory { |
561 | + return &mockFactory{make(map[string]golxc.Container)} |
562 | +} |
563 | + |
564 | +type mockContainer struct { |
565 | + factory *mockFactory |
566 | + name string |
567 | + state golxc.State |
568 | + logFile string |
569 | + logLevel golxc.LogLevel |
570 | +} |
571 | + |
572 | +// Name returns the name of the container. |
573 | +func (mock *mockContainer) Name() string { |
574 | + return mock.name |
575 | +} |
576 | + |
577 | +// Create creates a new container based on the given template. |
578 | +func (mock *mockContainer) Create(configFile, template string, templateArgs ...string) error { |
579 | + mock.state = golxc.StateStopped |
580 | + mock.factory.instances[mock.name] = mock |
581 | + return nil |
582 | +} |
583 | + |
584 | +// Start runs the container as a daemon. |
585 | +func (mock *mockContainer) Start(configFile, consoleFile string) error { |
586 | + mock.state = golxc.StateRunning |
587 | + return nil |
588 | +} |
589 | + |
590 | +// Stop terminates the running container. |
591 | +func (mock *mockContainer) Stop() error { |
592 | + mock.state = golxc.StateStopped |
593 | + return nil |
594 | +} |
595 | + |
596 | +// Clone creates a copy of the container, giving the copy the specified name. |
597 | +func (mock *mockContainer) Clone(name string) (golxc.Container, error) { |
598 | + container := &mockContainer{ |
599 | + factory: mock.factory, |
600 | + name: name, |
601 | + state: golxc.StateStopped, |
602 | + logLevel: golxc.LogWarning, |
603 | + } |
604 | + mock.factory.instances[name] = container |
605 | + return container, nil |
606 | +} |
607 | + |
608 | +// Freeze freezes all the container's processes. |
609 | +func (mock *mockContainer) Freeze() error { |
610 | + return nil |
611 | +} |
612 | + |
613 | +// Unfreeze thaws all frozen container's processes. |
614 | +func (mock *mockContainer) Unfreeze() error { |
615 | + return nil |
616 | +} |
617 | + |
618 | +// Destroy stops and removes the container. |
619 | +func (mock *mockContainer) Destroy() error { |
620 | + mock.state = golxc.StateUnknown |
621 | + delete(mock.factory.instances, mock.name) |
622 | + return nil |
623 | +} |
624 | + |
625 | +// Wait waits for one of the specified container states. |
626 | +func (mock *mockContainer) Wait(states ...golxc.State) error { |
627 | + return nil |
628 | +} |
629 | + |
630 | +// Info returns the status and the process id of the container. |
631 | +func (mock *mockContainer) Info() (golxc.State, int, error) { |
632 | + pid := -1 |
633 | + if mock.state == golxc.StateRunning { |
634 | + pid = 42 |
635 | + } |
636 | + return mock.state, pid, nil |
637 | +} |
638 | + |
639 | +// IsConstructed checks if the container image exists. |
640 | +func (mock *mockContainer) IsConstructed() bool { |
641 | + return mock.state != golxc.StateUnknown |
642 | +} |
643 | + |
644 | +// IsRunning checks if the state of the container is 'RUNNING'. |
645 | +func (mock *mockContainer) IsRunning() bool { |
646 | + return mock.state == golxc.StateRunning |
647 | +} |
648 | + |
649 | +// String returns information about the container, like the name, state, |
650 | +// and process id. |
651 | +func (mock *mockContainer) String() string { |
652 | + _, pid, _ := mock.Info() |
653 | + return fmt.Sprintf("<MockContainer %q, state: %s, pid %d>", mock.name, string(mock.state), pid) |
654 | +} |
655 | + |
656 | +// LogFile returns the current filename used for the LogFile. |
657 | +func (mock *mockContainer) LogFile() string { |
658 | + return mock.logFile |
659 | +} |
660 | + |
661 | +// LogLevel returns the current logging level (only used if the |
662 | +// LogFile is not ""). |
663 | +func (mock *mockContainer) LogLevel() golxc.LogLevel { |
664 | + return mock.logLevel |
665 | +} |
666 | + |
667 | +// SetLogFile sets both the LogFile and LogLevel. |
668 | +func (mock *mockContainer) SetLogFile(filename string, level golxc.LogLevel) { |
669 | + mock.logFile = filename |
670 | + mock.logLevel = level |
671 | +} |
672 | + |
673 | +func (mock *mockFactory) New(name string) golxc.Container { |
674 | + container := &mockContainer{ |
675 | + factory: mock, |
676 | + name: name, |
677 | + state: golxc.StateUnknown, |
678 | + logLevel: golxc.LogWarning, |
679 | + } |
680 | + return container |
681 | +} |
682 | + |
683 | +func (mock *mockFactory) List() (result []golxc.Container, err error) { |
684 | + for _, container := range mock.instances { |
685 | + result = append(result, container) |
686 | + } |
687 | + return |
688 | +} |
689 | |
690 | === modified file 'environs/cloudinit/cloudinit.go' |
691 | --- environs/cloudinit/cloudinit.go 2013-06-14 02:32:16 +0000 |
692 | +++ environs/cloudinit/cloudinit.go 2013-06-25 21:33:28 +0000 |
693 | @@ -76,7 +76,7 @@ |
694 | |
695 | // MachineContainerType specifies the type of container that the machine |
696 | // is. If the machine is not a container, then the type is "". |
697 | - MachineContainerType string |
698 | + MachineContainerType state.ContainerType |
699 | |
700 | // AuthorizedKeys specifies the keys that are allowed to |
701 | // connect to the machine (see cloudinit.SSHAddAuthorizedKeys) |
702 | @@ -121,7 +121,7 @@ |
703 | c.AddPackage("git") |
704 | // Perfectly reasonable to install lxc on environment instances and kvm |
705 | // containers. |
706 | - if cfg.MachineContainerType != "lxc" { |
707 | + if cfg.MachineContainerType != state.LXC { |
708 | c.AddPackage("lxc") |
709 | } |
710 | |
711 | |
712 | === added file 'testing/instance.go' |
713 | --- testing/instance.go 1970-01-01 00:00:00 +0000 |
714 | +++ testing/instance.go 2013-06-25 21:33:28 +0000 |
715 | @@ -0,0 +1,25 @@ |
716 | +// Copyright 2013 Canonical Ltd. |
717 | +// Licensed under the AGPLv3, see LICENCE file for details. |
718 | + |
719 | +package testing |
720 | + |
721 | +import ( |
722 | + . "launchpad.net/gocheck" |
723 | + "launchpad.net/juju-core/instance" |
724 | +) |
725 | + |
726 | +// MatchInstances uses DeepEquals to check the instances returned. The lists |
727 | +// are first put into a map, so the ordering of the result and expected values |
728 | +// is not tested, and duplicates are ignored. |
729 | +func MatchInstances(c *C, result []instance.Instance, expected ...instance.Instance) { |
730 | + resultMap := make(map[instance.Id]instance.Instance) |
731 | + for _, i := range result { |
732 | + resultMap[i.Id()] = i |
733 | + } |
734 | + |
735 | + expectedMap := make(map[instance.Id]instance.Instance) |
736 | + for _, i := range expected { |
737 | + expectedMap[i.Id()] = i |
738 | + } |
739 | + c.Assert(resultMap, DeepEquals, expectedMap) |
740 | +} |
Reviewers: mp+169980_ code.launchpad. net,
Message:
Please take a look.
Description:
Implement lxc containers
This branch adds a containers interface, and an lxc
implementation of them. A Container is-a Instance, and
uses the interface embedding to copy across the Instance
methods.
At this stage, the containers do nothing special with
networking, and just use the default set up.
/var/log/juju is mounted from the host to the inside of
the container for ease of access to log files.
Some new checks have been added, along with some testing
functions for files.
https:/ /code.launchpad .net/~thumper/ juju-core/ lxc-container/ +merge/ 169980
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/10370044/
Affected files: container. go lxc/export_ test.go lxc/lxc. go lxc/lxc_ test.go lxc/mock- lxc_test. go checkers/ bool.go checkers/ relop.go
A [revision details]
A container/
A container/
A container/
A container/
A container/
A testing/
A testing/
A testing/file.go