blob: c56ebb3c7e7f48d3170bfbda20e812d2cd3a398a [file] [log] [blame]
Jiri Simsa70c32052014-06-18 11:38:21 -07001package impl_test
Jiri Simsa5293dcb2014-05-10 09:56:38 -07002
3import (
Jiri Simsa5293dcb2014-05-10 09:56:38 -07004 "fmt"
Jiri Simsa70c32052014-06-18 11:38:21 -07005 "io/ioutil"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07006 "os"
Bogdan Capritac87a9142014-07-21 10:38:13 -07007 goexec "os/exec"
Jiri Simsa70c32052014-06-18 11:38:21 -07008 "path/filepath"
Jiri Simsa70c32052014-06-18 11:38:21 -07009 "strings"
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070010 "sync"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070011 "testing"
12
13 "veyron/lib/signals"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070014 "veyron/lib/testutil/blackbox"
Jiri Simsae5268b92014-07-11 11:17:57 -070015 "veyron/services/mgmt/lib/exec"
Bogdan Capritac87a9142014-07-21 10:38:13 -070016 "veyron/services/mgmt/node/config"
Jiri Simsa70c32052014-06-18 11:38:21 -070017 "veyron/services/mgmt/node/impl"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070018
Jiri Simsa5293dcb2014-05-10 09:56:38 -070019 "veyron2/naming"
20 "veyron2/rt"
21 "veyron2/services/mgmt/application"
Bogdan Capritabc4f0982014-07-09 17:20:57 -070022 "veyron2/verror"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070023 "veyron2/vlog"
24)
25
Bogdan Capritac87a9142014-07-21 10:38:13 -070026// TestHelperProcess is blackbox boilerplate.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070027func TestHelperProcess(t *testing.T) {
28 blackbox.HelperProcess(t)
29}
30
Bogdan Capritac87a9142014-07-21 10:38:13 -070031func init() {
32 // All the tests and the subprocesses they start require a runtime; so just
33 // create it here.
34 rt.Init()
35
36 blackbox.CommandTable["nodeManager"] = nodeManager
37 blackbox.CommandTable["execScript"] = execScript
38}
39
40// execScript launches the script passed as argument.
41func execScript(args []string) {
42 if want, got := 1, len(args); want != got {
43 vlog.Fatalf("execScript expected %d arguments, got %d instead", want, got)
44 }
45 script := args[0]
46 env := []string{}
47 if os.Getenv("PAUSE_BEFORE_STOP") == "1" {
48 env = append(env, "PAUSE_BEFORE_STOP=1")
49 }
50 cmd := goexec.Cmd{
51 Path: script,
52 Env: env,
53 Stderr: os.Stderr,
54 Stdout: os.Stdout,
55 }
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070056 // os.exec.Cmd is not thread safe
57 var cmdLock sync.Mutex
Bogdan Capritac87a9142014-07-21 10:38:13 -070058 go func() {
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070059 cmdLock.Lock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070060 stdin, err := cmd.StdinPipe()
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070061 cmdLock.Unlock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070062 if err != nil {
63 vlog.Fatalf("Failed to get stdin pipe: %v", err)
64 }
65 blackbox.WaitForEOFOnStdin()
66 stdin.Close()
67 }()
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070068 cmdLock.Lock()
69 defer cmdLock.Unlock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070070 if err := cmd.Run(); err != nil {
71 vlog.Fatalf("Run cmd %v failed: %v", cmd, err)
72 }
73}
74
75// nodeManager sets up a node manager server. It accepts the name to publish
76// the server under as an argument. Additional arguments can optionally specify
77// node manager config settings.
78func nodeManager(args []string) {
79 if len(args) == 0 {
80 vlog.Fatalf("nodeManager expected at least an argument")
81 }
82 publishName := args[0]
83
84 defer fmt.Printf("%v terminating\n", publishName)
85 defer rt.R().Cleanup()
86 server, endpoint := newServer()
87 defer server.Stop()
88 name := naming.MakeTerminal(naming.JoinAddressName(endpoint, ""))
89 vlog.VI(1).Infof("Node manager name: %v", name)
90
91 // Satisfy the contract described in doc.go by passing the config state
92 // through to the node manager dispatcher constructor.
93 configState, err := config.Load()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070094 if err != nil {
Bogdan Capritac87a9142014-07-21 10:38:13 -070095 vlog.Fatalf("Failed to decode config state: %v", err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -070096 }
Bogdan Capritac87a9142014-07-21 10:38:13 -070097 configState.Name = name
98
99 // This exemplifies how to override or set specific config fields, if,
100 // for example, the node manager is invoked 'by hand' instead of via a
101 // script prepared by a previous version of the node manager.
102 if len(args) > 1 {
103 if want, got := 3, len(args)-1; want != got {
104 vlog.Fatalf("expected %d additional arguments, got %d instead", want, got)
105 }
106 configState.Root, configState.Origin, configState.CurrentLink = args[1], args[2], args[3]
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700107 }
Bogdan Capritac87a9142014-07-21 10:38:13 -0700108
109 dispatcher, err := impl.NewDispatcher(nil, configState)
110 if err != nil {
111 vlog.Fatalf("Failed to create node manager dispatcher: %v", err)
112 }
113 if err := server.Serve(publishName, dispatcher); err != nil {
114 vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
115 }
116
117 impl.InvokeCallback(name)
118
119 fmt.Println("ready")
120 <-signals.ShutdownOnSignals()
121 if os.Getenv("PAUSE_BEFORE_STOP") == "1" {
122 blackbox.WaitForEOFOnStdin()
123 }
124}
125
126// generateScript is very similar in behavior to its namesake in invoker.go.
127// However, we chose to re-implement it here for two reasons: (1) avoid making
128// generateScript public; and (2) how the test choses to invoke the node manager
129// subprocess the first time should be independent of how node manager
130// implementation sets up its updated versions.
131func generateScript(t *testing.T, root string, cmd *goexec.Cmd) string {
132 output := "#!/bin/bash\n"
133 output += strings.Join(config.QuoteEnv(cmd.Env), " ") + " "
134 output += cmd.Args[0] + " " + strings.Join(cmd.Args[1:], " ")
135 if err := os.MkdirAll(filepath.Join(root, "factory"), 0755); err != nil {
136 t.Fatalf("MkdirAll failed: %v", err)
137 }
138 // Why pigeons? To show that the name we choose for the initial script
139 // doesn't matter and in particular is independent of how node manager
140 // names its updated version scripts (noded.sh).
141 path := filepath.Join(root, "factory", "pigeons.sh")
142 if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
143 t.Fatalf("WriteFile(%v) failed: %v", path, err)
144 }
145 return path
146}
147
148// envelopeFromCmd returns an envelope that describes the given command object.
149func envelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
150 return &application.Envelope{
151 Title: application.NodeManagerTitle,
152 Args: cmd.Args[1:],
153 Env: cmd.Env,
154 Binary: "br",
155 }
156}
157
158// TestUpdateAndRevert makes the node manager go through the motions of updating
159// itself to newer versions (twice), and reverting itself back (twice). It also
160// checks that update and revert fail when they're supposed to. The initial
161// node manager is started 'by hand' via a blackbox command. Further versions
162// are started through the soft link that the node manager itself updates.
163func TestUpdateAndRevert(t *testing.T) {
164 // Set up mount table, application, and binary repositories.
165 defer setupLocalNamespace(t)()
166 envelope, cleanup := startApplicationRepository()
167 defer cleanup()
168 defer startBinaryRepository()()
169
170 // This is the local filesystem location that the node manager is told
171 // to use.
Jiri Simsae0fc61a2014-07-30 14:41:55 -0700172 root, perm := filepath.Join(os.TempDir(), "nodemanager"), os.FileMode(0700)
173 if err := os.Mkdir(root, perm); err != nil {
174 t.Fatalf("Mkdir(%v, %v) failed: %v", root, perm, err)
175 }
Bogdan Capritac87a9142014-07-21 10:38:13 -0700176 defer os.RemoveAll(root)
177
Jiri Simsae0fc61a2014-07-30 14:41:55 -0700178 // On some operating systems (e.g. darwin) os.TempDir() can
179 // return a symlink. To avoid having to account for this
180 // eventuality later, evaluate the symlink.
181 {
182 var err error
183 root, err = filepath.EvalSymlinks(root)
184 if err != nil {
185 t.Fatalf("EvalSymlinks(%v) failed: %v", root, err)
186 }
187 }
188
Bogdan Capritac87a9142014-07-21 10:38:13 -0700189 // Current link does not have to live in the root dir.
190 currLink := filepath.Join(os.TempDir(), "testcurrent")
191 defer os.Remove(currLink)
192
193 // Set up the initial version of the node manager, the so-called
194 // "factory" version.
195 nm := blackbox.HelperCommand(t, "nodeManager", "factoryNM", root, "ar", currLink)
196 defer setupChildCommand(nm)()
197
198 // This is the script that we'll point the current link to initially.
199 scriptPathFactory := generateScript(t, root, nm.Cmd)
200
201 if err := os.Symlink(scriptPathFactory, currLink); err != nil {
202 t.Fatalf("Symlink(%q, %q) failed: %v", scriptPathFactory, currLink, err)
203 }
204 // We instruct the initial node manager that we run to pause before
205 // stopping its service, so that we get a chance to verify that
206 // attempting an update while another one is ongoing will fail.
207 nm.Cmd.Env = exec.Setenv(nm.Cmd.Env, "PAUSE_BEFORE_STOP", "1")
208
209 resolveExpectError(t, "factoryNM", verror.NotFound) // Ensure a clean slate.
210
211 // Start the node manager -- we use the blackbox-generated command to
212 // start it. We could have also used the scriptPathFactory to start it, but
213 // this demonstrates that the initial node manager could be started by
214 // hand as long as the right initial configuration is passed into the
215 // node manager implementation.
216 if err := nm.Cmd.Start(); err != nil {
217 t.Fatalf("Start() failed: %v", err)
218 }
219 deferrer := nm.Cleanup
220 defer func() {
221 if deferrer != nil {
222 deferrer()
223 }
224 }()
225 nm.Expect("ready")
226 resolve(t, "factoryNM") // Verify the node manager has published itself.
227
228 // Simulate an invalid envelope in the application repository.
229 *envelope = *envelopeFromCmd(nm.Cmd)
230 envelope.Title = "bogus"
231 updateExpectError(t, "factoryNM", verror.BadArg) // Incorrect title.
232 revertExpectError(t, "factoryNM", verror.NotFound) // No previous version available.
233
234 // Set up a second version of the node manager. We use the blackbox
235 // command solely to collect the args and env we need to provide the
236 // application repository with an envelope that will actually run the
237 // node manager subcommand. The blackbox command is never started by
238 // hand -- instead, the information in the envelope will be used by the
239 // node manager to stage the next version.
240 nmV2 := blackbox.HelperCommand(t, "nodeManager", "v2NM")
241 defer setupChildCommand(nmV2)()
242 *envelope = *envelopeFromCmd(nmV2.Cmd)
243 update(t, "factoryNM")
244
245 // Current link should have been updated to point to v2.
246 evalLink := func() string {
247 path, err := filepath.EvalSymlinks(currLink)
248 if err != nil {
249 t.Fatalf("EvalSymlinks(%v) failed: %v", currLink, err)
250 }
251 return path
252 }
253 scriptPathV2 := evalLink()
254 if scriptPathFactory == scriptPathV2 {
255 t.Errorf("current link didn't change")
256 }
257
258 // This is from the child node manager started by the node manager
259 // as an update test.
260 nm.Expect("ready")
261 nm.Expect("v2NM terminating")
262
263 updateExpectError(t, "factoryNM", verror.Exists) // Update already in progress.
264
265 nm.CloseStdin()
266 nm.Expect("factoryNM terminating")
267 deferrer = nil
268 nm.Cleanup()
269
270 // A successful update means the node manager has stopped itself. We
271 // relaunch it from the current link.
272 runNM := blackbox.HelperCommand(t, "execScript", currLink)
273 resolveExpectError(t, "v2NM", verror.NotFound) // Ensure a clean slate.
274 if err := runNM.Cmd.Start(); err != nil {
275 t.Fatalf("Start() failed: %v", err)
276 }
277 deferrer = runNM.Cleanup
278 runNM.Expect("ready")
279 resolve(t, "v2NM") // Current link should have been launching v2.
280
281 // Try issuing an update without changing the envelope in the application
282 // repository: this should fail, and current link should be unchanged.
283 updateExpectError(t, "v2NM", verror.NotFound)
284 if evalLink() != scriptPathV2 {
285 t.Errorf("script changed")
286 }
287
288 // Create a third version of the node manager and issue an update.
289 nmV3 := blackbox.HelperCommand(t, "nodeManager", "v3NM")
290 defer setupChildCommand(nmV3)()
291 *envelope = *envelopeFromCmd(nmV3.Cmd)
292 update(t, "v2NM")
293
294 scriptPathV3 := evalLink()
295 if scriptPathV3 == scriptPathV2 {
296 t.Errorf("current link didn't change")
297 }
298
299 // This is from the child node manager started by the node manager
300 // as an update test.
301 runNM.Expect("ready")
302 // Both the parent and child node manager should terminate upon successful
303 // update.
304 runNM.ExpectSet([]string{"v3NM terminating", "v2NM terminating"})
305
306 deferrer = nil
307 runNM.Cleanup()
308
309 // Re-lanuch the node manager from current link.
310 runNM = blackbox.HelperCommand(t, "execScript", currLink)
311 // We instruct the node manager to pause before stopping its server, so
312 // that we can verify that a second revert fails while a revert is in
313 // progress.
314 runNM.Cmd.Env = exec.Setenv(nm.Cmd.Env, "PAUSE_BEFORE_STOP", "1")
315 resolveExpectError(t, "v3NM", verror.NotFound) // Ensure a clean slate.
316 if err := runNM.Cmd.Start(); err != nil {
317 t.Fatalf("Start() failed: %v", err)
318 }
319 deferrer = runNM.Cleanup
320 runNM.Expect("ready")
321 resolve(t, "v3NM") // Current link should have been launching v3.
322
323 // Revert the node manager to its previous version (v2).
324 revert(t, "v3NM")
325 revertExpectError(t, "v3NM", verror.Exists) // Revert already in progress.
326 nm.CloseStdin()
327 runNM.Expect("v3NM terminating")
328 if evalLink() != scriptPathV2 {
329 t.Errorf("current link didn't change")
330 }
331 deferrer = nil
332 runNM.Cleanup()
333
334 // Re-launch the node manager from current link.
335 runNM = blackbox.HelperCommand(t, "execScript", currLink)
336 resolveExpectError(t, "v2NM", verror.NotFound) // Ensure a clean slate.
337 if err := runNM.Cmd.Start(); err != nil {
338 t.Fatalf("Start() failed: %v", err)
339 }
340 deferrer = runNM.Cleanup
341 runNM.Expect("ready")
342 resolve(t, "v2NM") // Current link should have been launching v2.
343
344 // Revert the node manager to its previous version (factory).
345 revert(t, "v2NM")
346 runNM.Expect("v2NM terminating")
347 if evalLink() != scriptPathFactory {
348 t.Errorf("current link didn't change")
349 }
350 deferrer = nil
351 runNM.Cleanup()
352
353 // Re-launch the node manager from current link.
354 runNM = blackbox.HelperCommand(t, "execScript", currLink)
355 resolveExpectError(t, "factoryNM", verror.NotFound) // Ensure a clean slate.
356 if err := runNM.Cmd.Start(); err != nil {
357 t.Fatalf("Start() failed: %v", err)
358 }
359 deferrer = runNM.Cleanup
360 runNM.Expect("ready")
361 resolve(t, "factoryNM") // Current link should have been launching factory version.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700362}