Merge lp:~waigani/juju-core/add-start-stop-to-container-manager-interface into lp:~go-bot/juju-core/trunk
- add-start-stop-to-container-manager-interface
- Merge into trunk
Status: | Work in progress |
---|---|
Proposed branch: | lp:~waigani/juju-core/add-start-stop-to-container-manager-interface |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
849 lines (+384/-101) 14 files modified
container/interface.go (+9/-1) container/kvm/container.go (+73/-22) container/kvm/containerfactory.go (+36/-10) container/kvm/instance.go (+1/-4) container/kvm/interface.go (+11/-6) container/kvm/kvm.go (+19/-3) container/kvm/kvm_test.go (+12/-1) container/kvm/libvirt.go (+13/-3) container/kvm/mock/mock-kvm.go (+67/-17) container/kvm/mock/mock-kvm_test.go (+74/-18) container/lxc/lxc.go (+36/-14) container/lxc/lxc_test.go (+23/-0) container/testing/common.go (+8/-0) worker/provisioner/kvm-broker_test.go (+2/-2) |
To merge this branch: | bzr merge lp:~waigani/juju-core/add-start-stop-to-container-manager-interface |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+211832@code.launchpad.net |
Commit message
Description of the change
Container Manager Interface
Add StartContainer and StopContainer to
containerManger interface and implement
for lxc and kvm.
Jesse Meek (waigani) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
I'm a bit nervous about the consistency of start/stop vs create/destroy,
because the InstanceBroker interface has different meanings. Please make
that interface consistent -- assuming you can do so completely
mechanically, LGTM; the rest are just trivials.
https:/
File container/
https:/
container/
Blank lines between methods, please, it's getting harder to read.
https:/
File container/
https:/
container/
One concern about caching these values: how will they hold up if someone
messes with our containers out-of-band? I'm mainly worried about the
local provider here.
https:/
File container/
https:/
container/
and between fields as well, actually
https:/
container/
s/Start/start/
Jesse Meek (waigani) wrote : | # |
Please take a look.
https:/
File container/
https:/
container/
On 2014/03/24 09:51:56, fwereade wrote:
> Blank lines between methods, please, it's getting harder to read.
Done.
https:/
File container/
https:/
container/
On 2014/03/24 09:51:56, fwereade wrote:
> One concern about caching these values: how will they hold up if
someone messes
> with our containers out-of-band? I'm mainly worried about the local
provider
> here.
Good point. What is the cache actually giving us? How often do we call
the container methods Start, Stop, Create and Destroy and does the
expense of calling down to virsh and uvt-kvm each time have any
significant consequence? <- Questions for thumper.
Currently the container methods reset the values to nil after a
successful cache check. We could also reset the cache to nil if the
cache is not as expected and then rerun the check. But really, this is
just circumventing the cache.
On the other hand, what is our policy if people do mess with containers
out-of-band? Is that something we need to seamlessly recover from? We
could reset the cache to nil if the container methods error out for any
reason, so the next time they are called it will work. BUT this is just
adding more complexity at the expense of user experience.
Sigh, thinking out loud here ... ditching the cache seems simplest to
me.
https:/
File container/
https:/
container/
On 2014/03/24 09:51:56, fwereade wrote:
> and between fields as well, actually
Done.
https:/
container/
On 2014/03/24 09:51:56, fwereade wrote:
> s/Start/start/
Done.
Jesse Meek (waigani) wrote : | # |
Please take a look.
Jesse Meek (waigani) wrote : | # |
https:/
File container/
https:/
container/
The commented lines cause a go routine timeout. I'm not sure why?
Tim Penhey (thumper) wrote : | # |
NOT LGTM yet
https:/
File container/
https:/
container/
s/StopCOntainer
StopContainer stops a started container?
What if the container is already stopped? Do we error?
https:/
File container/
https:/
container/
We should move from a tree state boolean (which was sufficient), to an
actual state.
type kvmState string
var (
unknown kvmState = "unknown"
created kvmState = "created"
running kvmState = "running"
...
)
https:/
container/
wat?
https:/
File container/
https:/
container/
What happens if the container has stopped?
https:/
container/
need at least minimal docstrings on the methods
https:/
File container/
https:/
container/
pretty sure that start container shouldn't do nothing
https:/
container/
doc string should at least say what happens in the error conditions.
what if the container doesn't exist, what if it is already stopped, etc
https:/
container/
err != nil {
Worth noting that that Create both creates and starts the container.
https:/
container/
equally worth noting that Destroy both stops and destroys the container.
https:/
File container/
https:/
container/
c.Assert(
gc.IsNil)
What about starting a started container, or stopping a stopped
container? the mock should as closely as possible mirror what the virsh
commands return
https:/
File container/
https:/
container/kvm/...
Tim Penhey (thumper) wrote : | # |
On 2014/03/24 09:51:56, fwereade wrote:
> I'm a bit nervous about the consistency of start/stop vs
create/destroy, because
> the InstanceBroker interface has different meanings. Please make that
interface
> consistent -- assuming you can do so completely mechanically, LGTM;
the rest are
> just trivials.
The InstanceBroker is aimed at the provisioner, and for the provider to
support the methods
that will be called by the provisioner.
We have the lxcBroker and kvmBroker to do this mapping for us in the
provisioner code.
At the container level, it makes sense to have Create, Start, Stop, and
Destroy. This is
quite independent from the Broker.
William Reade (fwereade) wrote : | # |
On 2014/03/26 02:27:50, thumper wrote:
> On 2014/03/24 09:51:56, fwereade wrote:
> > I'm a bit nervous about the consistency of start/stop vs
create/destroy,
> because
> > the InstanceBroker interface has different meanings. Please make
that
> interface
> > consistent -- assuming you can do so completely mechanically, LGTM;
the rest
> are
> > just trivials.
> The InstanceBroker is aimed at the provisioner, and for the provider
to support
> the methods
> that will be called by the provisioner.
> We have the lxcBroker and kvmBroker to do this mapping for us in the
provisioner
> code.
> At the container level, it makes sense to have Create, Start, Stop,
and Destroy.
> This is
> quite independent from the Broker.
As discussed live: I'd like the Start/Stop/
have a consistent meaning across the codebase -- at least when talking
about such similar things as different implementations of Instance. If
that means the Broker methods start talking Create/Destroy, that works
just fine. I'm not asking for any sort of rearchitecting, just naming
consistency :).
William Reade (fwereade) wrote : | # |
WIP to address tim's comments.
Jesse Meek (waigani) wrote : | # |
Please take a look.
Jesse Meek (waigani) wrote : | # |
On 2014/03/31 06:15:26, waigani wrote:
> Please take a look.
I missed container/
with that. Should I make a new branch for updating the Broker interface?
- 2456. By Jesse Meek
-
Checked KVM states via virsh and uvt-kvm and matched them against KvmStatus
Jesse Meek (waigani) wrote : | # |
Please take a look.
Unmerged revisions
- 2456. By Jesse Meek
-
Checked KVM states via virsh and uvt-kvm and matched them against KvmStatus
- 2455. By Jesse Meek
-
Merge trunk.
- 2454. By Jesse Meek
-
Add KvmState and KvmStatus. Update container methods and tests to use them.
- 2453. By Jesse Meek
-
added tests to mock-kvm and fixed tests in kvm-broker
- 2452. By Jesse Meek
-
Code cleanup from review.
- 2451. By Jesse Meek
-
merge trunk, fix tests
- 2450. By Jesse Meek
-
implement lxc start/stop container, add tests
- 2449. By Jesse Meek
-
Added StartContainer and StopContainer
Preview Diff
1 | === modified file 'container/interface.go' |
2 | --- container/interface.go 2014-03-19 21:08:58 +0000 |
3 | +++ container/interface.go 2014-03-31 22:42:50 +0000 |
4 | @@ -25,8 +25,16 @@ |
5 | machineConfig *cloudinit.MachineConfig, |
6 | series string, |
7 | network *NetworkConfig) (instance.Instance, *instance.HardwareCharacteristics, error) |
8 | - // DestroyContainer stops and destroyes the container identified by Instance. |
9 | + |
10 | + // DestroyContainer stops and destroys the container identified by Instance. |
11 | DestroyContainer(instance.Instance) error |
12 | + |
13 | + // StartContainer starts a stopped container |
14 | + StartContainer(instance.Instance) error |
15 | + |
16 | + // StopContainer stops a started container |
17 | + StopContainer(instance.Instance) error |
18 | + |
19 | // ListContainers return a list of containers that have been started by |
20 | // this manager. |
21 | ListContainers() ([]instance.Instance, error) |
22 | |
23 | === modified file 'container/kvm/container.go' |
24 | --- container/kvm/container.go 2013-12-05 22:32:47 +0000 |
25 | +++ container/kvm/container.go 2014-03-31 22:42:50 +0000 |
26 | @@ -10,13 +10,20 @@ |
27 | "launchpad.net/juju-core/log" |
28 | ) |
29 | |
30 | +// KvmStatus is a cached status of the container |
31 | +type KvmStatus string |
32 | + |
33 | +const ( |
34 | + Unknown KvmStatus = "unknown" |
35 | + Stopped KvmStatus = "stopped" |
36 | + Running KvmStatus = "running" |
37 | + Destroyed KvmStatus = "destroyed" |
38 | +) |
39 | + |
40 | type kvmContainer struct { |
41 | - factory *containerFactory |
42 | - name string |
43 | - // started is a three state boolean, true, false, or unknown |
44 | - // this allows for checking when we don't know, but using a |
45 | - // value if we already know it (like in the list situation). |
46 | - started *bool |
47 | + factory *containerFactory |
48 | + name string |
49 | + cachedStatus KvmStatus |
50 | } |
51 | |
52 | var _ Container = (*kvmContainer)(nil) |
53 | @@ -25,7 +32,11 @@ |
54 | return c.name |
55 | } |
56 | |
57 | -func (c *kvmContainer) Start(params StartParams) error { |
58 | +func (c *kvmContainer) Create(params StartParams) error { |
59 | + if c.Exists() { |
60 | + logger.Debugf("%s already exists", c.name) |
61 | + return nil |
62 | + } |
63 | logger.Debugf("Synchronise images for %s %s", params.Series, params.Arch) |
64 | if err := SyncImages(params.Series, params.Arch); err != nil { |
65 | return err |
66 | @@ -49,34 +60,74 @@ |
67 | CpuCores: params.CpuCores, |
68 | RootDisk: params.RootDisk, |
69 | }); err != nil { |
70 | + c.cachedStatus = Unknown |
71 | return err |
72 | } |
73 | - |
74 | + c.cachedStatus = Running |
75 | logger.Debugf("Set machine %s to autostart", c.name) |
76 | return AutostartMachine(c.name) |
77 | } |
78 | |
79 | +func (c *kvmContainer) Destroy() error { |
80 | + if !c.Exists() { |
81 | + logger.Debugf("%s does not exist", c.name) |
82 | + return nil |
83 | + } |
84 | + logger.Debugf("Destroy %s", c.name) |
85 | + if err := DestroyMachine(c.name); err != nil { |
86 | + c.cachedStatus = Unknown |
87 | + return err |
88 | + } |
89 | + c.cachedStatus = Destroyed |
90 | + return nil |
91 | + |
92 | +} |
93 | + |
94 | func (c *kvmContainer) Stop() error { |
95 | - if !c.IsRunning() { |
96 | - logger.Debugf("%s is already stopped", c.name) |
97 | + if c.Status() == Stopped { |
98 | + logger.Debugf("%s is already Stopped", c.name) |
99 | return nil |
100 | } |
101 | - // Make started state unknown again. |
102 | - c.started = nil |
103 | logger.Debugf("Stop %s", c.name) |
104 | - return DestroyMachine(c.name) |
105 | -} |
106 | - |
107 | -func (c *kvmContainer) IsRunning() bool { |
108 | - if c.started != nil { |
109 | - return *c.started |
110 | - } |
111 | + if err := StopMachine(c.name); err != nil { |
112 | + c.cachedStatus = Unknown |
113 | + return err |
114 | + } |
115 | + c.cachedStatus = Stopped |
116 | + return nil |
117 | +} |
118 | + |
119 | +func (c *kvmContainer) Start() error { |
120 | + if c.Status() == Running { |
121 | + logger.Debugf("%s is already Running", c.name) |
122 | + return nil |
123 | + } |
124 | + logger.Debugf("start %s", c.name) |
125 | + if err := StartMachine(c.name); err != nil { |
126 | + c.cachedStatus = Unknown |
127 | + return err |
128 | + } |
129 | + c.cachedStatus = Running |
130 | + return nil |
131 | +} |
132 | + |
133 | +func (c *kvmContainer) Status() KvmStatus { |
134 | + if c.cachedStatus == Unknown { |
135 | + c.cachedStatus = GetStatusFromState(c.State()) |
136 | + } |
137 | + return c.cachedStatus |
138 | +} |
139 | + |
140 | +func (c *kvmContainer) State() KvmState { |
141 | machines, err := ListMachines() |
142 | if err != nil { |
143 | - return false |
144 | + return KvmState("") |
145 | } |
146 | - c.started = isRunning(machines[c.name]) |
147 | - return *c.started |
148 | + return KvmState(machines[c.name]) |
149 | +} |
150 | + |
151 | +func (c *kvmContainer) Exists() bool { |
152 | + return c.Status() == Running || c.Status() == Stopped |
153 | } |
154 | |
155 | func (c *kvmContainer) String() string { |
156 | |
157 | === modified file 'container/kvm/containerfactory.go' |
158 | --- container/kvm/containerfactory.go 2013-11-18 22:26:36 +0000 |
159 | +++ container/kvm/containerfactory.go 2014-03-31 22:42:50 +0000 |
160 | @@ -15,12 +15,38 @@ |
161 | } |
162 | } |
163 | |
164 | -func isRunning(value string) *bool { |
165 | - var result *bool = new(bool) |
166 | - if value == "running" { |
167 | - *result = true |
168 | - } |
169 | - return result |
170 | +// KvmState is the actual state of the kvm container, as returned by the cmd "uvt-kvm list" or |
171 | +// "virsh list". |
172 | +type KvmState string |
173 | + |
174 | +const ( |
175 | + KvmRunning KvmState = "running" |
176 | + KvmShutoff KvmState = "shut off" |
177 | + KvmStopped KvmState = "stopped" |
178 | + KvmIdle KvmState = "idle" |
179 | + KvmPaused KvmState = "paused" |
180 | + KvmShutdown KvmState = "shutdown" |
181 | + KvmCrashed KvmState = "crashed" |
182 | + KvmDying KvmState = "dying" |
183 | + KvmPmSuspended KvmState = "pmsuspended" |
184 | +) |
185 | + |
186 | +func GetStatusFromState(state KvmState) KvmStatus { |
187 | + stateStatus := map[KvmState]KvmStatus{ |
188 | + KvmRunning: Running, |
189 | + KvmIdle: Running, |
190 | + KvmPaused: Running, |
191 | + KvmPmSuspended: Running, |
192 | + KvmStopped: Stopped, |
193 | + KvmShutdown: Stopped, |
194 | + KvmShutoff: Stopped, |
195 | + KvmCrashed: Stopped, |
196 | + KvmDying: Stopped, |
197 | + } |
198 | + if _, ok := stateStatus[state]; !ok { |
199 | + return Unknown |
200 | + } |
201 | + return stateStatus[state] |
202 | } |
203 | |
204 | func (factory *containerFactory) List() (result []Container, err error) { |
205 | @@ -28,11 +54,11 @@ |
206 | if err != nil { |
207 | return nil, err |
208 | } |
209 | - for hostname, status := range machines { |
210 | + for hostname, state := range machines { |
211 | result = append(result, &kvmContainer{ |
212 | - factory: factory, |
213 | - name: hostname, |
214 | - started: isRunning(status), |
215 | + factory: factory, |
216 | + name: hostname, |
217 | + cachedStatus: GetStatusFromState(state), |
218 | }) |
219 | |
220 | } |
221 | |
222 | === modified file 'container/kvm/instance.go' |
223 | --- container/kvm/instance.go 2013-12-16 09:53:26 +0000 |
224 | +++ container/kvm/instance.go 2014-03-31 22:42:50 +0000 |
225 | @@ -23,10 +23,7 @@ |
226 | |
227 | // Status implements instance.Instance.Status. |
228 | func (kvm *kvmInstance) Status() string { |
229 | - if kvm.container.IsRunning() { |
230 | - return "running" |
231 | - } |
232 | - return "stopped" |
233 | + return string(kvm.container.Status()) |
234 | } |
235 | |
236 | func (*kvmInstance) Refresh() error { |
237 | |
238 | === modified file 'container/kvm/interface.go' |
239 | --- container/kvm/interface.go 2013-12-05 03:38:24 +0000 |
240 | +++ container/kvm/interface.go 2014-03-31 22:42:50 +0000 |
241 | @@ -25,14 +25,19 @@ |
242 | // Name returns the name of the container. |
243 | Name() string |
244 | |
245 | - // Start runs the container as a daemon. |
246 | - Start(params StartParams) error |
247 | - |
248 | - // Stop terminates the running container. |
249 | + // Create runs the container as a daemon. |
250 | + Create(params StartParams) error |
251 | + |
252 | + // Destroy terminates the container. |
253 | + Destroy() error |
254 | + |
255 | + // Start starts the container. |
256 | + Start() error |
257 | + |
258 | + // Stop stops the container. |
259 | Stop() error |
260 | |
261 | - // IsRunning returns wheter or not the container is running and active. |
262 | - IsRunning() bool |
263 | + Status() KvmStatus |
264 | |
265 | // String returns information about the container, like the name, state, |
266 | // and process id. |
267 | |
268 | === modified file 'container/kvm/kvm.go' |
269 | --- container/kvm/kvm.go 2014-03-19 21:08:58 +0000 |
270 | +++ container/kvm/kvm.go 2014-03-31 22:42:50 +0000 |
271 | @@ -78,6 +78,21 @@ |
272 | |
273 | var _ container.Manager = (*containerManager)(nil) |
274 | |
275 | +// StartContainer starts a container that exists but is stopped. |
276 | +func (manager *containerManager) StartContainer(instance instance.Instance) error { |
277 | + name := string(instance.Id()) |
278 | + kvmContainer := KvmObjectFactory.New(name) |
279 | + return kvmContainer.Start() |
280 | +} |
281 | + |
282 | +// StopContainer stops an existing container that is running. |
283 | +func (manager *containerManager) StopContainer(instance instance.Instance) error { |
284 | + name := string(instance.Id()) |
285 | + kvmContainer := KvmObjectFactory.New(name) |
286 | + return kvmContainer.Stop() |
287 | +} |
288 | + |
289 | +// CreateContainer creates and starts the container. |
290 | func (manager *containerManager) CreateContainer( |
291 | machineConfig *cloudinit.MachineConfig, |
292 | series string, |
293 | @@ -118,17 +133,18 @@ |
294 | } |
295 | |
296 | logger.Tracef("create the container, constraints: %v", machineConfig.Constraints) |
297 | - if err := kvmContainer.Start(startParams); err != nil { |
298 | + if err := kvmContainer.Create(startParams); err != nil { |
299 | return nil, nil, log.LoggedErrorf(logger, "kvm container creation failed: %v", err) |
300 | } |
301 | logger.Tracef("kvm container created") |
302 | return &kvmInstance{kvmContainer, name}, &hardware, nil |
303 | } |
304 | |
305 | +// DestroyContainer stops and destroys the container. |
306 | func (manager *containerManager) DestroyContainer(instance instance.Instance) error { |
307 | name := string(instance.Id()) |
308 | kvmContainer := KvmObjectFactory.New(name) |
309 | - if err := kvmContainer.Stop(); err != nil { |
310 | + if err := kvmContainer.Destroy(); err != nil { |
311 | logger.Errorf("failed to stop kvm container: %v", err) |
312 | return err |
313 | } |
314 | @@ -148,7 +164,7 @@ |
315 | if !strings.HasPrefix(name, managerPrefix) { |
316 | continue |
317 | } |
318 | - if container.IsRunning() { |
319 | + if container.Status() == Running { |
320 | result = append(result, &kvmInstance{container, name}) |
321 | } |
322 | } |
323 | |
324 | === modified file 'container/kvm/kvm_test.go' |
325 | --- container/kvm/kvm_test.go 2014-03-19 21:08:58 +0000 |
326 | +++ container/kvm/kvm_test.go 2014-03-31 22:42:50 +0000 |
327 | @@ -58,7 +58,7 @@ |
328 | func (s *KVMSuite) createRunningContainer(c *gc.C, name string) kvm.Container { |
329 | kvmContainer := s.Factory.New(name) |
330 | network := container.BridgeNetworkConfig("testbr0") |
331 | - c.Assert(kvmContainer.Start(kvm.StartParams{ |
332 | + c.Assert(kvmContainer.Create(kvm.StartParams{ |
333 | Series: "quantal", |
334 | Arch: version.Current.Arch, |
335 | UserDataFile: "userdata.txt", |
336 | @@ -108,6 +108,17 @@ |
337 | c.Assert(filepath.Join(s.RemovedDir, name), jc.IsDirectory) |
338 | } |
339 | |
340 | +func (s *KVMSuite) TestStartContainer(c *gc.C) { |
341 | + instance := containertesting.CreateContainer(c, s.manager, "1/kvm/0") |
342 | + c.Assert(containertesting.StopContainer(c, s.manager, instance), gc.IsNil) |
343 | + c.Assert(containertesting.StartContainer(c, s.manager, instance), gc.IsNil) |
344 | +} |
345 | + |
346 | +func (s *KVMSuite) TestStopContainer(c *gc.C) { |
347 | + instance := containertesting.CreateContainer(c, s.manager, "1/kvm/0") |
348 | + c.Assert(containertesting.StopContainer(c, s.manager, instance), gc.IsNil) |
349 | +} |
350 | + |
351 | type ConstraintsSuite struct { |
352 | testbase.LoggingSuite |
353 | } |
354 | |
355 | === modified file 'container/kvm/libvirt.go' |
356 | --- container/kvm/libvirt.go 2013-12-05 22:32:47 +0000 |
357 | +++ container/kvm/libvirt.go 2014-03-31 22:42:50 +0000 |
358 | @@ -104,6 +104,16 @@ |
359 | return err |
360 | } |
361 | |
362 | +func StopMachine(hostname string) error { |
363 | + _, err := run("virsh", "destroy", hostname) |
364 | + return err |
365 | +} |
366 | + |
367 | +func StartMachine(hostname string) error { |
368 | + _, err := run("virsh", "start", hostname) |
369 | + return err |
370 | +} |
371 | + |
372 | // AutostartMachine indicates that the virtual machines should automatically |
373 | // restart when the host restarts. |
374 | func AutostartMachine(hostname string) error { |
375 | @@ -113,7 +123,7 @@ |
376 | |
377 | // ListMachines returns a map of machine name to state, where state is one of: |
378 | // running, idle, paused, shutdown, shut off, crashed, dying, pmsuspended. |
379 | -func ListMachines() (map[string]string, error) { |
380 | +func ListMachines() (map[string]KvmState, error) { |
381 | output, err := run("virsh", "-q", "list", "--all") |
382 | if err != nil { |
383 | return nil, err |
384 | @@ -122,11 +132,11 @@ |
385 | // Regex matching is the easiest way to match the lines. |
386 | // id hostname status |
387 | // separated by whitespace, with whitespace at the start too. |
388 | - result := make(map[string]string) |
389 | + result := make(map[string]KvmState) |
390 | for _, s := range machineListPattern.FindAllStringSubmatchIndex(output, -1) { |
391 | hostnameAndStatus := machineListPattern.ExpandString(nil, "$hostname $status", output, s) |
392 | parts := strings.SplitN(string(hostnameAndStatus), " ", 2) |
393 | - result[parts[0]] = parts[1] |
394 | + result[parts[0]] = KvmState(parts[1]) |
395 | } |
396 | return result, nil |
397 | } |
398 | |
399 | === modified file 'container/kvm/mock/mock-kvm.go' |
400 | --- container/kvm/mock/mock-kvm.go 2013-12-05 03:38:24 +0000 |
401 | +++ container/kvm/mock/mock-kvm.go 2014-03-31 22:42:50 +0000 |
402 | @@ -15,14 +15,22 @@ |
403 | type Action int |
404 | |
405 | const ( |
406 | + // A container has been created.. |
407 | + Created Action = iota |
408 | + // A container has been destroyed. |
409 | + Destroyed |
410 | // A container has been started. |
411 | - Started Action = iota |
412 | + Started |
413 | // A container has been stopped. |
414 | Stopped |
415 | ) |
416 | |
417 | func (action Action) String() string { |
418 | switch action { |
419 | + case Created: |
420 | + return "Created" |
421 | + case Destroyed: |
422 | + return "Destroyed" |
423 | case Started: |
424 | return "Started" |
425 | case Stopped: |
426 | @@ -45,48 +53,77 @@ |
427 | } |
428 | |
429 | type mockFactory struct { |
430 | - instances map[string]kvm.Container |
431 | + instances map[string]*mockContainer |
432 | listeners []chan<- Event |
433 | } |
434 | |
435 | -func MockFactory() ContainerFactory { |
436 | +func MockFactory() *mockFactory { |
437 | return &mockFactory{ |
438 | - instances: make(map[string]kvm.Container), |
439 | + instances: make(map[string]*mockContainer), |
440 | } |
441 | } |
442 | |
443 | type mockContainer struct { |
444 | - factory *mockFactory |
445 | - name string |
446 | - started bool |
447 | + factory *mockFactory |
448 | + name string |
449 | + cachedStatus kvm.KvmStatus |
450 | + mockState kvm.KvmState |
451 | } |
452 | |
453 | +var _ kvm.Container = (*mockContainer)(nil) |
454 | +var _ ContainerFactory = (*mockFactory)(nil) |
455 | + |
456 | // Name returns the name of the container. |
457 | func (mock *mockContainer) Name() string { |
458 | return mock.name |
459 | } |
460 | |
461 | -func (mock *mockContainer) Start(params kvm.StartParams) error { |
462 | - if mock.started { |
463 | +func (mock *mockContainer) Create(params kvm.StartParams) error { |
464 | + if mock.Exists() { |
465 | + return fmt.Errorf("container already exists") |
466 | + } |
467 | + mock.factory.notify(Created, mock.name) |
468 | + mock.cachedStatus = kvm.Running |
469 | + return nil |
470 | +} |
471 | + |
472 | +// Destroy terminates the running container. |
473 | +func (mock *mockContainer) Destroy() error { |
474 | + if !mock.Exists() { |
475 | + return fmt.Errorf("container does not exist") |
476 | + } |
477 | + mock.factory.notify(Destroyed, mock.name) |
478 | + mock.cachedStatus = kvm.Destroyed |
479 | + return nil |
480 | +} |
481 | + |
482 | +func (mock *mockContainer) Start() error { |
483 | + if mock.Status() == kvm.Running { |
484 | return fmt.Errorf("container is already running") |
485 | } |
486 | - mock.started = true |
487 | mock.factory.notify(Started, mock.name) |
488 | + mock.cachedStatus = kvm.Running |
489 | return nil |
490 | } |
491 | |
492 | -// Stop terminates the running container. |
493 | func (mock *mockContainer) Stop() error { |
494 | - if !mock.started { |
495 | + if mock.Status() != kvm.Running { |
496 | return fmt.Errorf("container is not running") |
497 | } |
498 | - mock.started = false |
499 | mock.factory.notify(Stopped, mock.name) |
500 | + mock.cachedStatus = kvm.Stopped |
501 | return nil |
502 | } |
503 | |
504 | -func (mock *mockContainer) IsRunning() bool { |
505 | - return mock.started |
506 | +func (mock *mockContainer) Status() kvm.KvmStatus { |
507 | + if mock.cachedStatus == kvm.Unknown { |
508 | + mock.cachedStatus = kvm.GetStatusFromState(mock.State()) |
509 | + } |
510 | + return mock.cachedStatus |
511 | +} |
512 | + |
513 | +func (c *mockContainer) Exists() bool { |
514 | + return c.Status() == kvm.Running || c.Status() == kvm.Stopped |
515 | } |
516 | |
517 | // String returns information about the container. |
518 | @@ -103,9 +140,12 @@ |
519 | if ok { |
520 | return container |
521 | } |
522 | + state := kvm.KvmState("") |
523 | container = &mockContainer{ |
524 | - factory: mock, |
525 | - name: name, |
526 | + factory: mock, |
527 | + name: name, |
528 | + cachedStatus: kvm.GetStatusFromState(state), |
529 | + mockState: state, |
530 | } |
531 | mock.instances[name] = container |
532 | return container |
533 | @@ -118,6 +158,16 @@ |
534 | return |
535 | } |
536 | |
537 | +func (mock *mockContainer) State() kvm.KvmState { |
538 | + factory := MockFactory() |
539 | + for _, container := range factory.instances { |
540 | + if container.Name() == mock.name { |
541 | + return container.mockState |
542 | + } |
543 | + } |
544 | + return kvm.KvmState("") |
545 | +} |
546 | + |
547 | func (mock *mockFactory) notify(action Action, instanceId string) { |
548 | event := Event{action, instanceId} |
549 | for _, c := range mock.listeners { |
550 | |
551 | === modified file 'container/kvm/mock/mock-kvm_test.go' |
552 | --- container/kvm/mock/mock-kvm_test.go 2014-03-13 07:54:56 +0000 |
553 | +++ container/kvm/mock/mock-kvm_test.go 2014-03-31 22:42:50 +0000 |
554 | @@ -39,41 +39,83 @@ |
555 | factory := mock.MockFactory() |
556 | container := factory.New("first") |
557 | c.Assert(container.Name(), gc.Equals, "first") |
558 | - c.Assert(container.IsRunning(), jc.IsFalse) |
559 | + c.Assert(container.Status(), gc.Not(gc.Equals), kvm.Running) |
560 | } |
561 | |
562 | func (*MockSuite) TestContainerStoppingStoppedErrors(c *gc.C) { |
563 | factory := mock.MockFactory() |
564 | container := factory.New("first") |
565 | - err := container.Stop() |
566 | + err := container.Create(kvm.StartParams{}) |
567 | + c.Assert(err, gc.IsNil) |
568 | + err = container.Stop() |
569 | + c.Assert(err, gc.IsNil) |
570 | + err = container.Stop() |
571 | c.Assert(err, gc.ErrorMatches, "container is not running") |
572 | } |
573 | |
574 | +func (*MockSuite) TestContainerDestroyDestroyedErrors(c *gc.C) { |
575 | + factory := mock.MockFactory() |
576 | + container := factory.New("first") |
577 | + err := container.Destroy() |
578 | + c.Assert(err, gc.ErrorMatches, "container does not exist") |
579 | +} |
580 | + |
581 | +func (*MockSuite) TestContainerCreateStarts(c *gc.C) { |
582 | + factory := mock.MockFactory() |
583 | + container := factory.New("first") |
584 | + err := container.Create(kvm.StartParams{}) |
585 | + c.Assert(err, gc.IsNil) |
586 | + c.Assert(container.Status(), gc.Equals, kvm.Running) |
587 | +} |
588 | + |
589 | func (*MockSuite) TestContainerStartStarts(c *gc.C) { |
590 | factory := mock.MockFactory() |
591 | container := factory.New("first") |
592 | - err := container.Start(kvm.StartParams{}) |
593 | - c.Assert(err, gc.IsNil) |
594 | - c.Assert(container.IsRunning(), jc.IsTrue) |
595 | + err := container.Create(kvm.StartParams{}) |
596 | + c.Assert(err, gc.IsNil) |
597 | + err = container.Stop() |
598 | + c.Assert(err, gc.IsNil) |
599 | + err = container.Start() |
600 | + c.Assert(err, gc.IsNil) |
601 | + c.Assert(container.Status(), gc.Equals, kvm.Running) |
602 | } |
603 | |
604 | func (*MockSuite) TestContainerStartingRunningErrors(c *gc.C) { |
605 | factory := mock.MockFactory() |
606 | container := factory.New("first") |
607 | - err := container.Start(kvm.StartParams{}) |
608 | + err := container.Create(kvm.StartParams{}) |
609 | c.Assert(err, gc.IsNil) |
610 | - err = container.Start(kvm.StartParams{}) |
611 | + err = container.Start() |
612 | c.Assert(err, gc.ErrorMatches, "container is already running") |
613 | } |
614 | |
615 | +func (*MockSuite) TestContainerCreatingRunningErrors(c *gc.C) { |
616 | + factory := mock.MockFactory() |
617 | + container := factory.New("first") |
618 | + err := container.Create(kvm.StartParams{}) |
619 | + c.Assert(err, gc.IsNil) |
620 | + err = container.Create(kvm.StartParams{}) |
621 | + c.Assert(err, gc.ErrorMatches, "container already exists") |
622 | +} |
623 | + |
624 | func (*MockSuite) TestContainerStoppingRunningStops(c *gc.C) { |
625 | factory := mock.MockFactory() |
626 | container := factory.New("first") |
627 | - err := container.Start(kvm.StartParams{}) |
628 | + err := container.Create(kvm.StartParams{}) |
629 | c.Assert(err, gc.IsNil) |
630 | err = container.Stop() |
631 | c.Assert(err, gc.IsNil) |
632 | - c.Assert(container.IsRunning(), jc.IsFalse) |
633 | + c.Assert(container.Status(), gc.Not(gc.Equals), kvm.Running) |
634 | +} |
635 | + |
636 | +func (*MockSuite) TestContainerDestroyingRunningStops(c *gc.C) { |
637 | + factory := mock.MockFactory() |
638 | + container := factory.New("first") |
639 | + err := container.Create(kvm.StartParams{}) |
640 | + c.Assert(err, gc.IsNil) |
641 | + err = container.Destroy() |
642 | + c.Assert(err, gc.IsNil) |
643 | + c.Assert(container.Status(), gc.Not(gc.Equals), kvm.Running) |
644 | } |
645 | |
646 | func (*MockSuite) TestAddListener(c *gc.C) { |
647 | @@ -127,20 +169,28 @@ |
648 | |
649 | func (*MockSuite) TestEvents(c *gc.C) { |
650 | factory := mock.MockFactory() |
651 | - listener := make(chan mock.Event, 5) |
652 | + listener := make(chan mock.Event, 15) |
653 | factory.AddListener(listener) |
654 | |
655 | first := factory.New("first") |
656 | second := factory.New("second") |
657 | - first.Start(kvm.StartParams{}) |
658 | - second.Start(kvm.StartParams{}) |
659 | + first.Create(kvm.StartParams{}) |
660 | + second.Create(kvm.StartParams{}) |
661 | + first.Stop() |
662 | second.Stop() |
663 | - first.Stop() |
664 | + first.Start() |
665 | + second.Start() |
666 | + second.Destroy() |
667 | + first.Destroy() |
668 | |
669 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Created, "first"}) |
670 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Created, "second"}) |
671 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Stopped, "first"}) |
672 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Stopped, "second"}) |
673 | c.Assert(<-listener, gc.Equals, mock.Event{mock.Started, "first"}) |
674 | c.Assert(<-listener, gc.Equals, mock.Event{mock.Started, "second"}) |
675 | - c.Assert(<-listener, gc.Equals, mock.Event{mock.Stopped, "second"}) |
676 | - c.Assert(<-listener, gc.Equals, mock.Event{mock.Stopped, "first"}) |
677 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Destroyed, "second"}) |
678 | + c.Assert(<-listener, gc.Equals, mock.Event{mock.Destroyed, "first"}) |
679 | } |
680 | |
681 | func (*MockSuite) TestEventsGoToAllListeners(c *gc.C) { |
682 | @@ -151,11 +201,17 @@ |
683 | factory.AddListener(second) |
684 | |
685 | container := factory.New("container") |
686 | - container.Start(kvm.StartParams{}) |
687 | + container.Create(kvm.StartParams{}) |
688 | container.Stop() |
689 | + container.Start() |
690 | + container.Destroy() |
691 | |
692 | + c.Assert(<-first, gc.Equals, mock.Event{mock.Created, "container"}) |
693 | + c.Assert(<-second, gc.Equals, mock.Event{mock.Created, "container"}) |
694 | + c.Assert(<-first, gc.Equals, mock.Event{mock.Stopped, "container"}) |
695 | + c.Assert(<-second, gc.Equals, mock.Event{mock.Stopped, "container"}) |
696 | c.Assert(<-first, gc.Equals, mock.Event{mock.Started, "container"}) |
697 | c.Assert(<-second, gc.Equals, mock.Event{mock.Started, "container"}) |
698 | - c.Assert(<-first, gc.Equals, mock.Event{mock.Stopped, "container"}) |
699 | - c.Assert(<-second, gc.Equals, mock.Event{mock.Stopped, "container"}) |
700 | + c.Assert(<-first, gc.Equals, mock.Event{mock.Destroyed, "container"}) |
701 | + c.Assert(<-second, gc.Equals, mock.Event{mock.Destroyed, "container"}) |
702 | } |
703 | |
704 | === modified file 'container/lxc/lxc.go' |
705 | --- container/lxc/lxc.go 2014-03-19 21:08:58 +0000 |
706 | +++ container/lxc/lxc.go 2014-03-31 22:42:50 +0000 |
707 | @@ -112,6 +112,36 @@ |
708 | }, nil |
709 | } |
710 | |
711 | +func (manager *containerManager) StartContainer(instance instance.Instance) error { |
712 | + name := string(instance.Id()) |
713 | + lxcContainer := LxcObjectFactory.New(name) |
714 | + // Create the cloud-init. |
715 | + directory, err := container.NewDirectory(name) |
716 | + if err != nil { |
717 | + return err |
718 | + } |
719 | + // Start the lxc container with the appropriate settings for grabbing the |
720 | + // console output and a log file. |
721 | + consoleFile := filepath.Join(directory, "console.log") |
722 | + lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug) |
723 | + logger.Tracef("start the container") |
724 | + // We explicitly don't pass through the config file to the container.Start |
725 | + // method as we have passed it through at container creation time. This |
726 | + // is necessary to get the appropriate rootfs reference without explicitly |
727 | + // setting it ourselves. |
728 | + if err = lxcContainer.Start("", consoleFile); err != nil { |
729 | + logger.Errorf("container failed to start: %v", err) |
730 | + return err |
731 | + } |
732 | + return nil |
733 | +} |
734 | + |
735 | +func (manager *containerManager) StopContainer(instance instance.Instance) error { |
736 | + name := string(instance.Id()) |
737 | + lxcContainer := LxcObjectFactory.New(name) |
738 | + return lxcContainer.Stop() |
739 | +} |
740 | + |
741 | func (manager *containerManager) CreateContainer( |
742 | machineConfig *cloudinit.MachineConfig, |
743 | series string, |
744 | @@ -204,25 +234,17 @@ |
745 | if err := mountHostLogDir(name, manager.logdir); err != nil { |
746 | return nil, nil, err |
747 | } |
748 | - // Start the lxc container with the appropriate settings for grabbing the |
749 | - // console output and a log file. |
750 | - consoleFile := filepath.Join(directory, "console.log") |
751 | - lxcContainer.SetLogFile(filepath.Join(directory, "container.log"), golxc.LogDebug) |
752 | - logger.Tracef("start the container") |
753 | - // We explicitly don't pass through the config file to the container.Start |
754 | - // method as we have passed it through at container creation time. This |
755 | - // is necessary to get the appropriate rootfs reference without explicitly |
756 | - // setting it ourselves. |
757 | - if err = lxcContainer.Start("", consoleFile); err != nil { |
758 | - logger.Errorf("container failed to start: %v", err) |
759 | - return nil, nil, err |
760 | - } |
761 | + |
762 | + container := &lxcInstance{lxcContainer, name} |
763 | + |
764 | + manager.StartContainer(container) |
765 | + |
766 | arch := version.Current.Arch |
767 | hardware := &instance.HardwareCharacteristics{ |
768 | Arch: &arch, |
769 | } |
770 | logger.Tracef("container %q started: %v", name, time.Now().Sub(start)) |
771 | - return &lxcInstance{lxcContainer, name}, hardware, nil |
772 | + return container, hardware, nil |
773 | } |
774 | |
775 | func appendToContainerConfig(name, line string) error { |
776 | |
777 | === modified file 'container/lxc/lxc_test.go' |
778 | --- container/lxc/lxc_test.go 2014-03-19 21:08:58 +0000 |
779 | +++ container/lxc/lxc_test.go 2014-03-31 22:42:50 +0000 |
780 | @@ -113,6 +113,29 @@ |
781 | c.Assert(c.GetTestLog(), jc.Contains, `WARNING juju.container unused config option: "shazam" -> "Captain Marvel"`) |
782 | } |
783 | |
784 | +func (s *LxcSuite) TestStopContainer(c *gc.C) { |
785 | + manager := s.makeManager(c, "test") |
786 | + instance := containertesting.CreateContainer(c, manager, "1/lxc/0") |
787 | + err := containertesting.StopContainer(c, manager, instance) |
788 | + c.Assert(err, gc.IsNil) |
789 | +} |
790 | + |
791 | +func (s *LxcSuite) TestStartStoppedContainer(c *gc.C) { |
792 | + manager := s.makeManager(c, "test") |
793 | + instance := containertesting.CreateContainer(c, manager, "1/lxc/0") |
794 | + err := containertesting.StopContainer(c, manager, instance) |
795 | + c.Assert(err, gc.IsNil) |
796 | + err = containertesting.StartContainer(c, manager, instance) |
797 | + c.Assert(err, gc.IsNil) |
798 | +} |
799 | + |
800 | +func (s *LxcSuite) TestStartRunningContainer(c *gc.C) { |
801 | + manager := s.makeManager(c, "test") |
802 | + instance := containertesting.CreateContainer(c, manager, "1/lxc/0") |
803 | + err := containertesting.StartContainer(c, manager, instance) |
804 | + c.Assert(err, gc.ErrorMatches, "container is already running") |
805 | +} |
806 | + |
807 | func (s *LxcSuite) TestCreateContainer(c *gc.C) { |
808 | manager := s.makeManager(c, "test") |
809 | instance := containertesting.CreateContainer(c, manager, "1/lxc/0") |
810 | |
811 | === modified file 'container/testing/common.go' |
812 | --- container/testing/common.go 2014-03-19 20:54:52 +0000 |
813 | +++ container/testing/common.go 2014-03-31 22:42:50 +0000 |
814 | @@ -17,6 +17,14 @@ |
815 | "launchpad.net/juju-core/version" |
816 | ) |
817 | |
818 | +func StartContainer(c *gc.C, manager container.Manager, instance instance.Instance) error { |
819 | + return manager.StartContainer(instance) |
820 | +} |
821 | + |
822 | +func StopContainer(c *gc.C, manager container.Manager, instance instance.Instance) error { |
823 | + return manager.StopContainer(instance) |
824 | +} |
825 | + |
826 | func CreateContainer(c *gc.C, manager container.Manager, machineId string) instance.Instance { |
827 | stateInfo := jujutesting.FakeStateInfo(machineId) |
828 | apiInfo := jujutesting.FakeAPIInfo(machineId) |
829 | |
830 | === modified file 'worker/provisioner/kvm-broker_test.go' |
831 | --- worker/provisioner/kvm-broker_test.go 2014-03-14 02:19:35 +0000 |
832 | +++ worker/provisioner/kvm-broker_test.go 2014-03-31 22:42:50 +0000 |
833 | @@ -179,7 +179,7 @@ |
834 | func (s *kvmProvisionerSuite) expectStarted(c *gc.C, machine *state.Machine) string { |
835 | s.State.StartSync() |
836 | event := <-s.events |
837 | - c.Assert(event.Action, gc.Equals, mock.Started) |
838 | + c.Assert(event.Action, gc.Equals, mock.Created) |
839 | err := machine.Refresh() |
840 | c.Assert(err, gc.IsNil) |
841 | s.waitInstanceId(c, machine, instance.Id(event.InstanceId)) |
842 | @@ -189,7 +189,7 @@ |
843 | func (s *kvmProvisionerSuite) expectStopped(c *gc.C, instId string) { |
844 | s.State.StartSync() |
845 | event := <-s.events |
846 | - c.Assert(event.Action, gc.Equals, mock.Stopped) |
847 | + c.Assert(event.Action, gc.Equals, mock.Destroyed) |
848 | c.Assert(event.InstanceId, gc.Equals, instId) |
849 | } |
850 |
Reviewers: mp+211832_ code.launchpad. net,
Message:
Please take a look.
Description:
Container Manager Interface
Add StartContainer and StopContainer to
containerManger interface and implement
for lxc and kvm.
https:/ /code.launchpad .net/~waigani/ juju-core/ add-start- stop-to- container- manager- interface/ +merge/ 211832
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/77970043/
Affected files (+88, -66 lines): interface. go kvm/kvm. go kvm/kvm_ test.go kvm/live_ test.go lxc/lxc. go lxc/lxc_ test.go testing/ common. go local/environ. go provisioner/ kvm-broker. go provisioner/ lxc-broker. go
A [revision details]
M container/
M container/
M container/
M container/
M container/
M container/
M container/
M provider/
M worker/
M worker/