blob: 6935713183a0e1b41fc3e6829305f10eef233890 [file] [log] [blame]
Jiri Simsa70c32052014-06-18 11:38:21 -07001package impl_test
Jiri Simsa5293dcb2014-05-10 09:56:38 -07002
3import (
Bogdan Caprita1e379132014-08-03 23:02:31 -07004 "crypto/md5"
5 "encoding/base64"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07006 "fmt"
Jiri Simsa70c32052014-06-18 11:38:21 -07007 "io/ioutil"
Jiri Simsa5293dcb2014-05-10 09:56:38 -07008 "os"
Bogdan Capritac87a9142014-07-21 10:38:13 -07009 goexec "os/exec"
Jiri Simsa70c32052014-06-18 11:38:21 -070010 "path/filepath"
Jiri Simsa70c32052014-06-18 11:38:21 -070011 "strings"
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070012 "sync"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070013 "testing"
14
15 "veyron/lib/signals"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070016 "veyron/lib/testutil/blackbox"
Jiri Simsae5268b92014-07-11 11:17:57 -070017 "veyron/services/mgmt/lib/exec"
Bogdan Capritac87a9142014-07-21 10:38:13 -070018 "veyron/services/mgmt/node/config"
Jiri Simsa70c32052014-06-18 11:38:21 -070019 "veyron/services/mgmt/node/impl"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070020
Bogdan Caprita1e379132014-08-03 23:02:31 -070021 "veyron2/ipc"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070022 "veyron2/naming"
23 "veyron2/rt"
24 "veyron2/services/mgmt/application"
Bogdan Caprita1e379132014-08-03 23:02:31 -070025 "veyron2/services/mgmt/node"
Bogdan Capritabc4f0982014-07-09 17:20:57 -070026 "veyron2/verror"
Jiri Simsa5293dcb2014-05-10 09:56:38 -070027 "veyron2/vlog"
28)
29
Bogdan Capritac87a9142014-07-21 10:38:13 -070030// TestHelperProcess is blackbox boilerplate.
Jiri Simsa5293dcb2014-05-10 09:56:38 -070031func TestHelperProcess(t *testing.T) {
32 blackbox.HelperProcess(t)
33}
34
Bogdan Capritac87a9142014-07-21 10:38:13 -070035func init() {
36 // All the tests and the subprocesses they start require a runtime; so just
37 // create it here.
38 rt.Init()
39
Bogdan Capritac87a9142014-07-21 10:38:13 -070040 blackbox.CommandTable["execScript"] = execScript
Bogdan Caprita1e379132014-08-03 23:02:31 -070041 blackbox.CommandTable["nodeManager"] = nodeManager
42 blackbox.CommandTable["app"] = app
Bogdan Capritac87a9142014-07-21 10:38:13 -070043}
44
45// execScript launches the script passed as argument.
46func execScript(args []string) {
47 if want, got := 1, len(args); want != got {
48 vlog.Fatalf("execScript expected %d arguments, got %d instead", want, got)
49 }
50 script := args[0]
51 env := []string{}
52 if os.Getenv("PAUSE_BEFORE_STOP") == "1" {
53 env = append(env, "PAUSE_BEFORE_STOP=1")
54 }
55 cmd := goexec.Cmd{
56 Path: script,
57 Env: env,
58 Stderr: os.Stderr,
59 Stdout: os.Stdout,
60 }
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070061 // os.exec.Cmd is not thread safe
62 var cmdLock sync.Mutex
Bogdan Capritac87a9142014-07-21 10:38:13 -070063 go func() {
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070064 cmdLock.Lock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070065 stdin, err := cmd.StdinPipe()
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070066 cmdLock.Unlock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070067 if err != nil {
68 vlog.Fatalf("Failed to get stdin pipe: %v", err)
69 }
70 blackbox.WaitForEOFOnStdin()
71 stdin.Close()
72 }()
Cosmos Nicolaou00b0ecc2014-07-30 12:53:21 -070073 cmdLock.Lock()
74 defer cmdLock.Unlock()
Bogdan Capritac87a9142014-07-21 10:38:13 -070075 if err := cmd.Run(); err != nil {
76 vlog.Fatalf("Run cmd %v failed: %v", cmd, err)
77 }
78}
79
80// nodeManager sets up a node manager server. It accepts the name to publish
81// the server under as an argument. Additional arguments can optionally specify
82// node manager config settings.
83func nodeManager(args []string) {
84 if len(args) == 0 {
85 vlog.Fatalf("nodeManager expected at least an argument")
86 }
87 publishName := args[0]
88
89 defer fmt.Printf("%v terminating\n", publishName)
90 defer rt.R().Cleanup()
91 server, endpoint := newServer()
92 defer server.Stop()
93 name := naming.MakeTerminal(naming.JoinAddressName(endpoint, ""))
94 vlog.VI(1).Infof("Node manager name: %v", name)
95
96 // Satisfy the contract described in doc.go by passing the config state
97 // through to the node manager dispatcher constructor.
98 configState, err := config.Load()
Jiri Simsa5293dcb2014-05-10 09:56:38 -070099 if err != nil {
Bogdan Capritac87a9142014-07-21 10:38:13 -0700100 vlog.Fatalf("Failed to decode config state: %v", err)
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700101 }
Bogdan Capritac87a9142014-07-21 10:38:13 -0700102 configState.Name = name
103
104 // This exemplifies how to override or set specific config fields, if,
105 // for example, the node manager is invoked 'by hand' instead of via a
106 // script prepared by a previous version of the node manager.
107 if len(args) > 1 {
108 if want, got := 3, len(args)-1; want != got {
109 vlog.Fatalf("expected %d additional arguments, got %d instead", want, got)
110 }
111 configState.Root, configState.Origin, configState.CurrentLink = args[1], args[2], args[3]
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700112 }
Bogdan Capritac87a9142014-07-21 10:38:13 -0700113
114 dispatcher, err := impl.NewDispatcher(nil, configState)
115 if err != nil {
116 vlog.Fatalf("Failed to create node manager dispatcher: %v", err)
117 }
118 if err := server.Serve(publishName, dispatcher); err != nil {
119 vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
120 }
121
122 impl.InvokeCallback(name)
123
124 fmt.Println("ready")
125 <-signals.ShutdownOnSignals()
126 if os.Getenv("PAUSE_BEFORE_STOP") == "1" {
127 blackbox.WaitForEOFOnStdin()
128 }
129}
130
Bogdan Caprita1e379132014-08-03 23:02:31 -0700131// appService defines a test service that the test app should be running.
132// TODO(caprita): Use this to make calls to the app and verify how Suspend/Stop
133// interact with an active service.
134type appService struct{}
135
136func (appService) Echo(_ ipc.ServerCall, message string) (string, error) {
137 return message, nil
138}
139
140func app(args []string) {
141 if expected, got := 1, len(args); expected != got {
142 vlog.Fatalf("Unexpected number of arguments: expected %d, got %d", expected, got)
143 }
144 publishName := args[0]
145
146 defer rt.R().Cleanup()
147 server, _ := newServer()
148 defer server.Stop()
149 if err := server.Serve(publishName, ipc.SoloDispatcher(new(appService), nil)); err != nil {
150 vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
151 }
152 if call, err := rt.R().Client().StartCall(rt.R().NewContext(), "pingserver", "Ping", nil); err != nil {
153 vlog.Fatalf("StartCall failed: %v", err)
154 } else if err = call.Finish(); err != nil {
155 vlog.Fatalf("Finish failed: %v", err)
156 }
157 <-signals.ShutdownOnSignals()
158 if err := ioutil.WriteFile("testfile", []byte("goodbye world"), 0600); err != nil {
159 vlog.Fatalf("Failed to write testfile: %v", err)
160 }
161}
162
Bogdan Capritac87a9142014-07-21 10:38:13 -0700163// generateScript is very similar in behavior to its namesake in invoker.go.
164// However, we chose to re-implement it here for two reasons: (1) avoid making
165// generateScript public; and (2) how the test choses to invoke the node manager
166// subprocess the first time should be independent of how node manager
167// implementation sets up its updated versions.
168func generateScript(t *testing.T, root string, cmd *goexec.Cmd) string {
169 output := "#!/bin/bash\n"
170 output += strings.Join(config.QuoteEnv(cmd.Env), " ") + " "
171 output += cmd.Args[0] + " " + strings.Join(cmd.Args[1:], " ")
172 if err := os.MkdirAll(filepath.Join(root, "factory"), 0755); err != nil {
173 t.Fatalf("MkdirAll failed: %v", err)
174 }
175 // Why pigeons? To show that the name we choose for the initial script
176 // doesn't matter and in particular is independent of how node manager
177 // names its updated version scripts (noded.sh).
178 path := filepath.Join(root, "factory", "pigeons.sh")
179 if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
180 t.Fatalf("WriteFile(%v) failed: %v", path, err)
181 }
182 return path
183}
184
Bogdan Caprita1e379132014-08-03 23:02:31 -0700185// nodeEnvelopeFromCmd returns a node manager application envelope that
186// describes the given command object.
187func nodeEnvelopeFromCmd(cmd *goexec.Cmd) *application.Envelope {
188 return envelopeFromCmd(application.NodeManagerTitle, cmd)
189}
190
Bogdan Capritac87a9142014-07-21 10:38:13 -0700191// envelopeFromCmd returns an envelope that describes the given command object.
Bogdan Caprita1e379132014-08-03 23:02:31 -0700192func envelopeFromCmd(title string, cmd *goexec.Cmd) *application.Envelope {
Bogdan Capritac87a9142014-07-21 10:38:13 -0700193 return &application.Envelope{
Bogdan Caprita1e379132014-08-03 23:02:31 -0700194 Title: title,
Bogdan Capritac87a9142014-07-21 10:38:13 -0700195 Args: cmd.Args[1:],
196 Env: cmd.Env,
197 Binary: "br",
198 }
199}
200
Bogdan Caprita1e379132014-08-03 23:02:31 -0700201// TestNodeManagerUpdateAndRevert makes the node manager go through the motions of updating
Bogdan Capritac87a9142014-07-21 10:38:13 -0700202// itself to newer versions (twice), and reverting itself back (twice). It also
203// checks that update and revert fail when they're supposed to. The initial
204// node manager is started 'by hand' via a blackbox command. Further versions
205// are started through the soft link that the node manager itself updates.
Bogdan Caprita1e379132014-08-03 23:02:31 -0700206func TestNodeManagerUpdateAndRevert(t *testing.T) {
Bogdan Capritac87a9142014-07-21 10:38:13 -0700207 // Set up mount table, application, and binary repositories.
208 defer setupLocalNamespace(t)()
209 envelope, cleanup := startApplicationRepository()
210 defer cleanup()
211 defer startBinaryRepository()()
212
213 // This is the local filesystem location that the node manager is told
214 // to use.
Jiri Simsae0fc61a2014-07-30 14:41:55 -0700215 root, perm := filepath.Join(os.TempDir(), "nodemanager"), os.FileMode(0700)
Jiri Simsa2515ed72014-07-30 15:32:32 -0700216 if err := os.MkdirAll(root, perm); err != nil {
217 t.Fatalf("MkdirAll(%v, %v) failed: %v", root, perm, err)
Jiri Simsae0fc61a2014-07-30 14:41:55 -0700218 }
Bogdan Capritac87a9142014-07-21 10:38:13 -0700219 defer os.RemoveAll(root)
220
Jiri Simsae0fc61a2014-07-30 14:41:55 -0700221 // On some operating systems (e.g. darwin) os.TempDir() can
222 // return a symlink. To avoid having to account for this
223 // eventuality later, evaluate the symlink.
224 {
225 var err error
226 root, err = filepath.EvalSymlinks(root)
227 if err != nil {
228 t.Fatalf("EvalSymlinks(%v) failed: %v", root, err)
229 }
230 }
231
Bogdan Capritac87a9142014-07-21 10:38:13 -0700232 // Current link does not have to live in the root dir.
233 currLink := filepath.Join(os.TempDir(), "testcurrent")
234 defer os.Remove(currLink)
235
236 // Set up the initial version of the node manager, the so-called
237 // "factory" version.
238 nm := blackbox.HelperCommand(t, "nodeManager", "factoryNM", root, "ar", currLink)
239 defer setupChildCommand(nm)()
240
241 // This is the script that we'll point the current link to initially.
242 scriptPathFactory := generateScript(t, root, nm.Cmd)
243
244 if err := os.Symlink(scriptPathFactory, currLink); err != nil {
245 t.Fatalf("Symlink(%q, %q) failed: %v", scriptPathFactory, currLink, err)
246 }
247 // We instruct the initial node manager that we run to pause before
248 // stopping its service, so that we get a chance to verify that
249 // attempting an update while another one is ongoing will fail.
250 nm.Cmd.Env = exec.Setenv(nm.Cmd.Env, "PAUSE_BEFORE_STOP", "1")
251
252 resolveExpectError(t, "factoryNM", verror.NotFound) // Ensure a clean slate.
253
254 // Start the node manager -- we use the blackbox-generated command to
255 // start it. We could have also used the scriptPathFactory to start it, but
256 // this demonstrates that the initial node manager could be started by
257 // hand as long as the right initial configuration is passed into the
258 // node manager implementation.
259 if err := nm.Cmd.Start(); err != nil {
260 t.Fatalf("Start() failed: %v", err)
261 }
262 deferrer := nm.Cleanup
263 defer func() {
264 if deferrer != nil {
265 deferrer()
266 }
267 }()
268 nm.Expect("ready")
269 resolve(t, "factoryNM") // Verify the node manager has published itself.
270
271 // Simulate an invalid envelope in the application repository.
Bogdan Caprita1e379132014-08-03 23:02:31 -0700272 *envelope = *nodeEnvelopeFromCmd(nm.Cmd)
Bogdan Capritac87a9142014-07-21 10:38:13 -0700273 envelope.Title = "bogus"
274 updateExpectError(t, "factoryNM", verror.BadArg) // Incorrect title.
275 revertExpectError(t, "factoryNM", verror.NotFound) // No previous version available.
276
277 // Set up a second version of the node manager. We use the blackbox
278 // command solely to collect the args and env we need to provide the
279 // application repository with an envelope that will actually run the
280 // node manager subcommand. The blackbox command is never started by
281 // hand -- instead, the information in the envelope will be used by the
282 // node manager to stage the next version.
283 nmV2 := blackbox.HelperCommand(t, "nodeManager", "v2NM")
284 defer setupChildCommand(nmV2)()
Bogdan Caprita1e379132014-08-03 23:02:31 -0700285 *envelope = *nodeEnvelopeFromCmd(nmV2.Cmd)
Bogdan Capritac87a9142014-07-21 10:38:13 -0700286 update(t, "factoryNM")
287
288 // Current link should have been updated to point to v2.
289 evalLink := func() string {
290 path, err := filepath.EvalSymlinks(currLink)
291 if err != nil {
292 t.Fatalf("EvalSymlinks(%v) failed: %v", currLink, err)
293 }
294 return path
295 }
296 scriptPathV2 := evalLink()
297 if scriptPathFactory == scriptPathV2 {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700298 t.Fatalf("current link didn't change")
Bogdan Capritac87a9142014-07-21 10:38:13 -0700299 }
300
301 // This is from the child node manager started by the node manager
302 // as an update test.
303 nm.Expect("ready")
304 nm.Expect("v2NM terminating")
305
306 updateExpectError(t, "factoryNM", verror.Exists) // Update already in progress.
307
308 nm.CloseStdin()
309 nm.Expect("factoryNM terminating")
310 deferrer = nil
311 nm.Cleanup()
312
313 // A successful update means the node manager has stopped itself. We
314 // relaunch it from the current link.
315 runNM := blackbox.HelperCommand(t, "execScript", currLink)
316 resolveExpectError(t, "v2NM", verror.NotFound) // Ensure a clean slate.
317 if err := runNM.Cmd.Start(); err != nil {
318 t.Fatalf("Start() failed: %v", err)
319 }
320 deferrer = runNM.Cleanup
321 runNM.Expect("ready")
322 resolve(t, "v2NM") // Current link should have been launching v2.
323
324 // Try issuing an update without changing the envelope in the application
325 // repository: this should fail, and current link should be unchanged.
326 updateExpectError(t, "v2NM", verror.NotFound)
327 if evalLink() != scriptPathV2 {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700328 t.Fatalf("script changed")
Bogdan Capritac87a9142014-07-21 10:38:13 -0700329 }
330
331 // Create a third version of the node manager and issue an update.
332 nmV3 := blackbox.HelperCommand(t, "nodeManager", "v3NM")
333 defer setupChildCommand(nmV3)()
Bogdan Caprita1e379132014-08-03 23:02:31 -0700334 *envelope = *nodeEnvelopeFromCmd(nmV3.Cmd)
Bogdan Capritac87a9142014-07-21 10:38:13 -0700335 update(t, "v2NM")
336
337 scriptPathV3 := evalLink()
338 if scriptPathV3 == scriptPathV2 {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700339 t.Fatalf("current link didn't change")
Bogdan Capritac87a9142014-07-21 10:38:13 -0700340 }
341
342 // This is from the child node manager started by the node manager
343 // as an update test.
344 runNM.Expect("ready")
345 // Both the parent and child node manager should terminate upon successful
346 // update.
347 runNM.ExpectSet([]string{"v3NM terminating", "v2NM terminating"})
348
349 deferrer = nil
350 runNM.Cleanup()
351
352 // Re-lanuch the node manager from current link.
353 runNM = blackbox.HelperCommand(t, "execScript", currLink)
354 // We instruct the node manager to pause before stopping its server, so
355 // that we can verify that a second revert fails while a revert is in
356 // progress.
357 runNM.Cmd.Env = exec.Setenv(nm.Cmd.Env, "PAUSE_BEFORE_STOP", "1")
358 resolveExpectError(t, "v3NM", verror.NotFound) // Ensure a clean slate.
359 if err := runNM.Cmd.Start(); err != nil {
360 t.Fatalf("Start() failed: %v", err)
361 }
362 deferrer = runNM.Cleanup
363 runNM.Expect("ready")
364 resolve(t, "v3NM") // Current link should have been launching v3.
365
366 // Revert the node manager to its previous version (v2).
367 revert(t, "v3NM")
368 revertExpectError(t, "v3NM", verror.Exists) // Revert already in progress.
369 nm.CloseStdin()
370 runNM.Expect("v3NM terminating")
371 if evalLink() != scriptPathV2 {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700372 t.Fatalf("current link was not reverted correctly")
Bogdan Capritac87a9142014-07-21 10:38:13 -0700373 }
374 deferrer = nil
375 runNM.Cleanup()
376
377 // Re-launch the node manager from current link.
378 runNM = blackbox.HelperCommand(t, "execScript", currLink)
379 resolveExpectError(t, "v2NM", verror.NotFound) // Ensure a clean slate.
380 if err := runNM.Cmd.Start(); err != nil {
381 t.Fatalf("Start() failed: %v", err)
382 }
383 deferrer = runNM.Cleanup
384 runNM.Expect("ready")
385 resolve(t, "v2NM") // Current link should have been launching v2.
386
387 // Revert the node manager to its previous version (factory).
388 revert(t, "v2NM")
389 runNM.Expect("v2NM terminating")
390 if evalLink() != scriptPathFactory {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700391 t.Fatalf("current link was not reverted correctly")
Bogdan Capritac87a9142014-07-21 10:38:13 -0700392 }
393 deferrer = nil
394 runNM.Cleanup()
395
396 // Re-launch the node manager from current link.
397 runNM = blackbox.HelperCommand(t, "execScript", currLink)
398 resolveExpectError(t, "factoryNM", verror.NotFound) // Ensure a clean slate.
399 if err := runNM.Cmd.Start(); err != nil {
400 t.Fatalf("Start() failed: %v", err)
401 }
402 deferrer = runNM.Cleanup
403 runNM.Expect("ready")
404 resolve(t, "factoryNM") // Current link should have been launching factory version.
Jiri Simsa5293dcb2014-05-10 09:56:38 -0700405}
Bogdan Caprita1e379132014-08-03 23:02:31 -0700406
407type pingServerDisp chan struct{}
408
409func (p pingServerDisp) Ping(ipc.ServerCall) { close(p) }
410
411// TestAppStartStop installs an app, starts it, and then stops it.
412func TestAppStartStop(t *testing.T) {
413 // Set up mount table, application, and binary repositories.
414 defer setupLocalNamespace(t)()
415 envelope, cleanup := startApplicationRepository()
416 defer cleanup()
417 defer startBinaryRepository()()
418
419 // This is the local filesystem location that the node manager is told
420 // to use.
421 root := filepath.Join(os.TempDir(), "nodemanager")
422 defer os.RemoveAll(root)
423
424 // Set up the node manager. Since we won't do node manager updates,
425 // don't worry about its application envelope and current link.
426 nm := blackbox.HelperCommand(t, "nodeManager", "nm", root, "unused app repo name", "unused curr link")
427 defer setupChildCommand(nm)()
428 if err := nm.Cmd.Start(); err != nil {
429 t.Fatalf("Start() failed: %v", err)
430 }
431 defer nm.Cleanup()
432 nm.Expect("ready")
433
434 // Create the local server that the app uses to let us know it's ready.
435 server, _ := newServer()
436 defer server.Stop()
437 pingCh := make(chan struct{})
438 if err := server.Serve("pingserver", ipc.SoloDispatcher(pingServerDisp(pingCh), nil)); err != nil {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700439 t.Fatalf("Failed to set up ping server")
Bogdan Caprita1e379132014-08-03 23:02:31 -0700440 }
441
442 // Create an envelope for an app.
443 app := blackbox.HelperCommand(t, "app", "app1")
444 defer setupChildCommand(app)()
445 appTitle := "google naps"
446 *envelope = *envelopeFromCmd(appTitle, app.Cmd)
447
448 appsName := "nm//apps"
449 stub, err := node.BindApplication(appsName)
450 if err != nil {
451 t.Fatalf("BindApplication(%v) failed: %v", appsName, err)
452 }
453 appID, err := stub.Install(rt.R().NewContext(), "ar")
454 if err != nil {
455 t.Fatalf("Install failed: %v", err)
456 }
457 appName := naming.Join(appsName, appID)
458 stub, err = node.BindApplication(appName)
459 if err != nil {
460 t.Fatalf("BindApplication(%v) failed: %v", appName, err)
461 }
462 var instanceID string
463 if instanceIDs, err := stub.Start(rt.R().NewContext()); err != nil {
464 t.Fatalf("Start failed: %v", err)
465 } else {
466 if want, got := 1, len(instanceIDs); want != got {
467 t.Fatalf("Expected %v instance ids, got %v instead", want, got)
468 }
469 instanceID = instanceIDs[0]
470 }
471 // Wait until the app pings us that it's ready.
472 <-pingCh
473
474 instanceName := naming.Join(appName, instanceID)
475 stub, err = node.BindApplication(instanceName)
476 if err != nil {
477 t.Fatalf("BindApplication(%v) failed: %v", instanceName, err)
478 }
479 if err := stub.Stop(rt.R().NewContext(), 5); err != nil {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700480 t.Fatalf("Stop failed: %v", err)
Bogdan Caprita1e379132014-08-03 23:02:31 -0700481 }
482
483 // HACK ALERT: for now, we peek inside the node manager's directory
484 // structure (which ought to be opaque) to check for what the app has
485 // written to its local root.
486 //
487 // TODO(caprita): add support to node manager to browse logs/app local
488 // root.
489 applicationDirName := func(title string) string {
490 h := md5.New()
491 h.Write([]byte(title))
492 hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
493 return "app-" + hash
494 }
495 components := strings.Split(appID, "/")
496 appTitle, installationID := components[0], components[1]
497 instanceDir := filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "stopped-instance-"+instanceID)
498 rootDir := filepath.Join(instanceDir, "root")
499 testFile := filepath.Join(rootDir, "testfile")
500 if read, err := ioutil.ReadFile(testFile); err != nil {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700501 t.Fatalf("Failed to read %v: %v", testFile, err)
Bogdan Caprita1e379132014-08-03 23:02:31 -0700502 } else if want, got := "goodbye world", string(read); want != got {
Jiri Simsa6351ee72014-08-18 16:44:41 -0700503 t.Fatalf("Expected to read %v, got %v instead", want, got)
Bogdan Caprita1e379132014-08-03 23:02:31 -0700504 }
505 // END HACK
506}