blob: efbfe8eb5eea89c8da7b182465e3cf4bee9d25b7 [file] [log] [blame]
Bogdan Caprita4d67c042014-08-19 10:41:19 -07001package impl
2
3// The app invoker is responsible for managing the state of applications on the
4// node manager. The node manager manages the applications it installs and runs
5// using the following directory structure:
6//
7// TODO(caprita): Not all is yet implemented.
8//
9// <config.Root>/
10// app-<hash 1>/ - the application dir is named using a hash of the application title
11// installation-<id 1>/ - installations are labelled with ids
12// <version 1 timestamp>/ - timestamp of when the version was downloaded
13// bin - application binary
14// previous - symbolic link to previous version directory (TODO)
15// origin - object name for application envelope
16// envelope - application envelope (JSON-encoded)
17// <version 2 timestamp>
18// ...
19// current - symbolic link to the current version
20// instances/
21// instance-<id a>/ - instances are labelled with ids
22// root/ - workspace that the instance is run from
23// logs/ - stderr/stdout and log files generated by instance
24// info - app manager name and process id for the instance (if running)
25// version - symbolic link to installation version for the instance
Bogdan Caprita268b4192014-08-28 10:04:44 -070026// <status> - one of the values for instanceState enum
Bogdan Caprita4d67c042014-08-19 10:41:19 -070027// instance-<id b>
28// ...
Bogdan Caprita4d67c042014-08-19 10:41:19 -070029// installation-<id 2>
30// ...
31// app-<hash 2>
32// ...
33//
34// When node manager starts up, it goes through all instances and resumes the
35// ones that are not suspended. If the application was still running, it
36// suspends it first. If an application fails to resume, it stays suspended.
37//
38// When node manager shuts down, it suspends all running instances.
39//
40// Start starts an instance. Suspend kills the process but leaves the workspace
41// untouched. Resume restarts the process. Stop kills the process and prevents
42// future resumes (it also eventually gc's the workspace).
43//
44// If the process dies on its own, it stays dead and is assumed suspended.
45// TODO(caprita): Later, we'll add auto-restart option.
46//
47// Concurrency model: installations can be created independently of one another;
48// installations can be removed at any time (any running instances will be
49// stopped). The first call to Uninstall will rename the installation dir as a
Bogdan Caprita268b4192014-08-28 10:04:44 -070050// first step; subsequent Uninstall's will fail. Instances can be created
Bogdan Caprita4d67c042014-08-19 10:41:19 -070051// independently of one another, as long as the installation exists (if it gets
Bogdan Caprita268b4192014-08-28 10:04:44 -070052// Uninstall'ed during an instance Start, the Start may fail).
53//
54// The status file present in each instance is used to flag the state of the
55// instance and prevent concurrent operations against the instance:
56//
57// - when an instance is created with Start, it is placed in state 'suspended'.
58// To run the instance, Start transitions 'suspended' to 'starting' and then
59// 'started' (upon success) or the instance is deleted (upon failure).
60//
61// - Suspend attempts to transition from 'started' to 'suspending' (if the
62// instance was not in 'started' state, Suspend fails). From 'suspending', the
63// instance transitions to 'suspended' upon success or back to 'started' upon
64// failure.
65//
66// - Resume attempts to transition from 'suspended' to 'starting' (if the
67// instance was not in 'suspended' state, Resume fails). From 'starting', the
68// instance transitions to 'started' upon success or back to 'suspended' upon
69// failure.
70//
71// - Stop attempts to transition from 'started' to 'stopping' and then to
72// 'stopped' (upon success) or back to 'started' (upon failure); or from
73// 'suspended' to 'stopped'. If the initial state is neither 'started' or
74// 'suspended', Stop fails.
Bogdan Caprita4d67c042014-08-19 10:41:19 -070075//
76// TODO(caprita): There is room for synergy between how node manager organizes
77// its own workspace and that for the applications it runs. In particular,
78// previous, origin, and envelope could be part of a single config. We'll
79// refine that later.
80
81import (
82 "crypto/md5"
83 "encoding/base64"
84 "encoding/binary"
85 "encoding/json"
86 "fmt"
87 "hash/crc64"
88 "io/ioutil"
89 "os"
90 "os/exec"
91 "path/filepath"
92 "strings"
93 "time"
94
95 "veyron/lib/config"
96 vexec "veyron/services/mgmt/lib/exec"
97 iconfig "veyron/services/mgmt/node/config"
98
99 "veyron2/ipc"
100 "veyron2/mgmt"
101 "veyron2/naming"
102 "veyron2/rt"
103 "veyron2/services/mgmt/appcycle"
104 "veyron2/services/mgmt/application"
105 "veyron2/vlog"
106)
107
Bogdan Caprita268b4192014-08-28 10:04:44 -0700108// instanceState describes the states that an instance can be in at any time.
109type instanceState int
110
111const (
112 starting instanceState = iota
113 started
114 suspending
115 suspended
116 stopping
117 stopped
118)
119
120// String returns the name that will be used to encode the state as a file name
121// in the instance's dir.
122func (s instanceState) String() string {
123 switch s {
124 case starting:
125 return "starting"
126 case started:
127 return "started"
128 case suspending:
129 return "suspending"
130 case suspended:
131 return "suspended"
132 case stopping:
133 return "stopping"
134 case stopped:
135 return "stopped"
136 default:
137 return "unknown"
138 }
139}
140
141func transition(instanceDir string, initial, target instanceState) error {
142 initialState := filepath.Join(instanceDir, initial.String())
143 targetState := filepath.Join(instanceDir, target.String())
144 if err := os.Rename(initialState, targetState); err != nil {
145 if os.IsNotExist(err) {
146 return errInvalidOperation
147 }
148 vlog.Errorf("Rename(%v, %v) failed: %v", initialState, targetState, err) // Something went really wrong.
149 return errOperationFailed
150 }
151 return nil
152}
153
154func initializeState(instanceDir string, initial instanceState) error {
155 initialStatus := filepath.Join(instanceDir, initial.String())
156 if err := ioutil.WriteFile(initialStatus, []byte("status"), 0600); err != nil {
157 vlog.Errorf("WriteFile(%v) failed: %v", initialStatus, err)
158 return errOperationFailed
159 }
160 return nil
161}
162
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700163// instanceInfo holds state about a running instance.
164type instanceInfo struct {
165 AppCycleMgrName string
166 Pid int
167}
168
169func saveInstanceInfo(dir string, info *instanceInfo) error {
170 jsonInfo, err := json.Marshal(info)
171 if err != nil {
172 vlog.Errorf("Marshal(%v) failed: %v", info, err)
173 return errOperationFailed
174 }
175 infoPath := filepath.Join(dir, "info")
176 if err := ioutil.WriteFile(infoPath, jsonInfo, 0600); err != nil {
177 vlog.Errorf("WriteFile(%v) failed: %v", infoPath, err)
178 return errOperationFailed
179 }
180 return nil
181}
182
183func loadInstanceInfo(dir string) (*instanceInfo, error) {
184 infoPath := filepath.Join(dir, "info")
185 info := new(instanceInfo)
186 if infoBytes, err := ioutil.ReadFile(infoPath); err != nil {
187 vlog.Errorf("ReadFile(%v) failed: %v", infoPath, err)
188 return nil, errOperationFailed
189 } else if err := json.Unmarshal(infoBytes, info); err != nil {
190 vlog.Errorf("Unmarshal(%v) failed: %v", infoBytes, err)
191 return nil, errOperationFailed
192 }
193 return info, nil
194}
195
196// appInvoker holds the state of an application-related method invocation.
197type appInvoker struct {
198 callback *callbackState
199 config *iconfig.State
200 // suffix contains the name components of the current invocation name
201 // suffix. It is used to identify an application, installation, or
202 // instance.
203 suffix []string
204}
205
206func saveEnvelope(dir string, envelope *application.Envelope) error {
207 jsonEnvelope, err := json.Marshal(envelope)
208 if err != nil {
209 vlog.Errorf("Marshal(%v) failed: %v", envelope, err)
210 return errOperationFailed
211 }
212 envelopePath := filepath.Join(dir, "envelope")
213 if err := ioutil.WriteFile(envelopePath, jsonEnvelope, 0600); err != nil {
214 vlog.Errorf("WriteFile(%v) failed: %v", envelopePath, err)
215 return errOperationFailed
216 }
217 return nil
218}
219
220func loadEnvelope(dir string) (*application.Envelope, error) {
221 envelopePath := filepath.Join(dir, "envelope")
222 envelope := new(application.Envelope)
223 if envelopeBytes, err := ioutil.ReadFile(envelopePath); err != nil {
224 vlog.Errorf("ReadFile(%v) failed: %v", envelopePath, err)
225 return nil, errOperationFailed
226 } else if err := json.Unmarshal(envelopeBytes, envelope); err != nil {
227 vlog.Errorf("Unmarshal(%v) failed: %v", envelopeBytes, err)
228 return nil, errOperationFailed
229 }
230 return envelope, nil
231}
232
233func saveOrigin(dir, originVON string) error {
234 path := filepath.Join(dir, "origin")
235 if err := ioutil.WriteFile(path, []byte(originVON), 0600); err != nil {
236 vlog.Errorf("WriteFile(%v) failed: %v", path, err)
237 return errOperationFailed
238 }
239 return nil
240}
241
242// generateID returns a new unique id string. The uniqueness is based on the
243// current timestamp. Not cryptographically secure.
244func generateID() string {
245 timestamp := fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano))
246 h := crc64.New(crc64.MakeTable(crc64.ISO))
247 h.Write([]byte(timestamp))
248 b := make([]byte, 8)
249 binary.LittleEndian.PutUint64(b, uint64(h.Sum64()))
250 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
251}
252
253// TODO(caprita): Nothing prevents different applications from sharing the same
254// title, and thereby being installed in the same app dir. Do we want to
255// prevent that for the same user or across users?
256
257// applicationDirName generates a cryptographic hash of the application title,
258// to be used as a directory name for installations of the application with the
259// given title.
260func applicationDirName(title string) string {
261 h := md5.New()
262 h.Write([]byte(title))
263 hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
264 return "app-" + hash
265}
266
267func installationDirName(installationID string) string {
268 return "installation-" + installationID
269}
270
271func instanceDirName(instanceID string) string {
272 return "instance-" + instanceID
273}
274
Bogdan Caprita268b4192014-08-28 10:04:44 -0700275func mkdir(dir string) error {
276 perm := os.FileMode(0700)
277 if err := os.MkdirAll(dir, perm); err != nil {
278 vlog.Errorf("MkdirAll(%v, %v) failed: %v", dir, perm, err)
279 return err
280 }
281 return nil
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700282}
283
284func (i *appInvoker) Install(call ipc.ServerContext, applicationVON string) (string, error) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700285 if len(i.suffix) > 0 {
286 return "", errInvalidSuffix
287 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700288 ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
289 defer cancel()
290 envelope, err := fetchEnvelope(ctx, applicationVON)
291 if err != nil {
292 return "", err
293 }
294 if envelope.Title == application.NodeManagerTitle {
295 // Disallow node manager apps from being installed like a
296 // regular app.
297 return "", errInvalidOperation
298 }
299 installationID := generateID()
300 installationDir := filepath.Join(i.config.Root, applicationDirName(envelope.Title), installationDirName(installationID))
301 versionDir := filepath.Join(installationDir, generateVersionDirName())
Bogdan Caprita268b4192014-08-28 10:04:44 -0700302 if err := mkdir(versionDir); err != nil {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700303 return "", errOperationFailed
304 }
305 deferrer := func() {
306 if err := os.RemoveAll(versionDir); err != nil {
307 vlog.Errorf("RemoveAll(%v) failed: %v", versionDir, err)
308 }
309 }
310 defer func() {
311 if deferrer != nil {
312 deferrer()
313 }
314 }()
315 // TODO(caprita): Share binaries if already existing locally.
316 if err := generateBinary(versionDir, "bin", envelope, true); err != nil {
317 return "", err
318 }
319 if err := saveEnvelope(versionDir, envelope); err != nil {
320 return "", err
321 }
322 if err := saveOrigin(versionDir, applicationVON); err != nil {
323 return "", err
324 }
325 link := filepath.Join(installationDir, "current")
326 if err := os.Symlink(versionDir, link); err != nil {
327 vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, link, err)
328 return "", errOperationFailed
329 }
330 deferrer = nil
331 return naming.Join(envelope.Title, installationID), nil
332}
333
334func (*appInvoker) Refresh(ipc.ServerContext) error {
335 // TODO(jsimsa): Implement.
336 return nil
337}
338
339func (*appInvoker) Restart(ipc.ServerContext) error {
340 // TODO(jsimsa): Implement.
341 return nil
342}
343
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700344func (*appInvoker) Revert(ipc.ServerContext) error {
345 // TODO(jsimsa): Implement.
346 return nil
347}
348
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700349func openWriteFile(path string) (*os.File, error) {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700350 perm := os.FileMode(0600)
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700351 file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, perm)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700352 if err != nil {
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700353 vlog.Errorf("OpenFile(%v) failed: %v", path, err)
354 return nil, errOperationFailed
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700355 }
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700356 return file, nil
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700357}
358
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700359// installationDir returns the path to the directory containing the app
360// installation referred to by the invoker's suffix. Returns an error if the
361// suffix does not name an installation or if the named installation does not
362// exist.
363func (i *appInvoker) installationDir() (string, error) {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700364 components := i.suffix
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700365 if nComponents := len(components); nComponents != 2 {
366 return "", errInvalidSuffix
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700367 }
368 app, installation := components[0], components[1]
369 installationDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation))
370 if _, err := os.Stat(installationDir); err != nil {
371 if os.IsNotExist(err) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700372 return "", errNotExist
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700373 }
374 vlog.Errorf("Stat(%v) failed: %v", installationDir, err)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700375 return "", errOperationFailed
376 }
377 return installationDir, nil
378}
379
Bogdan Caprita268b4192014-08-28 10:04:44 -0700380// newInstance sets up the directory for a new application instance.
381func (i *appInvoker) newInstance() (string, string, error) {
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700382 installationDir, err := i.installationDir()
383 if err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700384 return "", "", err
385 }
386 instanceID := generateID()
387 instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
388 if mkdir(instanceDir) != nil {
389 return "", instanceID, errOperationFailed
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700390 }
391 currLink := filepath.Join(installationDir, "current")
Bogdan Caprita268b4192014-08-28 10:04:44 -0700392 versionDir, err := filepath.EvalSymlinks(currLink)
393 if err != nil {
394 vlog.Errorf("EvalSymlinks(%v) failed: %v", currLink, err)
395 return instanceDir, instanceID, err
396 }
397 versionLink := filepath.Join(instanceDir, "version")
398 if err := os.Symlink(versionDir, versionLink); err != nil {
399 vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err)
400 return instanceDir, instanceID, errOperationFailed
401 }
402 if err := initializeState(instanceDir, suspended); err != nil {
403 return instanceDir, instanceID, err
404 }
405 return instanceDir, instanceID, nil
406}
407
408func genCmd(instanceDir string) (*exec.Cmd, error) {
409 versionLink := filepath.Join(instanceDir, "version")
410 versionDir, err := filepath.EvalSymlinks(versionLink)
411 if err != nil {
412 vlog.Errorf("EvalSymlinks(%v) failed: %v", versionLink, err)
413 return nil, errOperationFailed
414 }
415 envelope, err := loadEnvelope(versionDir)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700416 if err != nil {
417 return nil, err
418 }
Bogdan Caprita268b4192014-08-28 10:04:44 -0700419 binPath := filepath.Join(versionDir, "bin")
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700420 if _, err := os.Stat(binPath); err != nil {
421 vlog.Errorf("Stat(%v) failed: %v", binPath, err)
422 return nil, errOperationFailed
423 }
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700424 // TODO(caprita): For the purpose of isolating apps, we should run them
425 // as different users. We'll need to either use the root process or a
426 // suid script to be able to do it.
427 cmd := exec.Command(binPath)
428 // TODO(caprita): Also pass in configuration info like NAMESPACE_ROOT to
429 // the app (to point to the device mounttable).
430 cmd.Env = envelope.Env
431 rootDir := filepath.Join(instanceDir, "root")
432 if err := mkdir(rootDir); err != nil {
433 return nil, err
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700434 }
Bogdan Caprita25d4faa2014-08-28 10:21:23 -0700435 cmd.Dir = rootDir
436 logDir := filepath.Join(instanceDir, "logs")
437 if err := mkdir(logDir); err != nil {
438 return nil, err
439 }
440 timestamp := time.Now().UnixNano()
441 if cmd.Stdout, err = openWriteFile(filepath.Join(logDir, fmt.Sprintf("STDOUT-%d", timestamp))); err != nil {
442 return nil, err
443 }
444 if cmd.Stderr, err = openWriteFile(filepath.Join(logDir, fmt.Sprintf("STDERR-%d", timestamp))); err != nil {
445 return nil, err
446 }
447 // Set up args and env.
448 cmd.Args = append(cmd.Args, "--log_dir=../logs")
449 cmd.Args = append(cmd.Args, envelope.Args...)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700450 return cmd, nil
451}
452
453func (i *appInvoker) startCmd(instanceDir string, cmd *exec.Cmd) error {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700454 // Setup up the child process callback.
455 callbackState := i.callback
Bogdan Caprita78b62162014-08-21 15:35:08 -0700456 listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey)
457 defer listener.cleanup()
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700458 cfg := config.New()
Bogdan Caprita78b62162014-08-21 15:35:08 -0700459 cfg.Set(mgmt.ParentNodeManagerConfigKey, listener.name())
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700460 handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
Bogdan Caprita268b4192014-08-28 10:04:44 -0700461 defer func() {
462 if handle != nil {
463 if err := handle.Clean(); err != nil {
464 vlog.Errorf("Clean() failed: %v", err)
465 }
466 }
467 }()
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700468 // Start the child process.
469 if err := handle.Start(); err != nil {
470 vlog.Errorf("Start() failed: %v", err)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700471 return errOperationFailed
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700472 }
473 // Wait for the child process to start.
Bogdan Caprita78b62162014-08-21 15:35:08 -0700474 timeout := 10 * time.Second
475 if err := handle.WaitForReady(timeout); err != nil {
476 vlog.Errorf("WaitForReady(%v) failed: %v", timeout, err)
Bogdan Caprita268b4192014-08-28 10:04:44 -0700477 return errOperationFailed
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700478 }
Bogdan Caprita78b62162014-08-21 15:35:08 -0700479 childName, err := listener.waitForValue(timeout)
480 if err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700481 return errOperationFailed
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700482 }
Bogdan Caprita78b62162014-08-21 15:35:08 -0700483 instanceInfo := &instanceInfo{
484 AppCycleMgrName: childName,
485 Pid: handle.Pid(),
486 }
487 if err := saveInstanceInfo(instanceDir, instanceInfo); err != nil {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700488 return err
Bogdan Caprita78b62162014-08-21 15:35:08 -0700489 }
490 // TODO(caprita): Spin up a goroutine to reap child status upon exit and
491 // transition it to suspended state if it exits on its own.
Bogdan Caprita268b4192014-08-28 10:04:44 -0700492 handle = nil
493 return nil
494}
495
496func (i *appInvoker) run(instanceDir string) error {
497 if err := transition(instanceDir, suspended, starting); err != nil {
498 return err
499 }
500 cmd, err := genCmd(instanceDir)
501 if err == nil {
502 err = i.startCmd(instanceDir, cmd)
503 }
504 if err != nil {
505 transition(instanceDir, starting, suspended)
506 return err
507 }
508 return transition(instanceDir, starting, started)
509}
510
511func (i *appInvoker) Start(ipc.ServerContext) ([]string, error) {
512 instanceDir, instanceID, err := i.newInstance()
513 if err == nil {
514 err = i.run(instanceDir)
515 }
516 if err != nil {
517 if instanceDir != "" {
518 if err := os.RemoveAll(instanceDir); err != nil {
519 vlog.Errorf("RemoveAll(%v) failed: %v", instanceDir, err)
520 }
521 }
522 return nil, err
523 }
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700524 return []string{instanceID}, nil
525}
526
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700527// instanceDir returns the path to the directory containing the app instance
528// referred to by the invoker's suffix, as well as the corresponding stopped
529// instance dir. Returns an error if the suffix does not name an instance.
Bogdan Caprita268b4192014-08-28 10:04:44 -0700530func (i *appInvoker) instanceDir() (string, error) {
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700531 components := i.suffix
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700532 if nComponents := len(components); nComponents != 3 {
Bogdan Caprita268b4192014-08-28 10:04:44 -0700533 return "", errInvalidSuffix
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700534 }
535 app, installation, instance := components[0], components[1], components[2]
536 instancesDir := filepath.Join(i.config.Root, applicationDirName(app), installationDirName(installation), "instances")
537 instanceDir := filepath.Join(instancesDir, instanceDirName(instance))
Bogdan Caprita268b4192014-08-28 10:04:44 -0700538 return instanceDir, nil
539}
540
541func (i *appInvoker) Resume(ipc.ServerContext) error {
542 instanceDir, err := i.instanceDir()
543 if err != nil {
544 return err
545 }
546 return i.run(instanceDir)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700547}
548
549func stopAppRemotely(appVON string) error {
550 appStub, err := appcycle.BindAppCycle(appVON)
551 if err != nil {
552 vlog.Errorf("BindAppCycle(%v) failed: %v", appVON, err)
553 return errOperationFailed
554 }
555 ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
556 defer cancel()
557 stream, err := appStub.Stop(ctx)
558 if err != nil {
559 vlog.Errorf("%v.Stop() failed: %v", appVON, err)
560 return errOperationFailed
561 }
562 rstream := stream.RecvStream()
563 for rstream.Advance() {
564 vlog.VI(2).Infof("%v.Stop() task update: %v", appVON, rstream.Value())
565 }
566 if err := rstream.Err(); err != nil {
567 vlog.Errorf("Advance() failed: %v", err)
568 return errOperationFailed
569 }
570 if err := stream.Finish(); err != nil {
571 vlog.Errorf("Finish() failed: %v", err)
572 return errOperationFailed
573 }
574 return nil
575}
576
Bogdan Caprita268b4192014-08-28 10:04:44 -0700577func stop(instanceDir string) error {
578 info, err := loadInstanceInfo(instanceDir)
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700579 if err != nil {
580 return err
581 }
Bogdan Caprita2968f4b2014-08-22 14:11:58 -0700582 return stopAppRemotely(info.AppCycleMgrName)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700583}
584
Bogdan Caprita268b4192014-08-28 10:04:44 -0700585// TODO(caprita): implement deadline for Stop.
586
587func (i *appInvoker) Stop(_ ipc.ServerContext, deadline uint32) error {
588 instanceDir, err := i.instanceDir()
589 if err != nil {
590 return err
591 }
592 if err := transition(instanceDir, suspended, stopped); err == errOperationFailed || err == nil {
593 return err
594 }
595 if err := transition(instanceDir, started, stopping); err != nil {
596 return err
597 }
598 if err := stop(instanceDir); err != nil {
599 transition(instanceDir, stopping, started)
600 return err
601 }
602 return transition(instanceDir, stopping, stopped)
603}
604
605func (i *appInvoker) Suspend(ipc.ServerContext) error {
606 instanceDir, err := i.instanceDir()
607 if err != nil {
608 return err
609 }
610 if err := transition(instanceDir, started, suspending); err != nil {
611 return err
612 }
613 if err := stop(instanceDir); err != nil {
614 transition(instanceDir, suspending, started)
615 return err
616 }
617 return transition(instanceDir, suspending, suspended)
Bogdan Caprita4d67c042014-08-19 10:41:19 -0700618}
619
620func (*appInvoker) Uninstall(ipc.ServerContext) error {
621 // TODO(jsimsa): Implement.
622 return nil
623}
624
625func (i *appInvoker) Update(ipc.ServerContext) error {
626 // TODO(jsimsa): Implement.
627 return nil
628}
629
630func (i *appInvoker) UpdateTo(_ ipc.ServerContext, von string) error {
631 // TODO(jsimsa): Implement.
632 return nil
633}