blob: 46fd993b90362bbf1a844fa5109e10a871d2e154 [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package impl
2
Jiri Simsa70c32052014-06-18 11:38:21 -07003// The implementation of the node manager expects that the node
4// manager installations are all organized in the following directory
5// structure:
6//
7// VEYRON_NM_ROOT/
8// current # symlink to one of the workspaces
9// workspace-1/
10// noded - the node manager binary
11// noded.sh - a shell script to start the binary
12// ...
13// workspace-n/
14// noded - the node manager binary
15// noded.sh - a shell script to start the binary
16//
17// The node manager is always expected to be started through
18// VEYRON_NM_ROOT/current/noded.sh, which is monitored by an init
19// daemon. This provides for simple and robust updates.
20//
21// To update the node manager to a newer version, a new workspace is
22// created and the "current" symlink is updated
23// accordingly. Similarly, to revert the node manager to a previous
24// version, all that is required is to update the symlink to point to
25// the previous workspace.
26
Jiri Simsa5293dcb2014-05-10 09:56:38 -070027import (
28 "bytes"
29 "errors"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070030 "fmt"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070031 "io"
32 "io/ioutil"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070033 "math/rand"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070034 "os"
35 "os/exec"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070036 "path/filepath"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070037 "reflect"
38 "regexp"
39 "runtime"
40 "strings"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070041 "sync"
Jiri Simsa4b968d92014-05-22 18:36:51 -070042 "time"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070043
Cosmos Nicolaou019270a2014-05-23 08:03:07 -070044 vexec "veyron/lib/exec"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070045 ibuild "veyron/services/mgmt/build"
46 "veyron/services/mgmt/profile"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070047
Jiri Simsa5293dcb2014-05-10 09:56:38 -070048 "veyron2/ipc"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070049 "veyron2/naming"
Jiri Simsa4b968d92014-05-22 18:36:51 -070050 "veyron2/rt"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070051 "veyron2/services/mgmt/application"
52 "veyron2/services/mgmt/build"
53 "veyron2/services/mgmt/content"
54 "veyron2/services/mgmt/node"
55 "veyron2/vlog"
56)
57
Jiri Simsa70c32052014-06-18 11:38:21 -070058const CURRENT = "current"
59
60var appsSuffix = regexp.MustCompile(`^apps\/.*$`)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070061
Jiri Simsa24e87aa2014-06-09 09:27:34 -070062// state wraps state shared between different node manager
63// invocations.
64type state struct {
65 // channels maps callback identifiers to channels that are used to
66 // communicate information from child processes.
67 channels map[string]chan string
68 // channelsMutex is a lock for coordinating concurrent access to
69 // <channels>.
70 channelsMutex *sync.Mutex
Jiri Simsa5293dcb2014-05-10 09:56:38 -070071 // envelope is the node manager application envelope.
72 envelope *application.Envelope
Jiri Simsa24e87aa2014-06-09 09:27:34 -070073 // name is the node manager name.
74 name string
Jiri Simsa70c32052014-06-18 11:38:21 -070075 // previous holds the local path to the previous version of the node
76 // manager.
77 previous string
Jiri Simsa24e87aa2014-06-09 09:27:34 -070078 // updating is a flag that records whether this instance of node
79 // manager is being updated.
80 updating bool
81 // updatingMutex is a lock for coordinating concurrent access to
82 // <updating>.
83 updatingMutex *sync.Mutex
84}
85
86// invoker holds the state of a node manager invocation.
87type invoker struct {
88 state *state
Jiri Simsa5293dcb2014-05-10 09:56:38 -070089 // suffix is the suffix of the current invocation that is assumed to
90 // be used as a relative veyron name to identify an application,
91 // installation, or instance.
92 suffix string
93}
94
95var (
Jiri Simsa24e87aa2014-06-09 09:27:34 -070096 errInvalidSuffix = errors.New("invalid suffix")
97 errOperationFailed = errors.New("operation failed")
98 errUpdateInProgress = errors.New("update in progress")
Jiri Simsa5293dcb2014-05-10 09:56:38 -070099)
100
101// NewInvoker is the invoker factory.
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700102func NewInvoker(state *state, suffix string) *invoker {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700103 return &invoker{
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700104 state: state,
105 suffix: suffix,
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700106 }
107}
108
109// NODE INTERFACE IMPLEMENTATION
110
111// computeNodeProfile generates a description of the runtime
112// environment (supported file format, OS, architecture, libraries) of
113// the host node.
114//
115// TODO(jsimsa): Avoid computing the host node description from
116// scratch if a recent cached copy exists.
117func (i *invoker) computeNodeProfile() (*profile.Specification, error) {
118 result := profile.Specification{Format: profile.Format{Attributes: make(map[string]string)}}
119
120 // Find out what the supported file format, operating system, and
121 // architecture is.
122 switch runtime.GOOS {
123 case "linux":
124 result.Format.Name = ibuild.ELF.String()
125 result.Format.Attributes["os"] = ibuild.LINUX.String()
126 case "darwin":
127 result.Format.Name = ibuild.MACH.String()
128 result.Format.Attributes["os"] = ibuild.DARWIN.String()
129 case "windows":
130 result.Format.Name = ibuild.PE.String()
131 result.Format.Attributes["os"] = ibuild.WINDOWS.String()
132 default:
133 return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
134 }
135 switch runtime.GOARCH {
136 case "amd64":
137 result.Format.Attributes["arch"] = ibuild.AMD64.String()
138 case "arm":
139 result.Format.Attributes["arch"] = ibuild.AMD64.String()
140 case "x86":
141 result.Format.Attributes["arch"] = ibuild.AMD64.String()
142 default:
143 return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
144 }
145
146 // Find out what the installed dynamically linked libraries are.
147 switch runtime.GOOS {
148 case "linux":
149 // For Linux, we identify what dynamically linked libraries are
150 // install by parsing the output of "ldconfig -p".
151 command := exec.Command("ldconfig", "-p")
152 output, err := command.CombinedOutput()
153 if err != nil {
154 return nil, err
155 }
156 buf := bytes.NewBuffer(output)
157 // Throw away the first line of output from ldconfig.
158 if _, err := buf.ReadString('\n'); err != nil {
159 return nil, errors.New("Could not identify libraries.")
160 }
161 // Extract the library name and version from every subsequent line.
162 result.Libraries = make(map[profile.Library]struct{})
163 line, err := buf.ReadString('\n')
164 for err == nil {
165 words := strings.Split(strings.Trim(line, " \t\n"), " ")
166 if len(words) > 0 {
167 tokens := strings.Split(words[0], ".so")
168 if len(tokens) != 2 {
169 return nil, errors.New("Could not identify library: " + words[0])
170 }
171 name := strings.TrimPrefix(tokens[0], "lib")
172 major, minor := "", ""
173 tokens = strings.SplitN(tokens[1], ".", 3)
174 if len(tokens) >= 2 {
175 major = tokens[1]
176 }
177 if len(tokens) >= 3 {
178 minor = tokens[2]
179 }
180 result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
181 }
182 line, err = buf.ReadString('\n')
183 }
184 case "darwin":
185 // TODO(jsimsa): Implement.
186 case "windows":
187 // TODO(jsimsa): Implement.
188 default:
189 return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
190 }
191 return &result, nil
192}
193
194// getProfile gets a profile description for the given profile.
195//
196// TODO(jsimsa): Avoid retrieving the list of known profiles from a
197// remote server if a recent cached copy exists.
198func (i *invoker) getProfile(name string) (*profile.Specification, error) {
199 // TODO(jsimsa): This function assumes the existence of a profile
200 // server from which the profiles can be retrieved. The profile
201 // server is a work in progress. When it exists, the commented out
202 // code below should work.
203 var profile profile.Specification
204 /*
205 client, err := r.NewClient()
206 if err != nil {
207 vlog.Errorf("NewClient() failed: %v", err)
208 return nil, err
209 }
210 defer client.Close()
211 server := // TODO
212 method := "Specification"
213 inputs := make([]interface{}, 0)
214 call, err := client.StartCall(server + "/" + name, method, inputs)
215 if err != nil {
216 vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
217 return nil, err
218 }
219 if err := call.Finish(&profiles); err != nil {
220 vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
221 return nil, err
222 }
223 */
224 return &profile, nil
225}
226
227// getKnownProfiles gets a list of description for all publicly known
228// profiles.
229//
230// TODO(jsimsa): Avoid retrieving the list of known profiles from a
231// remote server if a recent cached copy exists.
232func (i *invoker) getKnownProfiles() ([]profile.Specification, error) {
233 // TODO(jsimsa): This function assumes the existence of a profile
234 // server from which a list of known profiles can be retrieved. The
235 // profile server is a work in progress. When it exists, the
236 // commented out code below should work.
237 knownProfiles := make([]profile.Specification, 0)
238 /*
239 client, err := r.NewClient()
240 if err != nil {
241 vlog.Errorf("NewClient() failed: %v\n", err)
242 return nil, err
243 }
244 defer client.Close()
245 server := // TODO
246 method := "List"
247 inputs := make([]interface{}, 0)
248 call, err := client.StartCall(server, method, inputs)
249 if err != nil {
250 vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
251 return nil, err
252 }
253 if err := call.Finish(&knownProfiles); err != nil {
254 vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
255 return nil, err
256 }
257 */
258 return knownProfiles, nil
259}
260
261// matchProfiles inputs a profile that describes the host node and a
262// set of publicly known profiles and outputs a node description that
263// identifies the publicly known profiles supported by the host node.
264func (i *invoker) matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
265 result := node.Description{Profiles: make(map[string]struct{})}
266loop:
267 for _, profile := range known {
268 if profile.Format.Name != p.Format.Name {
269 continue
270 }
271 if profile.Format.Attributes["os"] != p.Format.Attributes["os"] {
272 continue
273 }
274 if profile.Format.Attributes["arch"] != p.Format.Attributes["arch"] {
275 continue
276 }
277 for library := range profile.Libraries {
278 // Current implementation requires exact library name and version match.
279 if _, found := p.Libraries[library]; !found {
280 continue loop
281 }
282 }
283 result.Profiles[profile.Label] = struct{}{}
284 }
285 return result
286}
287
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700288func (i *invoker) Describe(call ipc.ServerContext) (node.Description, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700289 vlog.VI(0).Infof("%v.Describe()", i.suffix)
290 empty := node.Description{}
291 nodeProfile, err := i.computeNodeProfile()
292 if err != nil {
293 return empty, err
294 }
295 knownProfiles, err := i.getKnownProfiles()
296 if err != nil {
297 return empty, err
298 }
299 result := i.matchProfiles(nodeProfile, knownProfiles)
300 return result, nil
301}
302
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700303func (i *invoker) IsRunnable(call ipc.ServerContext, binary build.BinaryDescription) (bool, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700304 vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, binary)
305 nodeProfile, err := i.computeNodeProfile()
306 if err != nil {
307 return false, err
308 }
309 binaryProfiles := make([]profile.Specification, 0)
310 for name, _ := range binary.Profiles {
311 profile, err := i.getProfile(name)
312 if err != nil {
313 return false, err
314 }
315 binaryProfiles = append(binaryProfiles, *profile)
316 }
317 result := i.matchProfiles(nodeProfile, binaryProfiles)
318 return len(result.Profiles) > 0, nil
319}
320
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700321func (i *invoker) Reset(call ipc.ServerContext, deadline uint64) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700322 vlog.VI(0).Infof("%v.Reset(%v)", i.suffix, deadline)
323 // TODO(jsimsa): Implement.
324 return nil
325}
326
327// APPLICATION INTERFACE IMPLEMENTATION
328
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700329func downloadBinary(workspace, binary string) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700330 stub, err := content.BindRepository(binary)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700331 if err != nil {
332 vlog.Errorf("BindContent(%q) failed: %v", binary, err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700333 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700334 }
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700335 stream, err := stub.Download(rt.R().NewContext())
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700336 if err != nil {
337 vlog.Errorf("Download() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700338 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700339 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700340 path := filepath.Join(workspace, "noded")
341 file, err := os.Create(path)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700342 if err != nil {
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700343 vlog.Errorf("Create(%q) failed: %v", path, err)
344 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700345 }
346 defer file.Close()
347 for {
348 bytes, err := stream.Recv()
349 if err == io.EOF {
350 break
351 }
352 if err != nil {
353 vlog.Errorf("Recv() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700354 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700355 }
356 if _, err := file.Write(bytes); err != nil {
357 vlog.Errorf("Write() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700358 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700359 }
360 }
361 if err := stream.Finish(); err != nil {
362 vlog.Errorf("Finish() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700363 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700364 }
365 mode := os.FileMode(0755)
366 if err := file.Chmod(mode); err != nil {
367 vlog.Errorf("Chmod(%v) failed: %v", mode, err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700368 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700369 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700370 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700371}
372
373func fetchEnvelope(origin string) (*application.Envelope, error) {
374 stub, err := application.BindRepository(origin)
375 if err != nil {
376 vlog.Errorf("BindRepository(%v) failed: %v", origin, err)
377 return nil, errOperationFailed
378 }
379 // TODO(jsimsa): Include logic that computes the set of supported
380 // profiles.
Jiri Simsa52567a02014-05-22 15:25:23 -0700381 profiles := []string{"test"}
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700382 envelope, err := stub.Match(rt.R().NewContext(), profiles)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700383 if err != nil {
384 vlog.Errorf("Match(%v) failed: %v", profiles, err)
385 return nil, errOperationFailed
386 }
387 return &envelope, nil
388}
389
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700390func generateBinary(workspace string, envelope *application.Envelope, newBinary bool) error {
391 if newBinary {
392 // Download the new binary.
393 return downloadBinary(workspace, envelope.Binary)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700394 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700395 // Link the current binary.
396 path := filepath.Join(workspace, "noded")
397 if err := os.Link(os.Args[0], path); err != nil {
398 vlog.Errorf("Link(%v, %v) failed: %v", os.Args[0], path, err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700399 return errOperationFailed
400 }
401 return nil
402}
403
Jiri Simsa70c32052014-06-18 11:38:21 -0700404// TODO(jsimsa): Replace PREVIOUS_ENV with a command-line flag when
405// command-line flags in tests are supported.
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700406func generateScript(workspace string, envelope *application.Envelope) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700407 path, err := filepath.EvalSymlinks(os.Args[0])
408 if err != nil {
409 vlog.Errorf("EvalSymlinks(%v) failed: %v", os.Args[0], err)
410 return errOperationFailed
411 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700412 output := "#!/bin/bash\n"
Jiri Simsa70c32052014-06-18 11:38:21 -0700413 output += PREVIOUS_ENV + "=" + filepath.Dir(path) + " "
414 output += strings.Join(envelope.Env, " ") + " "
415 output += os.Args[0] + " " + strings.Join(envelope.Args, " ")
416 path = filepath.Join(workspace, "noded.sh")
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700417 if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
418 vlog.Errorf("WriteFile(%v) failed: %v", path, err)
419 return errOperationFailed
420 }
421 return nil
422}
423
Jiri Simsa70c32052014-06-18 11:38:21 -0700424// getCurrentFileInfo returns the os.FileInfo for both the symbolic
425// link $VEYRON_NM_ROOT/current and the workspace this link points to.
426func getCurrentFileInfo() (os.FileInfo, os.FileInfo, error) {
427 path := filepath.Join(os.Getenv(ROOT_ENV), CURRENT)
428 link, err := os.Lstat(path)
429 if err != nil {
430 vlog.Errorf("Lstat(%v) failed: %v", path, err)
431 return nil, nil, err
432 }
433 workspace, err := os.Stat(path)
434 if err != nil {
435 vlog.Errorf("Stat(%v) failed: %v", path, err)
436 return nil, nil, err
437 }
438 return link, workspace, nil
439}
440
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700441func updateLink(workspace string) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700442 link := filepath.Join(os.Getenv(ROOT_ENV), CURRENT)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700443 newLink := link + ".new"
444 fi, err := os.Lstat(newLink)
445 if err == nil {
446 if err := os.Remove(fi.Name()); err != nil {
447 vlog.Errorf("Remove(%v) failed: %v", fi.Name(), err)
448 return errOperationFailed
449 }
450 }
451 if err := os.Symlink(workspace, newLink); err != nil {
452 vlog.Errorf("Symlink(%v, %v) failed: %v", workspace, newLink, err)
453 return errOperationFailed
454 }
455 if err := os.Rename(newLink, link); err != nil {
456 vlog.Errorf("Rename(%v, %v) failed: %v", newLink, link, err)
457 return errOperationFailed
458 }
459 return nil
460}
461
462func (i *invoker) registerCallback(id string, channel chan string) {
463 i.state.channelsMutex.Lock()
464 defer i.state.channelsMutex.Unlock()
465 i.state.channels[id] = channel
466}
467
Jiri Simsa70c32052014-06-18 11:38:21 -0700468func (i *invoker) revertNodeManager() error {
469 if err := updateLink(i.state.previous); err != nil {
470 return err
471 }
472 rt.R().Stop()
473 return nil
474}
475
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700476func (i *invoker) testNodeManager(workspace string, envelope *application.Envelope) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700477 path := filepath.Join(workspace, "noded.sh")
478 cmd := exec.Command(path)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700479 cmd.Stdout = os.Stdout
480 cmd.Stderr = os.Stderr
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700481 // Setup up the child process callback.
482 id := fmt.Sprintf("%d", rand.Int())
483 handle := vexec.NewParentHandle(cmd, vexec.CallbackNameOpt(naming.JoinAddressName(i.state.name, id)))
484 callbackChan := make(chan string)
485 i.registerCallback(id, callbackChan)
486 defer i.unregisterCallback(id)
487 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700488 if err := handle.Start(); err != nil {
489 vlog.Errorf("Start() failed: %v", err)
490 return errOperationFailed
491 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700492 // Wait for the child process to start.
Jenkins Veyron7a7704a2014-06-12 00:51:14 +0000493 testTimeout := 10 * time.Second
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700494 if err := handle.WaitForReady(testTimeout); err != nil {
495 vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
496 if err := cmd.Process.Kill(); err != nil {
497 vlog.Errorf("Kill() failed: %v", err)
498 }
Jiri Simsa4b968d92014-05-22 18:36:51 -0700499 return errOperationFailed
500 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700501 // Wait for the child process to invoke the Callback().
502 select {
503 case address := <-callbackChan:
504 // Check that invoking Update() succeeds.
505 address = naming.JoinAddressName(address, "nm")
506 nmClient, err := node.BindNode(address)
507 if err != nil {
508 vlog.Errorf("BindNode(%v) failed: %v", address, err)
Jiri Simsa70c32052014-06-18 11:38:21 -0700509 if err := handle.Clean(); err != nil {
510 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700511 }
512 return errOperationFailed
513 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700514 linkOld, workspaceOld, err := getCurrentFileInfo()
515 if err != nil {
516 if err := handle.Clean(); err != nil {
517 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700518 }
519 return errOperationFailed
520 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700521 if err := nmClient.Revert(rt.R().NewContext()); err != nil {
522 if err := handle.Clean(); err != nil {
523 vlog.Errorf("Clean() failed: %v", err)
524 }
525 return errOperationFailed
526 }
527 linkNew, workspaceNew, err := getCurrentFileInfo()
528 if err != nil {
529 if err := handle.Clean(); err != nil {
530 vlog.Errorf("Clean() failed: %v", err)
531 }
532 return errOperationFailed
533 }
534 // Check that the new node manager updated the symbolic link
535 // $VEYRON_NM_ROOT/current.
536 if !linkOld.ModTime().Before(linkNew.ModTime()) {
537 vlog.Errorf("new node manager test failed")
538 return errOperationFailed
539 }
540 // Check that the symbolic link $VEYRON_NM_ROOT/current points to
541 // the same workspace.
542 if workspaceOld.Name() != workspaceNew.Name() {
543 updateLink(workspaceOld.Name())
544 vlog.Errorf("new node manager test failed")
545 return errOperationFailed
546 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700547 case <-time.After(testTimeout):
548 vlog.Errorf("Waiting for callback timed out")
Jiri Simsa70c32052014-06-18 11:38:21 -0700549 if err := handle.Clean(); err != nil {
550 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700551 }
552 return errOperationFailed
553 }
554 return nil
555}
556
557func (i *invoker) unregisterCallback(id string) {
558 i.state.channelsMutex.Lock()
559 defer i.state.channelsMutex.Unlock()
560 delete(i.state.channels, id)
561}
562
563func (i *invoker) updateNodeManager() error {
564 envelope, err := fetchEnvelope(os.Getenv(ORIGIN_ENV))
565 if err != nil {
566 return err
567 }
568 if !reflect.DeepEqual(envelope, i.state.envelope) {
569 // Create new workspace.
570 workspace := filepath.Join(os.Getenv(ROOT_ENV), fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano)))
571 perm := os.FileMode(0755)
572 if err := os.MkdirAll(workspace, perm); err != nil {
573 vlog.Errorf("MkdirAll(%v, %v) failed: %v", workspace, perm, err)
574 return errOperationFailed
575 }
576 // Populate the new workspace with a node manager binary.
577 if err := generateBinary(workspace, envelope, envelope.Binary != i.state.envelope.Binary); err != nil {
578 if err := os.RemoveAll(workspace); err != nil {
579 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
580 }
581 return err
582 }
583 // Populate the new workspace with a node manager script.
584 if err := generateScript(workspace, envelope); err != nil {
585 if err := os.RemoveAll(workspace); err != nil {
586 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
587 }
588 return err
589 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700590 if err := i.testNodeManager(workspace, envelope); err != nil {
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700591 if err := os.RemoveAll(workspace); err != nil {
592 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
593 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700594 return err
595 }
596 // If the binary has changed, update the node manager symlink.
597 if err := updateLink(workspace); err != nil {
598 if err := os.RemoveAll(workspace); err != nil {
599 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
600 }
601 return err
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700602 }
603 rt.R().Stop()
604 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700605 return nil
606}
607
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700608func (i *invoker) Install(call ipc.ServerContext) (string, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700609 vlog.VI(0).Infof("%v.Install()", i.suffix)
610 // TODO(jsimsa): Implement.
611 return "", nil
612}
613
Jiri Simsa70c32052014-06-18 11:38:21 -0700614func (i *invoker) Refresh(call ipc.ServerContext) error {
615 vlog.VI(0).Infof("%v.Refresh()", i.suffix)
616 // TODO(jsimsa): Implement.
617 return nil
618}
619
620func (i *invoker) Restart(call ipc.ServerContext) error {
621 vlog.VI(0).Infof("%v.Restart()", i.suffix)
622 // TODO(jsimsa): Implement.
623 return nil
624}
625
626func (i *invoker) Resume(call ipc.ServerContext) error {
627 vlog.VI(0).Infof("%v.Resume()", i.suffix)
628 // TODO(jsimsa): Implement.
629 return nil
630}
631
632func (i *invoker) Revert(call ipc.ServerContext) error {
633 vlog.VI(0).Infof("%v.Revert()", i.suffix)
634 if i.state.previous == "" {
635 return errOperationFailed
636 }
637 i.state.updatingMutex.Lock()
638 if i.state.updating {
639 i.state.updatingMutex.Unlock()
640 return errUpdateInProgress
641 } else {
642 i.state.updating = true
643 }
644 i.state.updatingMutex.Unlock()
645 err := i.revertNodeManager()
646 i.state.updatingMutex.Lock()
647 i.state.updating = false
648 i.state.updatingMutex.Unlock()
649 return err
650}
651
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700652func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700653 vlog.VI(0).Infof("%v.Start()", i.suffix)
654 // TODO(jsimsa): Implement.
655 return make([]string, 0), nil
656}
657
Jiri Simsa70c32052014-06-18 11:38:21 -0700658func (i *invoker) Stop(call ipc.ServerContext, deadline uint64) error {
659 vlog.VI(0).Infof("%v.Stop(%v)", i.suffix, deadline)
660 // TODO(jsimsa): Implement.
661 return nil
662}
663
664func (i *invoker) Suspend(call ipc.ServerContext) error {
665 vlog.VI(0).Infof("%v.Suspend()", i.suffix)
666 // TODO(jsimsa): Implement.
667 return nil
668}
669
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700670func (i *invoker) Uninstall(call ipc.ServerContext) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700671 vlog.VI(0).Infof("%v.Uninstall()", i.suffix)
672 // TODO(jsimsa): Implement.
673 return nil
674}
675
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700676func (i *invoker) Update(call ipc.ServerContext) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700677 vlog.VI(0).Infof("%v.Update()", i.suffix)
678 switch {
679 case i.suffix == "nm":
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700680 // This branch attempts to update the node manager itself.
681 i.state.updatingMutex.Lock()
682 if i.state.updating {
683 i.state.updatingMutex.Unlock()
684 return errUpdateInProgress
685 } else {
686 i.state.updating = true
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700687 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700688 i.state.updatingMutex.Unlock()
689 err := i.updateNodeManager()
Jiri Simsa70c32052014-06-18 11:38:21 -0700690 i.state.updatingMutex.Lock()
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700691 i.state.updating = false
Jiri Simsa70c32052014-06-18 11:38:21 -0700692 i.state.updatingMutex.Unlock()
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700693 return err
Jiri Simsa70c32052014-06-18 11:38:21 -0700694 case appsSuffix.MatchString(i.suffix):
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700695 // TODO(jsimsa): Implement.
696 return nil
697 default:
698 return errInvalidSuffix
699 }
700}
701
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700702// CALLBACK RECEIVER INTERFACE IMPLEMENTATION
703
704func (i *invoker) Callback(call ipc.ServerContext, name string) error {
705 vlog.VI(0).Infof("%v.Callback()", i.suffix)
706 i.state.channelsMutex.Lock()
707 channel, ok := i.state.channels[i.suffix]
708 i.state.channelsMutex.Unlock()
709 if !ok {
710 return errInvalidSuffix
711 }
712 channel <- name
713 return nil
714}