blob: 6cbc12a05d7aab7f1d96cb2a07102ea0f4864b12 [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/ioutil"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070032 "math/rand"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070033 "os"
34 "os/exec"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070035 "path/filepath"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070036 "reflect"
37 "regexp"
38 "runtime"
39 "strings"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070040 "sync"
Jiri Simsa4b968d92014-05-22 18:36:51 -070041 "time"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070042
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070043 "veyron/lib/config"
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"
Jiri Simsa51d78fc2014-07-09 18:34:08 -070046 "veyron/services/mgmt/lib/binary"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070047 "veyron/services/mgmt/profile"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070048
Jiri Simsa5293dcb2014-05-10 09:56:38 -070049 "veyron2/ipc"
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -070050 "veyron2/mgmt"
Jiri Simsa24e87aa2014-06-09 09:27:34 -070051 "veyron2/naming"
Jiri Simsa4b968d92014-05-22 18:36:51 -070052 "veyron2/rt"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070053 "veyron2/services/mgmt/application"
54 "veyron2/services/mgmt/build"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070055 "veyron2/services/mgmt/node"
Jiri Simsaddbfebb2014-06-20 15:56:06 -070056 "veyron2/services/mgmt/repository"
Bogdan Caprita55c10122014-07-09 15:35:07 -070057 "veyron2/verror"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070058 "veyron2/vlog"
59)
60
Jiri Simsaa95daec2014-06-20 15:51:59 -070061const CurrentWorkspace = "current"
Jiri Simsa70c32052014-06-18 11:38:21 -070062
Jiri Simsa24e87aa2014-06-09 09:27:34 -070063// state wraps state shared between different node manager
64// invocations.
65type state struct {
66 // channels maps callback identifiers to channels that are used to
67 // communicate information from child processes.
68 channels map[string]chan string
69 // channelsMutex is a lock for coordinating concurrent access to
70 // <channels>.
71 channelsMutex *sync.Mutex
Jiri Simsa5293dcb2014-05-10 09:56:38 -070072 // envelope is the node manager application envelope.
73 envelope *application.Envelope
Jiri Simsa24e87aa2014-06-09 09:27:34 -070074 // name is the node manager name.
75 name string
Jiri Simsa70c32052014-06-18 11:38:21 -070076 // previous holds the local path to the previous version of the node
77 // manager.
78 previous string
Jiri Simsa24e87aa2014-06-09 09:27:34 -070079 // updating is a flag that records whether this instance of node
80 // manager is being updated.
81 updating bool
82 // updatingMutex is a lock for coordinating concurrent access to
83 // <updating>.
84 updatingMutex *sync.Mutex
85}
86
87// invoker holds the state of a node manager invocation.
88type invoker struct {
89 state *state
Jiri Simsa5293dcb2014-05-10 09:56:38 -070090 // suffix is the suffix of the current invocation that is assumed to
Bogdan Capritad9281a32014-07-02 14:40:39 -070091 // be used as a relative object name to identify an application,
Jiri Simsa5293dcb2014-05-10 09:56:38 -070092 // installation, or instance.
93 suffix string
94}
95
96var (
Bogdan Caprita55c10122014-07-09 15:35:07 -070097 appsSuffix = regexp.MustCompile(`^apps\/.*$`)
98
99 errInvalidSuffix = verror.BadArgf("invalid suffix")
100 errOperationFailed = verror.Internalf("operation failed")
101 errUpdateInProgress = verror.Existsf("update in progress")
102 errIncompatibleUpdate = verror.BadArgf("update failed: mismatching app title")
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700103)
104
105// NewInvoker is the invoker factory.
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700106func NewInvoker(state *state, suffix string) *invoker {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700107 return &invoker{
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700108 state: state,
109 suffix: suffix,
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700110 }
111}
112
113// NODE INTERFACE IMPLEMENTATION
114
115// computeNodeProfile generates a description of the runtime
116// environment (supported file format, OS, architecture, libraries) of
117// the host node.
118//
119// TODO(jsimsa): Avoid computing the host node description from
120// scratch if a recent cached copy exists.
121func (i *invoker) computeNodeProfile() (*profile.Specification, error) {
122 result := profile.Specification{Format: profile.Format{Attributes: make(map[string]string)}}
123
124 // Find out what the supported file format, operating system, and
125 // architecture is.
126 switch runtime.GOOS {
127 case "linux":
128 result.Format.Name = ibuild.ELF.String()
129 result.Format.Attributes["os"] = ibuild.LINUX.String()
130 case "darwin":
131 result.Format.Name = ibuild.MACH.String()
132 result.Format.Attributes["os"] = ibuild.DARWIN.String()
133 case "windows":
134 result.Format.Name = ibuild.PE.String()
135 result.Format.Attributes["os"] = ibuild.WINDOWS.String()
136 default:
137 return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
138 }
139 switch runtime.GOARCH {
140 case "amd64":
141 result.Format.Attributes["arch"] = ibuild.AMD64.String()
142 case "arm":
143 result.Format.Attributes["arch"] = ibuild.AMD64.String()
144 case "x86":
145 result.Format.Attributes["arch"] = ibuild.AMD64.String()
146 default:
147 return nil, errors.New("Unsupported hardware architecture: " + runtime.GOARCH)
148 }
149
150 // Find out what the installed dynamically linked libraries are.
151 switch runtime.GOOS {
152 case "linux":
153 // For Linux, we identify what dynamically linked libraries are
154 // install by parsing the output of "ldconfig -p".
155 command := exec.Command("ldconfig", "-p")
156 output, err := command.CombinedOutput()
157 if err != nil {
158 return nil, err
159 }
160 buf := bytes.NewBuffer(output)
161 // Throw away the first line of output from ldconfig.
162 if _, err := buf.ReadString('\n'); err != nil {
163 return nil, errors.New("Could not identify libraries.")
164 }
165 // Extract the library name and version from every subsequent line.
166 result.Libraries = make(map[profile.Library]struct{})
167 line, err := buf.ReadString('\n')
168 for err == nil {
169 words := strings.Split(strings.Trim(line, " \t\n"), " ")
170 if len(words) > 0 {
171 tokens := strings.Split(words[0], ".so")
172 if len(tokens) != 2 {
173 return nil, errors.New("Could not identify library: " + words[0])
174 }
175 name := strings.TrimPrefix(tokens[0], "lib")
176 major, minor := "", ""
177 tokens = strings.SplitN(tokens[1], ".", 3)
178 if len(tokens) >= 2 {
179 major = tokens[1]
180 }
181 if len(tokens) >= 3 {
182 minor = tokens[2]
183 }
184 result.Libraries[profile.Library{Name: name, MajorVersion: major, MinorVersion: minor}] = struct{}{}
185 }
186 line, err = buf.ReadString('\n')
187 }
188 case "darwin":
189 // TODO(jsimsa): Implement.
190 case "windows":
191 // TODO(jsimsa): Implement.
192 default:
193 return nil, errors.New("Unsupported operating system: " + runtime.GOOS)
194 }
195 return &result, nil
196}
197
198// getProfile gets a profile description for the given profile.
199//
200// TODO(jsimsa): Avoid retrieving the list of known profiles from a
201// remote server if a recent cached copy exists.
202func (i *invoker) getProfile(name string) (*profile.Specification, error) {
203 // TODO(jsimsa): This function assumes the existence of a profile
204 // server from which the profiles can be retrieved. The profile
205 // server is a work in progress. When it exists, the commented out
206 // code below should work.
207 var profile profile.Specification
208 /*
209 client, err := r.NewClient()
210 if err != nil {
211 vlog.Errorf("NewClient() failed: %v", err)
212 return nil, err
213 }
214 defer client.Close()
215 server := // TODO
216 method := "Specification"
217 inputs := make([]interface{}, 0)
218 call, err := client.StartCall(server + "/" + name, method, inputs)
219 if err != nil {
220 vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server + "/" + name, method, inputs, err)
221 return nil, err
222 }
223 if err := call.Finish(&profiles); err != nil {
224 vlog.Errorf("Finish(%v) failed: %v\n", &profiles, err)
225 return nil, err
226 }
227 */
228 return &profile, nil
229}
230
231// getKnownProfiles gets a list of description for all publicly known
232// profiles.
233//
234// TODO(jsimsa): Avoid retrieving the list of known profiles from a
235// remote server if a recent cached copy exists.
236func (i *invoker) getKnownProfiles() ([]profile.Specification, error) {
237 // TODO(jsimsa): This function assumes the existence of a profile
238 // server from which a list of known profiles can be retrieved. The
239 // profile server is a work in progress. When it exists, the
240 // commented out code below should work.
241 knownProfiles := make([]profile.Specification, 0)
242 /*
243 client, err := r.NewClient()
244 if err != nil {
245 vlog.Errorf("NewClient() failed: %v\n", err)
246 return nil, err
247 }
248 defer client.Close()
249 server := // TODO
250 method := "List"
251 inputs := make([]interface{}, 0)
252 call, err := client.StartCall(server, method, inputs)
253 if err != nil {
254 vlog.Errorf("StartCall(%s, %q, %v) failed: %v\n", server, method, inputs, err)
255 return nil, err
256 }
257 if err := call.Finish(&knownProfiles); err != nil {
258 vlog.Errorf("Finish(&knownProfile) failed: %v\n", err)
259 return nil, err
260 }
261 */
262 return knownProfiles, nil
263}
264
265// matchProfiles inputs a profile that describes the host node and a
266// set of publicly known profiles and outputs a node description that
267// identifies the publicly known profiles supported by the host node.
268func (i *invoker) matchProfiles(p *profile.Specification, known []profile.Specification) node.Description {
269 result := node.Description{Profiles: make(map[string]struct{})}
270loop:
271 for _, profile := range known {
272 if profile.Format.Name != p.Format.Name {
273 continue
274 }
275 if profile.Format.Attributes["os"] != p.Format.Attributes["os"] {
276 continue
277 }
278 if profile.Format.Attributes["arch"] != p.Format.Attributes["arch"] {
279 continue
280 }
281 for library := range profile.Libraries {
282 // Current implementation requires exact library name and version match.
283 if _, found := p.Libraries[library]; !found {
284 continue loop
285 }
286 }
287 result.Profiles[profile.Label] = struct{}{}
288 }
289 return result
290}
291
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700292func (i *invoker) Describe(call ipc.ServerContext) (node.Description, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700293 vlog.VI(0).Infof("%v.Describe()", i.suffix)
294 empty := node.Description{}
295 nodeProfile, err := i.computeNodeProfile()
296 if err != nil {
297 return empty, err
298 }
299 knownProfiles, err := i.getKnownProfiles()
300 if err != nil {
301 return empty, err
302 }
303 result := i.matchProfiles(nodeProfile, knownProfiles)
304 return result, nil
305}
306
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700307func (i *invoker) IsRunnable(call ipc.ServerContext, binary build.BinaryDescription) (bool, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700308 vlog.VI(0).Infof("%v.IsRunnable(%v)", i.suffix, binary)
309 nodeProfile, err := i.computeNodeProfile()
310 if err != nil {
311 return false, err
312 }
313 binaryProfiles := make([]profile.Specification, 0)
314 for name, _ := range binary.Profiles {
315 profile, err := i.getProfile(name)
316 if err != nil {
317 return false, err
318 }
319 binaryProfiles = append(binaryProfiles, *profile)
320 }
321 result := i.matchProfiles(nodeProfile, binaryProfiles)
322 return len(result.Profiles) > 0, nil
323}
324
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700325func (i *invoker) Reset(call ipc.ServerContext, deadline uint64) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700326 vlog.VI(0).Infof("%v.Reset(%v)", i.suffix, deadline)
327 // TODO(jsimsa): Implement.
328 return nil
329}
330
331// APPLICATION INTERFACE IMPLEMENTATION
332
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700333func downloadBinary(workspace, name string) error {
334 data, err := binary.Download(name)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700335 if err != nil {
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700336 vlog.Errorf("Download(%v) failed: %v", name, err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700337 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700338 }
Jiri Simsa51d78fc2014-07-09 18:34:08 -0700339 path, perm := filepath.Join(workspace, "noded"), os.FileMode(755)
340 if err := ioutil.WriteFile(path, data, perm); err != nil {
341 vlog.Errorf("WriteFile(%v, %v) failed: %v", path, perm, err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700342 return errOperationFailed
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700343 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700344 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700345}
346
347func fetchEnvelope(origin string) (*application.Envelope, error) {
Jiri Simsaddbfebb2014-06-20 15:56:06 -0700348 stub, err := repository.BindApplication(origin)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700349 if err != nil {
350 vlog.Errorf("BindRepository(%v) failed: %v", origin, err)
351 return nil, errOperationFailed
352 }
353 // TODO(jsimsa): Include logic that computes the set of supported
354 // profiles.
Jiri Simsa52567a02014-05-22 15:25:23 -0700355 profiles := []string{"test"}
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700356 envelope, err := stub.Match(rt.R().NewContext(), profiles)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700357 if err != nil {
358 vlog.Errorf("Match(%v) failed: %v", profiles, err)
359 return nil, errOperationFailed
360 }
361 return &envelope, nil
362}
363
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700364func generateBinary(workspace string, envelope *application.Envelope, newBinary bool) error {
365 if newBinary {
366 // Download the new binary.
367 return downloadBinary(workspace, envelope.Binary)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700368 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700369 // Link the current binary.
370 path := filepath.Join(workspace, "noded")
371 if err := os.Link(os.Args[0], path); err != nil {
372 vlog.Errorf("Link(%v, %v) failed: %v", os.Args[0], path, err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700373 return errOperationFailed
374 }
375 return nil
376}
377
Jiri Simsaa95daec2014-06-20 15:51:59 -0700378// TODO(jsimsa): Replace <PreviousEnv> with a command-line flag when
Jiri Simsa70c32052014-06-18 11:38:21 -0700379// command-line flags in tests are supported.
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700380func generateScript(workspace string, envelope *application.Envelope) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700381 path, err := filepath.EvalSymlinks(os.Args[0])
382 if err != nil {
383 vlog.Errorf("EvalSymlinks(%v) failed: %v", os.Args[0], err)
384 return errOperationFailed
385 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700386 output := "#!/bin/bash\n"
Jiri Simsaa95daec2014-06-20 15:51:59 -0700387 output += PreviousEnv + "=" + filepath.Dir(path) + " "
Jiri Simsa70c32052014-06-18 11:38:21 -0700388 output += strings.Join(envelope.Env, " ") + " "
Jiri Simsa1adc1ba2014-07-10 16:08:08 -0700389 output += filepath.Join(workspace, "noded") + " "
390 output += strings.Join(envelope.Args, " ")
Jiri Simsa70c32052014-06-18 11:38:21 -0700391 path = filepath.Join(workspace, "noded.sh")
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700392 if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
393 vlog.Errorf("WriteFile(%v) failed: %v", path, err)
394 return errOperationFailed
395 }
396 return nil
397}
398
Jiri Simsa70c32052014-06-18 11:38:21 -0700399// getCurrentFileInfo returns the os.FileInfo for both the symbolic
400// link $VEYRON_NM_ROOT/current and the workspace this link points to.
401func getCurrentFileInfo() (os.FileInfo, os.FileInfo, error) {
Jiri Simsaa95daec2014-06-20 15:51:59 -0700402 path := filepath.Join(os.Getenv(RootEnv), CurrentWorkspace)
Jiri Simsa70c32052014-06-18 11:38:21 -0700403 link, err := os.Lstat(path)
404 if err != nil {
405 vlog.Errorf("Lstat(%v) failed: %v", path, err)
406 return nil, nil, err
407 }
408 workspace, err := os.Stat(path)
409 if err != nil {
410 vlog.Errorf("Stat(%v) failed: %v", path, err)
411 return nil, nil, err
412 }
413 return link, workspace, nil
414}
415
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700416func updateLink(workspace string) error {
Jiri Simsaa95daec2014-06-20 15:51:59 -0700417 link := filepath.Join(os.Getenv(RootEnv), CurrentWorkspace)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700418 newLink := link + ".new"
419 fi, err := os.Lstat(newLink)
420 if err == nil {
421 if err := os.Remove(fi.Name()); err != nil {
422 vlog.Errorf("Remove(%v) failed: %v", fi.Name(), err)
423 return errOperationFailed
424 }
425 }
426 if err := os.Symlink(workspace, newLink); err != nil {
427 vlog.Errorf("Symlink(%v, %v) failed: %v", workspace, newLink, err)
428 return errOperationFailed
429 }
430 if err := os.Rename(newLink, link); err != nil {
431 vlog.Errorf("Rename(%v, %v) failed: %v", newLink, link, err)
432 return errOperationFailed
433 }
434 return nil
435}
436
437func (i *invoker) registerCallback(id string, channel chan string) {
438 i.state.channelsMutex.Lock()
439 defer i.state.channelsMutex.Unlock()
440 i.state.channels[id] = channel
441}
442
Jiri Simsa70c32052014-06-18 11:38:21 -0700443func (i *invoker) revertNodeManager() error {
444 if err := updateLink(i.state.previous); err != nil {
445 return err
446 }
447 rt.R().Stop()
448 return nil
449}
450
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700451func (i *invoker) testNodeManager(workspace string, envelope *application.Envelope) error {
Jiri Simsa70c32052014-06-18 11:38:21 -0700452 path := filepath.Join(workspace, "noded.sh")
453 cmd := exec.Command(path)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700454 cmd.Stdout = os.Stdout
455 cmd.Stderr = os.Stderr
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700456 // Setup up the child process callback.
457 id := fmt.Sprintf("%d", rand.Int())
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700458 cfg := config.New()
459 cfg.Set(mgmt.ParentNodeManagerConfigKey, naming.JoinAddressName(i.state.name, id))
460 handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700461 callbackChan := make(chan string)
462 i.registerCallback(id, callbackChan)
463 defer i.unregisterCallback(id)
464 // Start the child process.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700465 if err := handle.Start(); err != nil {
466 vlog.Errorf("Start() failed: %v", err)
467 return errOperationFailed
468 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700469 // Wait for the child process to start.
Jenkins Veyron7a7704a2014-06-12 00:51:14 +0000470 testTimeout := 10 * time.Second
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700471 if err := handle.WaitForReady(testTimeout); err != nil {
472 vlog.Errorf("WaitForReady(%v) failed: %v", testTimeout, err)
473 if err := cmd.Process.Kill(); err != nil {
474 vlog.Errorf("Kill() failed: %v", err)
475 }
Jiri Simsa4b968d92014-05-22 18:36:51 -0700476 return errOperationFailed
477 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700478 // Wait for the child process to invoke the Callback().
479 select {
480 case address := <-callbackChan:
481 // Check that invoking Update() succeeds.
482 address = naming.JoinAddressName(address, "nm")
483 nmClient, err := node.BindNode(address)
484 if err != nil {
485 vlog.Errorf("BindNode(%v) failed: %v", address, err)
Jiri Simsa70c32052014-06-18 11:38:21 -0700486 if err := handle.Clean(); err != nil {
487 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700488 }
489 return errOperationFailed
490 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700491 linkOld, workspaceOld, err := getCurrentFileInfo()
492 if err != nil {
493 if err := handle.Clean(); err != nil {
494 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700495 }
496 return errOperationFailed
497 }
Jiri Simsa76e34592014-06-25 14:24:06 -0700498 // Since the resolution of mtime for files is seconds,
499 // the test sleeps for a second to make sure it can
500 // check whether the "current" symlink is updated.
501 time.Sleep(time.Second)
Jiri Simsa70c32052014-06-18 11:38:21 -0700502 if err := nmClient.Revert(rt.R().NewContext()); err != nil {
503 if err := handle.Clean(); err != nil {
504 vlog.Errorf("Clean() failed: %v", err)
505 }
506 return errOperationFailed
507 }
508 linkNew, workspaceNew, err := getCurrentFileInfo()
509 if err != nil {
510 if err := handle.Clean(); err != nil {
511 vlog.Errorf("Clean() failed: %v", err)
512 }
513 return errOperationFailed
514 }
515 // Check that the new node manager updated the symbolic link
516 // $VEYRON_NM_ROOT/current.
517 if !linkOld.ModTime().Before(linkNew.ModTime()) {
518 vlog.Errorf("new node manager test failed")
519 return errOperationFailed
520 }
521 // Check that the symbolic link $VEYRON_NM_ROOT/current points to
522 // the same workspace.
523 if workspaceOld.Name() != workspaceNew.Name() {
524 updateLink(workspaceOld.Name())
525 vlog.Errorf("new node manager test failed")
526 return errOperationFailed
527 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700528 case <-time.After(testTimeout):
529 vlog.Errorf("Waiting for callback timed out")
Jiri Simsa70c32052014-06-18 11:38:21 -0700530 if err := handle.Clean(); err != nil {
531 vlog.Errorf("Clean() failed: %v", err)
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700532 }
533 return errOperationFailed
534 }
535 return nil
536}
537
538func (i *invoker) unregisterCallback(id string) {
539 i.state.channelsMutex.Lock()
540 defer i.state.channelsMutex.Unlock()
541 delete(i.state.channels, id)
542}
543
544func (i *invoker) updateNodeManager() error {
Jiri Simsaa95daec2014-06-20 15:51:59 -0700545 envelope, err := fetchEnvelope(os.Getenv(OriginEnv))
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700546 if err != nil {
547 return err
548 }
Bogdan Caprita55c10122014-07-09 15:35:07 -0700549 if envelope.Title != application.NodeManagerTitle {
550 return errIncompatibleUpdate
551 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700552 if !reflect.DeepEqual(envelope, i.state.envelope) {
553 // Create new workspace.
Jiri Simsaa95daec2014-06-20 15:51:59 -0700554 workspace := filepath.Join(os.Getenv(RootEnv), fmt.Sprintf("%v", time.Now().Format(time.RFC3339Nano)))
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700555 perm := os.FileMode(0755)
556 if err := os.MkdirAll(workspace, perm); err != nil {
557 vlog.Errorf("MkdirAll(%v, %v) failed: %v", workspace, perm, err)
558 return errOperationFailed
559 }
560 // Populate the new workspace with a node manager binary.
561 if err := generateBinary(workspace, envelope, envelope.Binary != i.state.envelope.Binary); err != nil {
562 if err := os.RemoveAll(workspace); err != nil {
563 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
564 }
565 return err
566 }
567 // Populate the new workspace with a node manager script.
568 if err := generateScript(workspace, envelope); err != nil {
569 if err := os.RemoveAll(workspace); err != nil {
570 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
571 }
572 return err
573 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700574 if err := i.testNodeManager(workspace, envelope); err != nil {
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700575 if err := os.RemoveAll(workspace); err != nil {
576 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
577 }
Jiri Simsa70c32052014-06-18 11:38:21 -0700578 return err
579 }
580 // If the binary has changed, update the node manager symlink.
581 if err := updateLink(workspace); err != nil {
582 if err := os.RemoveAll(workspace); err != nil {
583 vlog.Errorf("RemoveAll(%v) failed: %v", workspace, err)
584 }
585 return err
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700586 }
587 rt.R().Stop()
588 }
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700589 return nil
590}
591
Bogdan Caprita55c10122014-07-09 15:35:07 -0700592func (i *invoker) Install(call ipc.ServerContext, von string) (string, error) {
593 vlog.VI(0).Infof("%v.Install(%q)", i.suffix, von)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700594 // TODO(jsimsa): Implement.
595 return "", nil
596}
597
Jiri Simsa70c32052014-06-18 11:38:21 -0700598func (i *invoker) Refresh(call ipc.ServerContext) error {
599 vlog.VI(0).Infof("%v.Refresh()", i.suffix)
600 // TODO(jsimsa): Implement.
601 return nil
602}
603
604func (i *invoker) Restart(call ipc.ServerContext) error {
605 vlog.VI(0).Infof("%v.Restart()", i.suffix)
606 // TODO(jsimsa): Implement.
607 return nil
608}
609
610func (i *invoker) Resume(call ipc.ServerContext) error {
611 vlog.VI(0).Infof("%v.Resume()", i.suffix)
612 // TODO(jsimsa): Implement.
613 return nil
614}
615
616func (i *invoker) Revert(call ipc.ServerContext) error {
617 vlog.VI(0).Infof("%v.Revert()", i.suffix)
618 if i.state.previous == "" {
619 return errOperationFailed
620 }
621 i.state.updatingMutex.Lock()
622 if i.state.updating {
623 i.state.updatingMutex.Unlock()
624 return errUpdateInProgress
625 } else {
626 i.state.updating = true
627 }
628 i.state.updatingMutex.Unlock()
629 err := i.revertNodeManager()
630 i.state.updatingMutex.Lock()
631 i.state.updating = false
632 i.state.updatingMutex.Unlock()
633 return err
634}
635
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700636func (i *invoker) Start(call ipc.ServerContext) ([]string, error) {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700637 vlog.VI(0).Infof("%v.Start()", i.suffix)
638 // TODO(jsimsa): Implement.
639 return make([]string, 0), nil
640}
641
Jiri Simsa70c32052014-06-18 11:38:21 -0700642func (i *invoker) Stop(call ipc.ServerContext, deadline uint64) error {
643 vlog.VI(0).Infof("%v.Stop(%v)", i.suffix, deadline)
644 // TODO(jsimsa): Implement.
645 return nil
646}
647
648func (i *invoker) Suspend(call ipc.ServerContext) error {
649 vlog.VI(0).Infof("%v.Suspend()", i.suffix)
650 // TODO(jsimsa): Implement.
651 return nil
652}
653
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700654func (i *invoker) Uninstall(call ipc.ServerContext) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700655 vlog.VI(0).Infof("%v.Uninstall()", i.suffix)
656 // TODO(jsimsa): Implement.
657 return nil
658}
659
Matt Rosencrantzf5afcaf2014-06-02 11:31:22 -0700660func (i *invoker) Update(call ipc.ServerContext) error {
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700661 vlog.VI(0).Infof("%v.Update()", i.suffix)
662 switch {
663 case i.suffix == "nm":
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700664 // This branch attempts to update the node manager itself.
665 i.state.updatingMutex.Lock()
666 if i.state.updating {
667 i.state.updatingMutex.Unlock()
668 return errUpdateInProgress
669 } else {
670 i.state.updating = true
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700671 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700672 i.state.updatingMutex.Unlock()
673 err := i.updateNodeManager()
Jiri Simsa70c32052014-06-18 11:38:21 -0700674 i.state.updatingMutex.Lock()
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700675 i.state.updating = false
Jiri Simsa70c32052014-06-18 11:38:21 -0700676 i.state.updatingMutex.Unlock()
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700677 return err
Jiri Simsa70c32052014-06-18 11:38:21 -0700678 case appsSuffix.MatchString(i.suffix):
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700679 // TODO(jsimsa): Implement.
680 return nil
681 default:
682 return errInvalidSuffix
683 }
Bogdan Caprita55c10122014-07-09 15:35:07 -0700684
685}
686
687func (i *invoker) UpdateTo(call ipc.ServerContext, von string) error {
688 vlog.VI(0).Infof("%v.UpdateTo(%q)", i.suffix, von)
689 // TODO(jsimsa): Implement.
690 return nil
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700691}
692
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700693// CONFIG INTERFACE IMPLEMENTATION
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700694
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700695func (i *invoker) Set(_ ipc.ServerContext, key, value string) error {
696 vlog.VI(0).Infof("%v.Set(%v, %v)", i.suffix, key, value)
697 // For now, only handle the child node manager name. We'll add handling
698 // for the child's app cycle manager name later on.
699 if key != mgmt.ChildNodeManagerConfigKey {
700 return nil
701 }
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700702 i.state.channelsMutex.Lock()
703 channel, ok := i.state.channels[i.suffix]
704 i.state.channelsMutex.Unlock()
705 if !ok {
706 return errInvalidSuffix
707 }
Bogdan Capritaa4d9ee42014-06-20 16:42:53 -0700708 channel <- value
Jiri Simsa24e87aa2014-06-09 09:27:34 -0700709 return nil
710}