func testApp(s *CLISuite, t *c.C, remote string) { app := s.newGitRepo(t, "") name := random.String(30) flynnRemote := fmt.Sprintf("%s\t%s/%s.git (push)", remote, s.clusterConf(t).GitURL, name) if remote == "flynn" { t.Assert(app.flynn("create", "-y", name), Outputs, fmt.Sprintf("Created %s\n", name)) } else { t.Assert(app.flynn("create", "-r", remote, "-y", name), Outputs, fmt.Sprintf("Created %s\n", name)) } t.Assert(app.flynn("apps"), SuccessfulOutputContains, name) t.Assert(app.flynn("-c", "default", "apps"), SuccessfulOutputContains, name) if remote == "" { t.Assert(app.git("remote", "-v"), c.Not(SuccessfulOutputContains), flynnRemote) } else { t.Assert(app.git("remote", "-v"), SuccessfulOutputContains, flynnRemote) } // make sure flynn components are listed t.Assert(app.flynn("apps"), SuccessfulOutputContains, "router") t.Assert(app.flynn("-c", "default", "apps"), SuccessfulOutputContains, "router") // flynn delete if remote == "flynn" { t.Assert(app.flynn("delete", "--yes"), Succeeds) } else { if remote == "" { t.Assert(app.flynn("-a", name, "delete", "--yes", "-r", remote), Succeeds) } else { t.Assert(app.flynn("delete", "--yes", "-r", remote), Succeeds) } } t.Assert(app.git("remote", "-v"), c.Not(SuccessfulOutputContains), flynnRemote) }
func (s *CLISuite) TestReleaseRollback(t *c.C) { // create an app and release it r := s.newGitRepo(t, "http") app := "release-rollback-" + random.String(8) t.Assert(r.flynn("create", app), Succeeds) t.Assert(r.git("push", "flynn", "master"), Succeeds) // check that rollback fails when there's only a single release res := r.flynn("release", "rollback", "--yes") t.Assert(res, c.Not(Succeeds)) // create a second release t.Assert(r.git("commit", "--allow-empty", "--message", "empty commit"), Succeeds) t.Assert(r.git("push", "flynn", "master"), Succeeds) // get the releases client := s.controllerClient(t) releases, err := client.AppReleaseList(app) t.Assert(err, c.IsNil) t.Assert(releases, c.HasLen, 2) // rollback to the second release res = r.flynn("release", "rollback", "--yes") t.Assert(res, Succeeds) // revert rollback res = r.flynn("release", "rollback", "--yes", releases[0].ID) t.Assert(res, Succeeds) // check that attempting to rollback to the current release fails res = r.flynn("release", "rollback", "--yes", releases[0].ID) t.Assert(res, c.Not(Succeeds)) }
func (s *ControllerSuite) TestBackup(t *c.C) { client := s.controllerClient(t) out, err := client.Backup() t.Assert(err, c.IsNil) defer out.Close() data := make(map[string][]byte) tr := tar.NewReader(out) for { h, err := tr.Next() if err == io.EOF { break } t.Assert(err, c.IsNil) b := make([]byte, h.Size) _, err = tr.Read(b) t.Assert(err, c.IsNil) _, filename := filepath.Split(h.Name) data[filename] = b } sql, ok := data["postgres.sql.gz"] t.Assert(ok, c.Equals, true) t.Assert(len(sql) > 0, c.Equals, true) flynn, ok := data["flynn.json"] t.Assert(ok, c.Equals, true) var apps map[string]*ct.ExpandedFormation t.Assert(json.Unmarshal(flynn, &apps), c.IsNil) for _, name := range []string{"postgres", "discoverd", "flannel", "controller"} { ef, ok := apps[name] t.Assert(ok, c.Equals, true) t.Assert(ef.App, c.Not(c.IsNil)) t.Assert(ef.Release, c.Not(c.IsNil)) t.Assert(ef.Processes, c.Not(c.IsNil)) t.Assert(ef.App.Name, c.Equals, name) } }
func (s *CLISuite) TestMeta(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() t.Assert(app.flynn("meta", "set", "META_TEST=var", "SECOND_VAL=2"), Succeeds) t.Assert(app.flynn("meta").Output, Matches, `META_TEST *var`) t.Assert(app.flynn("meta").Output, Matches, `SECOND_VAL *2`) // test that unset can remove all meta tags t.Assert(app.flynn("meta", "unset", "META_TEST", "SECOND_VAL"), Succeeds) t.Assert(app.flynn("meta").Output, c.Not(Matches), `META_TEST *var`) t.Assert(app.flynn("meta").Output, c.Not(Matches), `SECOND_VAL *2`) }
func (s *CLISuite) TestCluster(t *c.C) { // use a custom flynnrc to avoid disrupting other tests file, err := ioutil.TempFile("", "") t.Assert(err, c.IsNil) flynn := func(cmdArgs ...string) *CmdResult { cmd := exec.Command(args.CLI, cmdArgs...) cmd.Env = flynnEnv(file.Name()) return run(t, cmd) } // cluster add t.Assert(flynn("cluster", "add", "--no-git", "foo", "https://controller.foo.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster"), SuccessfulOutputContains, "foo") t.Assert(flynn("cluster", "add", "--no-git", "-p", "KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs=", "test", "https://controller.test.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster"), SuccessfulOutputContains, "test") t.Assert(flynn("cluster", "add", "-f", "--no-git", "-p", "KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs=", "test", "https://controller.test.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster"), SuccessfulOutputContains, "test") t.Assert(flynn("cluster", "add", "-f", "-d", "--no-git", "-p", "KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs=", "test", "https://controller.test.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster"), SuccessfulOutputContains, "test") // make sure the cluster is present in the config cfg, err := config.ReadFile(file.Name()) t.Assert(err, c.IsNil) t.Assert(cfg.Default, c.Equals, "test") t.Assert(cfg.Clusters, c.HasLen, 2) t.Assert(cfg.Clusters[0].Name, c.Equals, "foo") t.Assert(cfg.Clusters[1].Name, c.Equals, "test") // overwriting with a conflicting name and a different conflicting url should error conflict := flynn("cluster", "add", "-f", "--no-git", "foo", "https://controller.test.example.com", "e09dc5301d72be755a3d666f617c4600") t.Assert(conflict, c.Not(Succeeds)) t.Assert(conflict, OutputContains, "conflict with") // overwriting (without --force) should not work t.Assert(flynn("cluster", "add", "test", "foo", "bar"), c.Not(Succeeds)) t.Assert(flynn("cluster"), SuccessfulOutputContains, "test") t.Assert(flynn("cluster"), SuccessfulOutputContains, "(default)") // change default cluster t.Assert(flynn("cluster", "default", "test"), SuccessfulOutputContains, "\"test\" is now the default cluster.") t.Assert(flynn("cluster", "default", "missing"), OutputContains, "Cluster \"missing\" does not exist and cannot be set as default.") t.Assert(flynn("cluster", "default"), SuccessfulOutputContains, "test") cfg, err = config.ReadFile(file.Name()) t.Assert(err, c.IsNil) t.Assert(cfg.Default, c.Equals, "test") // cluster remove t.Assert(flynn("cluster", "remove", "test"), Succeeds) t.Assert(flynn("cluster"), c.Not(SuccessfulOutputContains), "test") cfg, err = config.ReadFile(file.Name()) t.Assert(err, c.IsNil) t.Assert(cfg.Clusters, c.HasLen, 1) t.Assert(flynn("cluster", "remove", "foo"), Succeeds) // cluster remove default and set next available t.Assert(flynn("cluster", "add", "-d", "--no-git", "-p", "KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs=", "test", "https://controller.test.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster", "add", "--no-git", "-p", "KGCENkp53YF5OvOKkZIry71+czFRkSw2ZdMszZ/0ljs=", "next", "https://controller.next.example.com", "e09dc5301d72be755a3d666f617c4600"), Succeeds) t.Assert(flynn("cluster", "remove", "test"), SuccessfulOutputContains, "Cluster \"test\" removed and \"next\" is now the default cluster.") t.Assert(flynn("cluster", "default"), SuccessfulOutputContains, "next") }
func (s *CLISuite) TestRun(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() // this shouldn't be logged t.Assert(app.sh("echo foo"), Outputs, "foo\n") // drain the events app.waitFor(ct.JobEvents{"": {ct.JobStateUp: 1, ct.JobStateDown: 1}}) // this should be logged due to the --enable-log flag t.Assert(app.flynn("run", "--enable-log", "echo", "hello"), Outputs, "hello\n") app.waitFor(ct.JobEvents{"": {ct.JobStateUp: 1, ct.JobStateDown: 1}}) detached := app.flynn("run", "-d", "echo", "world") t.Assert(detached, Succeeds) t.Assert(detached, c.Not(Outputs), "world\n") id := strings.TrimSpace(detached.Output) jobID := app.waitFor(ct.JobEvents{"": {ct.JobStateUp: 1, ct.JobStateDown: 1}}) t.Assert(jobID, c.Equals, id) t.Assert(app.flynn("log", "--raw-output"), Outputs, "hello\nworld\n") // test stdin and stderr streams := app.flynnCmd("run", "sh", "-c", "cat 1>&2") stdin, err := streams.StdinPipe() t.Assert(err, c.IsNil) go func() { stdin.Write([]byte("goto stderr")) stdin.Close() }() var stderr bytes.Buffer var stdout bytes.Buffer streams.Stderr = &stderr streams.Stdout = &stdout t.Assert(streams.Run(), c.IsNil) t.Assert(stderr.String(), c.Equals, "goto stderr") t.Assert(stdout.String(), c.Equals, "") // test exit code exit := app.sh("exit 42") t.Assert(exit, c.Not(Succeeds)) if msg, ok := exit.Err.(*exec.ExitError); ok { // there is error code code := msg.Sys().(syscall.WaitStatus).ExitStatus() t.Assert(code, c.Equals, 42) } else { t.Fatal("There was no error code!") } }
func (s *RedisSuite) TestDumpRestore(t *c.C) { a := s.newCliTestApp(t) res := a.flynn("resource", "add", "redis") t.Assert(res, Succeeds) id := strings.Split(res.Output, " ")[2] release, err := s.controllerClient(t).GetAppRelease(a.id) t.Assert(err, c.IsNil) t.Assert(release.Env["FLYNN_REDIS"], c.Not(c.Equals), "") a.waitForService(release.Env["FLYNN_REDIS"]) t.Assert(a.flynn("redis", "redis-cli", "set", "foo", "bar"), Succeeds) file := filepath.Join(t.MkDir(), "dump.rdb") t.Assert(a.flynn("redis", "dump", "-f", file), Succeeds) t.Assert(a.flynn("redis", "redis-cli", "del", "foo"), Succeeds) a.flynn("redis", "restore", "-f", file) query := a.flynn("redis", "redis-cli", "get", "foo") t.Assert(query, SuccessfulOutputContains, "bar") t.Assert(a.flynn("resource", "remove", "redis", id), Succeeds) }
func (s *GitDeploySuite) TestNonMasterPush(t *c.C) { r := s.newGitRepo(t, "empty") t.Assert(r.flynn("create"), Succeeds) push := r.git("push", "flynn", "master:foo") t.Assert(push, c.Not(Succeeds)) t.Assert(push, OutputContains, "push must include a change to the master branch") }
func (s *HostSuite) TestVolumeCreation(t *c.C) { h := s.anyHostClient(t) vol, err := h.CreateVolume("default") t.Assert(err, c.IsNil) t.Assert(vol.ID, c.Not(c.Equals), "") t.Assert(h.DestroyVolume(vol.ID), c.IsNil) }
func (s *HostSuite) TestVolumeDeleteOnStop(t *c.C) { hosts, err := s.clusterClient(t).Hosts() t.Assert(err, c.IsNil) t.Assert(hosts, c.Not(c.HasLen), 0) h := hosts[0] // stream job events so we can wait for cleanup events events := make(chan *host.Event) stream, err := h.StreamEvents("all", events) t.Assert(err, c.IsNil) defer stream.Close() waitCleanup := func(jobID string) { timeout := time.After(30 * time.Second) for { select { case event := <-events: if event.JobID == jobID && event.Event == host.JobEventCleanup { return } case <-timeout: t.Fatal("timed out waiting for cleanup event") } } } for _, deleteOnStop := range []bool{true, false} { job := &host.Job{ Config: host.ContainerConfig{ Args: []string{"sh", "-c", "ls -d /foo"}, DisableLog: true, }, } // provision a volume req := &ct.VolumeReq{Path: "/foo", DeleteOnStop: deleteOnStop} vol, err := utils.ProvisionVolume(req, h, job) t.Assert(err, c.IsNil) defer h.DestroyVolume(vol.ID) // run the job cmd := exec.JobUsingCluster(s.clusterClient(t), s.createArtifact(t, "test-apps"), job) cmd.HostID = h.ID() out, err := cmd.CombinedOutput() t.Assert(err, c.IsNil) t.Assert(string(out), c.Equals, "/foo\n") // wait for a cleanup event waitCleanup(job.ID) // check if the volume was deleted or not vol, err = h.GetVolume(vol.ID) if deleteOnStop { t.Assert(hh.IsObjectNotFoundError(err), c.Equals, true) } else { t.Assert(err, c.IsNil) } } }
func (s *CLISuite) TestScaleAll(t *c.C) { client := s.controllerClient(t) app := s.newCliTestApp(t) release := app.release defer app.cleanup() scale := app.flynn("scale", "echoer=1", "printer=2") t.Assert(scale, Succeeds) scale = app.flynn("scale", "--all") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, fmt.Sprintf("%s (current)\n", release.ID)) t.Assert(scale, SuccessfulOutputContains, "echoer=1") t.Assert(scale, SuccessfulOutputContains, "printer=2") prevRelease := release release = &ct.Release{ ArtifactIDs: release.ArtifactIDs, Env: release.Env, Meta: release.Meta, Processes: release.Processes, } t.Assert(client.CreateRelease(release), c.IsNil) t.Assert(client.SetAppRelease(app.id, release.ID), c.IsNil) scale = app.flynn("scale", "echoer=2", "printer=1") t.Assert(scale, Succeeds) scale = app.flynn("scale", "--all") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, fmt.Sprintf("%s (current)\n", release.ID)) t.Assert(scale, SuccessfulOutputContains, "echoer=2") t.Assert(scale, SuccessfulOutputContains, "printer=1") t.Assert(scale, SuccessfulOutputContains, fmt.Sprintf("%s\n", prevRelease.ID)) t.Assert(scale, SuccessfulOutputContains, "echoer=1") t.Assert(scale, SuccessfulOutputContains, "printer=2") scale = app.flynn("scale", "--all", "--release", release.ID) t.Assert(scale, c.Not(Succeeds)) scale = app.flynn("scale", "--all", "echoer=3", "printer=3") t.Assert(scale, c.Not(Succeeds)) }
func (s *CLISuite) TestReleaseDelete(t *c.C) { // create an app and release it twice r := s.newGitRepo(t, "http") app := "release-delete-" + random.String(8) t.Assert(r.flynn("create", app), Succeeds) t.Assert(r.git("push", "flynn", "master"), Succeeds) t.Assert(r.git("commit", "--allow-empty", "--message", "empty commit"), Succeeds) t.Assert(r.git("push", "flynn", "master"), Succeeds) // get the releases client := s.controllerClient(t) releases, err := client.AppReleaseList(app) t.Assert(err, c.IsNil) t.Assert(releases, c.HasLen, 2) // check the current release cannot be deleted res := r.flynn("release", "delete", "--yes", releases[0].ID) t.Assert(res, c.Not(Succeeds)) t.Assert(res.Output, c.Equals, "validation_error: cannot delete current app release\n") // associate the initial release with another app otherApp := &ct.App{Name: "release-delete-" + random.String(8)} t.Assert(client.CreateApp(otherApp), c.IsNil) t.Assert(client.PutFormation(&ct.Formation{AppID: otherApp.ID, ReleaseID: releases[1].ID}), c.IsNil) // check deleting the initial release just deletes the formation res = r.flynn("release", "delete", "--yes", releases[1].ID) t.Assert(res, Succeeds) t.Assert(res.Output, c.Equals, "Release scaled down for app but not fully deleted (still associated with 1 other apps)\n") // check the slug artifact still exists slugArtifact, err := client.GetArtifact(releases[1].ArtifactIDs[1]) t.Assert(err, c.IsNil) s.assertURI(t, slugArtifact.URI, http.StatusOK) slugLayerURL := slugArtifact.LayerURL(slugArtifact.Manifest().Rootfs[0].Layers[0]) s.assertURI(t, slugLayerURL, http.StatusOK) // check the inital release can now be deleted res = r.flynn("-a", otherApp.ID, "release", "delete", "--yes", releases[1].ID) t.Assert(res, Succeeds) t.Assert(res.Output, c.Equals, fmt.Sprintf("Deleted release %s (deleted 2 files)\n", releases[1].ID)) // check the slug artifact was deleted _, err = client.GetArtifact(slugArtifact.ID) t.Assert(err, c.Equals, controller.ErrNotFound) s.assertURI(t, slugArtifact.URI, http.StatusNotFound) s.assertURI(t, slugLayerURL, http.StatusNotFound) // check the image artifact was not deleted (since it is shared between both releases) _, err = client.GetArtifact(releases[1].ArtifactIDs[0]) t.Assert(err, c.IsNil) }
func (s *CLISuite) TestResourceRemove(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() add := app.flynn("resource", "add", "postgres") t.Assert(add, Succeeds) t.Assert(app.flynn("resource").Output, Matches, "postgres") t.Assert(app.flynn("env").Output, Matches, "FLYNN_POSTGRES") id := strings.Split(add.Output, " ")[2] // change one of the env vars provided by the resource t.Assert(app.flynn("env", "set", "PGUSER=testuser"), Succeeds) remove := app.flynn("resource", "remove", "postgres", id) t.Assert(remove, Succeeds) t.Assert(app.flynn("resource").Output, c.Not(Matches), "postgres") // test that unmodified vars are removed t.Assert(app.flynn("env").Output, c.Not(Matches), "FLYNN_POSTGRES") // but that modifed ones are retained t.Assert(app.flynn("env", "get", "PGUSER").Output, Matches, "testuser") }
func (s *HostSuite) TestAddFailingJob(t *c.C) { // get a host and watch events hosts, err := s.clusterClient(t).Hosts() t.Assert(err, c.IsNil) t.Assert(hosts, c.Not(c.HasLen), 0) h := hosts[0] jobID := random.UUID() events := make(chan *host.Event) stream, err := h.StreamEvents(jobID, events) t.Assert(err, c.IsNil) defer stream.Close() // add a job with a non existent partition job := &host.Job{ ID: jobID, ImageArtifact: &host.Artifact{ Type: host.ArtifactTypeDocker, URI: "http://example.com?name=foo&id=bar", }, Partition: "nonexistent", } t.Assert(h.AddJob(job), c.IsNil) // check we get a create then error event actual := make(map[string]*host.Event, 2) loop: for { select { case e, ok := <-events: if !ok { t.Fatalf("job event stream closed unexpectedly: %s", stream.Err()) } if _, ok := actual[e.Event]; ok { t.Fatalf("unexpected event: %v", e) } actual[e.Event] = e if len(actual) >= 2 { break loop } case <-time.After(30 * time.Second): t.Fatal("timed out waiting for job event") } } t.Assert(actual[host.JobEventCreate], c.NotNil) e := actual[host.JobEventError] t.Assert(e, c.NotNil) t.Assert(e.Job, c.NotNil) t.Assert(e.Job.Error, c.NotNil) t.Assert(*e.Job.Error, c.Equals, `host: invalid job partition "nonexistent"`) }
func (s *CLISuite) TestRunNoImage(t *c.C) { r := s.newGitRepo(t, "empty-release") t.Assert(r.flynn("create"), Succeeds) t.Assert(r.flynn("env", "set", "FOO=BAR", "BUILDPACK_URL=https://github.com/kr/heroku-buildpack-inline"), Succeeds) // running a command before pushing should error cmd := r.flynn("run", "env") t.Assert(cmd, c.Not(Succeeds)) t.Assert(cmd, OutputContains, "App release has no image, push a release first") // command should work after push t.Assert(r.git("push", "flynn", "master"), Succeeds) cmd = r.flynn("run", "env") t.Assert(cmd, Succeeds) t.Assert(cmd, OutputContains, "FOO=BAR") }
func (h *Helper) testBuildCaching(t *c.C) { r := h.newGitRepo(t, "build-cache") t.Assert(r.flynn("create"), Succeeds) t.Assert(r.flynn("env", "set", "BUILDPACK_URL=https://github.com/kr/heroku-buildpack-inline"), Succeeds) r.git("commit", "-m", "bump", "--allow-empty") push := r.git("push", "flynn", "master") t.Assert(push, Succeeds) t.Assert(push, c.Not(OutputContains), "cached") r.git("commit", "-m", "bump", "--allow-empty") push = r.git("push", "flynn", "master") t.Assert(push, SuccessfulOutputContains, "cached: 0") r.git("commit", "-m", "bump", "--allow-empty") push = r.git("push", "flynn", "master") t.Assert(push, SuccessfulOutputContains, "cached: 1") }
func (s *GitDeploySuite) TestCrashingApp(t *c.C) { // create a crashing app r := s.newGitRepo(t, "crash") app := "crashing-app" t.Assert(r.flynn("create", app), Succeeds) t.Assert(r.flynn("env", "set", "BUILDPACK_URL=https://github.com/kr/heroku-buildpack-inline"), Succeeds) // check the push is rejected as the job won't start push := r.git("push", "flynn", "master") t.Assert(push, c.Not(Succeeds)) t.Assert(push, OutputContains, "web job failed to start") // check the formation was scaled down client := s.controllerClient(t) release, err := client.GetAppRelease(app) t.Assert(err, c.IsNil) formation, err := client.GetFormation(app, release.ID) t.Assert(err, c.IsNil) t.Assert(formation.Processes["web"], c.Equals, 0) }
func (s *GitreceiveSuite) TestRepoCaching(t *c.C) { r := s.newGitRepo(t, "empty") t.Assert(r.flynn("create"), Succeeds) r.git("commit", "-m", "bump", "--allow-empty") r.git("commit", "-m", "bump", "--allow-empty") push := r.git("push", "flynn", "master") t.Assert(push, Succeeds) t.Assert(push, c.Not(OutputContains), "cached") // cycle the receiver to clear any cache t.Assert(flynn(t, "/", "-a", "gitreceive", "scale", "app=0"), Succeeds) t.Assert(flynn(t, "/", "-a", "gitreceive", "scale", "app=1"), Succeeds) _, err := s.discoverdClient(t).Instances("gitreceive", 10*time.Second) t.Assert(err, c.IsNil) r.git("commit", "-m", "bump", "--allow-empty") push = r.git("push", "flynn", "master", "--progress") // should only contain one object t.Assert(push, SuccessfulOutputContains, "Counting objects: 1, done.") }
func (s *CLISuite) TestRelease(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() release := &ct.Release{ ArtifactIDs: []string{s.createArtifact(t, "test-apps").ID}, Env: map[string]string{"GLOBAL": "FOO"}, Processes: map[string]ct.ProcessType{ "echoer": { Args: []string{"/bin/echoer"}, Env: map[string]string{"ECHOER_ONLY": "BAR"}, }, "env": { Args: []string{"sh", "-c", "env; while true; do sleep 60; done"}, Env: map[string]string{"ENV_ONLY": "BAZ"}, }, }, } client := s.controllerClient(t) t.Assert(client.CreateRelease(release), c.IsNil) t.Assert(client.SetAppRelease(app.id, release.ID), c.IsNil) updateFile := filepath.Join(t.MkDir(), "updates.json") updateJSON := []byte(`{ "processes": { "echoer": { "env": {"ECHOER_ONLY": "BAT"} }, "env": { "env": {"ENV_UPDATE": "QUUX"} } } }`) t.Assert(ioutil.WriteFile(updateFile, updateJSON, 0644), c.IsNil) t.Assert(app.flynn("release", "update", updateFile), Succeeds) resultJSON := []byte(`{ "env": {"GLOBAL": "FOO"}, "processes": { "echoer": { "args": ["/bin/echoer"], "env": { "ECHOER_ONLY": "BAT" } }, "env": { "args": ["sh", "-c", "env; while true; do sleep 60; done"], "env": { "ENV_ONLY": "BAZ", "ENV_UPDATE": "QUUX" } } } }`) result := &ct.Release{} t.Assert(json.Unmarshal(resultJSON, &result), c.IsNil) for typ, proc := range result.Processes { resource.SetDefaults(&proc.Resources) result.Processes[typ] = proc } release, err := s.controller.GetAppRelease(app.name) t.Assert(err, c.IsNil) t.Assert(release.Env, c.DeepEquals, result.Env) t.Assert(release.Processes, c.DeepEquals, result.Processes) scaleCmd := app.flynn("scale", "--no-wait", "env=1", "foo=1") t.Assert(scaleCmd, c.Not(Succeeds)) t.Assert(scaleCmd, OutputContains, "ERROR: unknown process types: \"foo\"") // create a job watcher for the new release watcher, err := client.WatchJobEvents(app.name, release.ID) t.Assert(err, c.IsNil) defer watcher.Close() scaleCmd = app.flynn("scale", "--no-wait", "env=1") t.Assert(watcher.WaitFor(ct.JobEvents{"env": {ct.JobStateUp: 1}}, scaleTimeout, nil), c.IsNil) envLog := app.flynn("log") t.Assert(envLog, Succeeds) t.Assert(envLog, SuccessfulOutputContains, "GLOBAL=FOO") t.Assert(envLog, SuccessfulOutputContains, "ENV_ONLY=BAZ") t.Assert(envLog, SuccessfulOutputContains, "ENV_UPDATE=QUUX") t.Assert(envLog, c.Not(SuccessfulOutputContains), "ECHOER_ONLY=BAR") t.Assert(envLog, c.Not(SuccessfulOutputContains), "ECHOER_UPDATE=BAT") }
func (s *SchedulerSuite) TestRollbackController(t *c.C) { // get the current controller release client := s.controllerClient(t) app, err := client.GetApp("controller") t.Assert(err, c.IsNil) release, err := client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) watcher, err := s.controllerClient(t).WatchJobEvents(app.ID, release.ID) t.Assert(err, c.IsNil) defer watcher.Close() // get the current controller formation formation, err := client.GetFormation(app.ID, release.ID) t.Assert(err, c.IsNil) currentReleaseID := release.ID // create a controller deployment that will fail release.ID = "" worker := release.Processes["worker"] worker.Args = []string{"/i/dont/exist"} release.Processes["worker"] = worker t.Assert(client.CreateRelease(release), c.IsNil) deployment, err := client.CreateDeployment(app.ID, release.ID) t.Assert(err, c.IsNil) events := make(chan *ct.DeploymentEvent) eventStream, err := client.StreamDeployment(deployment, events) t.Assert(err, c.IsNil) defer eventStream.Close() // wait for the deploy to fail loop: for { select { case e, ok := <-events: if !ok { t.Fatal("unexpected close of deployment event stream") } debugf(t, "got deployment event: %s %s", e.JobType, e.JobState) switch e.Status { case "complete": t.Fatal("the deployment succeeded when it should have failed") case "failed": break loop } case <-time.After(2 * time.Minute): t.Fatal("timed out waiting for the deploy to fail") } } // wait for jobs to come back up hosts, err := s.clusterClient(t).Hosts() expected := map[string]map[ct.JobState]int{ "web": {ct.JobStateUp: formation.Processes["web"]}, "scheduler": {ct.JobStateUp: len(hosts)}, } t.Assert(watcher.WaitFor(expected, scaleTimeout, nil), c.IsNil) // check the correct controller jobs are running t.Assert(err, c.IsNil) t.Assert(hosts, c.Not(c.HasLen), 0) actual := make(map[string]map[string]int) for _, h := range hosts { jobs, err := h.ListJobs() t.Assert(err, c.IsNil) for _, job := range jobs { if job.Status != host.StatusRunning { continue } appID := job.Job.Metadata["flynn-controller.app"] if appID != app.ID { continue } releaseID := job.Job.Metadata["flynn-controller.release"] if releaseID != currentReleaseID { continue } if _, ok := actual[releaseID]; !ok { actual[releaseID] = make(map[string]int) } typ := job.Job.Metadata["flynn-controller.type"] actual[releaseID][typ]++ } } t.Assert(actual, c.DeepEquals, map[string]map[string]int{ currentReleaseID: { "web": formation.Processes["web"], "scheduler": formation.Processes["scheduler"] * len(hosts), "worker": formation.Processes["worker"], }, }) }
func (s *SchedulerSuite) TestControllerRestart(t *c.C) { // get the current controller details app, err := s.controllerClient(t).GetApp("controller") t.Assert(err, c.IsNil) release, err := s.controllerClient(t).GetAppRelease("controller") t.Assert(err, c.IsNil) formation, err := s.controllerClient(t).GetFormation(app.ID, release.ID) t.Assert(err, c.IsNil) list, err := s.controllerClient(t).JobList("controller") t.Assert(err, c.IsNil) var jobs []*ct.Job for _, job := range list { if job.Type == "web" && job.State == ct.JobStateUp { jobs = append(jobs, job) } } t.Assert(jobs, c.HasLen, formation.Processes["web"]) jobID := jobs[0].ID hostID, _ := cluster.ExtractHostID(jobID) t.Assert(hostID, c.Not(c.Equals), "") debugf(t, "current controller app[%s] host[%s] job[%s]", app.ID, hostID, jobID) // subscribe to service events, wait for current event events := make(chan *discoverd.Event) stream, err := s.discoverdClient(t).Service("controller").Watch(events) t.Assert(err, c.IsNil) defer stream.Close() type serviceEvents map[discoverd.EventKind]int wait := func(expected serviceEvents) { actual := make(serviceEvents) outer: for { select { case event := <-events: actual[event.Kind]++ for kind, count := range expected { if actual[kind] != count { continue outer } } return case <-time.After(scaleTimeout): t.Fatal("timed out waiting for controller service event") } } } wait(serviceEvents{discoverd.EventKindCurrent: 1}) // start another controller and wait for it to come up debug(t, "scaling the controller up") formation.Processes["web"]++ t.Assert(s.controllerClient(t).PutFormation(formation), c.IsNil) wait(serviceEvents{discoverd.EventKindUp: 1}) // kill the first controller and check the scheduler brings it back online cc := cluster.NewClientWithServices(s.discoverdClient(t).Service) hc, err := cc.Host(hostID) t.Assert(err, c.IsNil) debug(t, "stopping job ", jobID) t.Assert(hc.StopJob(jobID), c.IsNil) wait(serviceEvents{discoverd.EventKindUp: 1, discoverd.EventKindDown: 1}) // scale back down debug(t, "scaling the controller down") formation.Processes["web"]-- t.Assert(s.controllerClient(t).PutFormation(formation), c.IsNil) wait(serviceEvents{discoverd.EventKindDown: 1}) // unset the suite's client so other tests use a new client s.controller = nil }
func (s *CLISuite) TestRoute(t *c.C) { client := s.controllerClient(t) app := s.newCliTestApp(t) defer app.cleanup() // The router API does not currently give us a "read your own writes" // guarantee, so we must retry a few times if we don't get the expected // result. assertRouteContains := func(str string, contained bool) { var res *CmdResult attempt.Strategy{ Total: 10 * time.Second, Delay: 500 * time.Millisecond, }.Run(func() error { res = app.flynn("route") if contained == strings.Contains(res.Output, str) { return nil } return errors.New("unexpected output") }) if contained { t.Assert(res, SuccessfulOutputContains, str) } else { t.Assert(res, c.Not(SuccessfulOutputContains), str) } } // flynn route add http route := random.String(32) + ".dev" newRoute := app.flynn("route", "add", "http", route) t.Assert(newRoute, Succeeds) routeID := strings.TrimSpace(newRoute.Output) assertRouteContains(routeID, true) // ensure sticky and leader flags default to not set routes, err := client.RouteList(app.name) t.Assert(err, c.IsNil) var found bool for _, r := range routes { if fmt.Sprintf("%s/%s", r.Type, r.ID) != routeID { continue } t.Assert(r.Sticky, c.Equals, false) t.Assert(r.Leader, c.Equals, false) found = true break } t.Assert(found, c.Equals, true, c.Commentf("didn't find route")) // flynn route add http --sticky --leader route = random.String(32) + ".dev" newRoute = app.flynn("route", "add", "http", "--sticky", route, "--leader") t.Assert(newRoute, Succeeds) routeID = strings.TrimSpace(newRoute.Output) assertRouteContains(routeID, true) // duplicate http route dupRoute := app.flynn("route", "add", "http", "--sticky", route) t.Assert(dupRoute, c.Not(Succeeds)) t.Assert(dupRoute.Output, c.Equals, "conflict: Duplicate route\n") // ensure sticky and leader flags are set routes, err = client.RouteList(app.name) t.Assert(err, c.IsNil) for _, r := range routes { if fmt.Sprintf("%s/%s", r.Type, r.ID) != routeID { continue } t.Assert(r.Sticky, c.Equals, true) t.Assert(r.Leader, c.Equals, true) found = true break } t.Assert(found, c.Equals, true, c.Commentf("didn't find route")) // flynn route update --no-sticky newRoute = app.flynn("route", "update", routeID, "--no-sticky") t.Assert(newRoute, Succeeds) r, err := client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Sticky, c.Equals, false) // flynn route update --no-leader newRoute = app.flynn("route", "update", routeID, "--no-leader") t.Assert(newRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Leader, c.Equals, false) // flynn route update --service newRoute = app.flynn("route", "update", routeID, "--service", "foo") t.Assert(newRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Service, c.Equals, "foo") t.Assert(r.Sticky, c.Equals, false) // flynn route update --sticky newRoute = app.flynn("route", "update", routeID, "--sticky") t.Assert(newRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Sticky, c.Equals, true) t.Assert(r.Service, c.Equals, "foo") // flynn route update --leader newRoute = app.flynn("route", "update", routeID, "--leader") t.Assert(newRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Leader, c.Equals, true) t.Assert(r.Service, c.Equals, "foo") // flynn route add domain path pathRoute := app.flynn("route", "add", "http", route+"/path/") t.Assert(pathRoute, Succeeds) pathRouteID := strings.TrimSpace(pathRoute.Output) assertRouteContains(pathRouteID, true) // flynn route add domain path duplicate dupRoute = app.flynn("route", "add", "http", route+"/path/") t.Assert(dupRoute, c.Not(Succeeds)) t.Assert(dupRoute.Output, c.Equals, "conflict: Duplicate route\n") // flynn route add domain path without trailing should correct to trailing noTrailingRoute := app.flynn("route", "add", "http", route+"/path2") t.Assert(noTrailingRoute, Succeeds) noTrailingRouteID := strings.TrimSpace(noTrailingRoute.Output) assertRouteContains(noTrailingRouteID, true) // flynn route should show the corrected trailing path assertRouteContains("/path2/", true) // flynn route remove should fail because of dependent route delFail := app.flynn("route", "remove", routeID) t.Assert(delFail, c.Not(Succeeds)) // But removing the dependent route and then the default route should work t.Assert(app.flynn("route", "remove", pathRouteID), Succeeds) assertRouteContains(pathRouteID, false) t.Assert(app.flynn("route", "remove", noTrailingRouteID), Succeeds) assertRouteContains(noTrailingRouteID, false) t.Assert(app.flynn("route", "remove", routeID), Succeeds) assertRouteContains(routeID, false) // flynn route add tcp tcpRoute := app.flynn("route", "add", "tcp") t.Assert(tcpRoute, Succeeds) routeID = strings.Split(tcpRoute.Output, " ")[0] assertRouteContains(routeID, true) // flynn route add tcp --port portRoute := app.flynn("route", "add", "tcp", "--port", "9999") t.Assert(portRoute, Succeeds) routeID = strings.Split(portRoute.Output, " ")[0] port := strings.Split(portRoute.Output, " ")[4] t.Assert(port, c.Equals, "9999\n") assertRouteContains(routeID, true) // flynn route update --service portRoute = app.flynn("route", "update", routeID, "--service", "foo") t.Assert(portRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Service, c.Equals, "foo") // flynn route remove t.Assert(app.flynn("route", "remove", routeID), Succeeds) assertRouteContains(routeID, false) writeTemp := func(data, prefix string) (string, error) { f, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("flynn-test-%s", prefix)) t.Assert(err, c.IsNil) _, err = f.WriteString(data) t.Assert(err, c.IsNil) stat, err := f.Stat() t.Assert(err, c.IsNil) return filepath.Join(os.TempDir(), stat.Name()), nil } // flynn route add http with tls cert cert, err := tlscert.Generate([]string{"example.com"}) t.Assert(err, c.IsNil) certPath, err := writeTemp(cert.Cert, "tls-cert") t.Assert(err, c.IsNil) keyPath, err := writeTemp(cert.PrivateKey, "tls-key") certRoute := app.flynn("route", "add", "http", "--tls-cert", certPath, "--tls-key", keyPath, "example.com") t.Assert(certRoute, Succeeds) routeID = strings.TrimSpace(certRoute.Output) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Domain, c.Equals, "example.com") t.Assert(r.Certificate, c.NotNil) t.Assert(r.Certificate.Cert, c.Equals, strings.Trim(cert.Cert, "\n")) t.Assert(r.Certificate.Key, c.Equals, strings.Trim(cert.PrivateKey, "\n")) // flynn route update tls cert cert, err = tlscert.Generate([]string{"example.com"}) t.Assert(err, c.IsNil) certPath, err = writeTemp(cert.Cert, "tls-cert") t.Assert(err, c.IsNil) keyPath, err = writeTemp(cert.PrivateKey, "tls-key") certRoute = app.flynn("route", "update", routeID, "--tls-cert", certPath, "--tls-key", keyPath) t.Assert(certRoute, Succeeds) r, err = client.GetRoute(app.id, routeID) t.Assert(err, c.IsNil) t.Assert(r.Domain, c.Equals, "example.com") t.Assert(r.Certificate, c.NotNil) t.Assert(r.Certificate.Cert, c.Equals, strings.Trim(cert.Cert, "\n")) t.Assert(r.Certificate.Key, c.Equals, strings.Trim(cert.PrivateKey, "\n")) // flynn route remove t.Assert(app.flynn("route", "remove", routeID), Succeeds) assertRouteContains(routeID, false) }
func (s *SchedulerSuite) TestDeployController(t *c.C) { // get the current controller release client := s.controllerClient(t) app, err := client.GetApp("controller") t.Assert(err, c.IsNil) release, err := client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) // get the current controller formation formation, err := client.GetFormation(app.ID, release.ID) t.Assert(err, c.IsNil) // create a controller deployment release.ID = "" t.Assert(client.CreateRelease(release), c.IsNil) deployment, err := client.CreateDeployment(app.ID, release.ID) t.Assert(err, c.IsNil) events := make(chan *ct.DeploymentEvent) eventStream, err := client.StreamDeployment(deployment, events) t.Assert(err, c.IsNil) defer eventStream.Close() // wait for the deploy to complete (this doesn't wait for specific events // due to the fact that when the deployer deploys itself, some events will // not get sent) loop: for { select { case e, ok := <-events: if !ok { t.Fatal("unexpected close of deployment event stream") } debugf(t, "got deployment event: %s %s", e.JobType, e.JobState) switch e.Status { case "complete": break loop case "failed": t.Fatal("the deployment failed") } case <-time.After(time.Duration(app.DeployTimeout) * time.Second): t.Fatal("timed out waiting for the deploy to complete") } } // check the correct controller jobs are running hosts, err := s.clusterClient(t).Hosts() t.Assert(err, c.IsNil) t.Assert(hosts, c.Not(c.HasLen), 0) actual := make(map[string]map[string]int) for _, h := range hosts { jobs, err := h.ListJobs() t.Assert(err, c.IsNil) for _, job := range jobs { if job.Status != host.StatusRunning { continue } appID := job.Job.Metadata["flynn-controller.app"] if appID != app.ID { continue } releaseID := job.Job.Metadata["flynn-controller.release"] if _, ok := actual[releaseID]; !ok { actual[releaseID] = make(map[string]int) } typ := job.Job.Metadata["flynn-controller.type"] actual[releaseID][typ]++ } } expected := map[string]map[string]int{release.ID: { "web": formation.Processes["web"], "worker": formation.Processes["worker"], "scheduler": len(hosts), }} t.Assert(actual, c.DeepEquals, expected) }
func (s *CLISuite) TestRelease(t *c.C) { releaseJSON := []byte(`{ "env": {"GLOBAL": "FOO"}, "processes": { "echoer": { "args": ["/bin/echoer"], "env": {"ECHOER_ONLY": "BAR"} }, "env": { "args": ["sh", "-c", "env; while true; do sleep 60; done"], "env": {"ENV_ONLY": "BAZ"} } } }`) updateJSON := []byte(`{ "processes": { "echoer": { "env": {"ECHOER_ONLY": "BAT"} }, "env": { "env": {"ENV_UPDATE": "QUUX"} } } }`) resultJSON := []byte(`{ "env": {"GLOBAL": "FOO"}, "processes": { "echoer": { "args": ["/bin/echoer"], "env": { "ECHOER_ONLY": "BAT" } }, "env": { "args": ["sh", "-c", "env; while true; do sleep 60; done"], "env": { "ENV_ONLY": "BAZ", "ENV_UPDATE": "QUUX" } } } }`) release := &ct.Release{} t.Assert(json.Unmarshal(releaseJSON, &release), c.IsNil) for typ, proc := range release.Processes { resource.SetDefaults(&proc.Resources) release.Processes[typ] = proc } addFile, err := ioutil.TempFile("", "") t.Assert(err, c.IsNil) addFile.Write(releaseJSON) addFile.Close() app := s.newCliTestApp(t) defer app.cleanup() t.Assert(app.flynn("release", "add", "-f", addFile.Name(), imageURIs["test-apps"]), Succeeds) r1, err := s.controller.GetAppRelease(app.name) t.Assert(err, c.IsNil) t.Assert(r1.Env, c.DeepEquals, release.Env) t.Assert(r1.Processes, c.DeepEquals, release.Processes) result := &ct.Release{} t.Assert(json.Unmarshal(resultJSON, &result), c.IsNil) for typ, proc := range result.Processes { resource.SetDefaults(&proc.Resources) result.Processes[typ] = proc } updateFile, err := ioutil.TempFile("", "") t.Assert(err, c.IsNil) updateFile.Write(updateJSON) updateFile.Close() t.Assert(app.flynn("release", "update", updateFile.Name()), Succeeds) r2, err := s.controller.GetAppRelease(app.name) t.Assert(err, c.IsNil) t.Assert(r2.Env, c.DeepEquals, result.Env) t.Assert(r2.Processes, c.DeepEquals, result.Processes) scaleCmd := app.flynn("scale", "--no-wait", "env=1", "foo=1") t.Assert(scaleCmd, c.Not(Succeeds)) t.Assert(scaleCmd, OutputContains, "ERROR: unknown process types: \"foo\"") // create a job watcher for the new release watcher, err := s.controllerClient(t).WatchJobEvents(app.name, r2.ID) t.Assert(err, c.IsNil) defer watcher.Close() scaleCmd = app.flynn("scale", "--no-wait", "env=1") t.Assert(watcher.WaitFor(ct.JobEvents{"env": {ct.JobStateUp: 1}}, scaleTimeout, nil), c.IsNil) envLog := app.flynn("log") t.Assert(envLog, Succeeds) t.Assert(envLog, SuccessfulOutputContains, "GLOBAL=FOO") t.Assert(envLog, SuccessfulOutputContains, "ENV_ONLY=BAZ") t.Assert(envLog, SuccessfulOutputContains, "ENV_UPDATE=QUUX") t.Assert(envLog, c.Not(SuccessfulOutputContains), "ECHOER_ONLY=BAR") t.Assert(envLog, c.Not(SuccessfulOutputContains), "ECHOER_UPDATE=BAT") }
// Test taffy's ability to deploy repos that require key authentication func (s *TaffyDeploySuite) TestPrivateDeploys(t *c.C) { assertMeta := func(m map[string]string, k string, checker c.Checker, args ...interface{}) { v, ok := m[k] t.Assert(ok, c.Equals, true) t.Assert(v, checker, args...) } client := s.controllerClient(t) github := map[string]string{ "user": "******", "repo": "nodejs-flynn-example", "branch": "master", "rev": "5e177fec38fbde7d0a03e9e8dccf8757c68caa11", "clone_url": "[email protected]:/flynn-examples/nodejs-flynn-example.git", } app := &ct.App{} t.Assert(client.CreateApp(app), c.IsNil) debugf(t, "created app %s (%s)", app.Name, app.ID) sshKey := `MIIEpAIBAAKCAQEA2UnQ/17TfzQRt4HInuP1SYz/tSNaCGO3NDIPLydVu8mmxuKT zlJtH3pz3uWpMEKdZtSjV+QngJL8OFzanQVZtRBJjF2m+cywHJoZA5KsplMon+R+ QmVqu92WlcRdkcft1F1CLoTXTmHHfvuhOkG6GgJONNLP9Z14EsQ7MbBh5guafWOX kdGFajyd+T2aj27yIkK44WjWqiLjxRIAtgOJrmd/3H0w3E+O1cgNrA2gkFEUhvR1 OHz8SmugYva0VZWKvxZ6muZvn26L1tajYsCntCRR3/a74cAnVFAXjqSatL6YTbSH sdtE91kEC73/U4SL3OFdDiCrAvXpJ480C2/GQQIDAQABAoIBAHNQNVYRIPS00WIt wiZwm8/4wAuFQ1aIdMWCe4Ruv5T1I0kRHZe1Lqwx9CQqhWtTLu1Pk5AlSMF3P9s5 i9sg58arahzP5rlS43OKZBP9Vxq9ryWLwWXDJK2mny/EElQ3YgP9qg29+fVi9thw +dNM5lK/PnnSFwMmGn77HN712D6Yl3CCJJjsAunTfPzR9hyEqX5YvUB5eq/TNhXe sqrKcGORIoNfv7WohlFSkTAXIvoMxmFWXg8piZ9/b1W4NwvO4wup3ZSErIk0AQ97 HtyXJIXgtj6pLkPqvPXPGvS3quYAddNxvGIdvge7w5LHnrxOzdqbeDAVmJLVwVlv oo+7aQECgYEA8ZliUuA8q86SWE0N+JZUqbTvE6VzyWG0/u0BJYDkH7yHkbpFOIEy KTw048WOZLQ6/wPwL8Hb090Cas/6pmRFMgCedarzXc9fvGEwW95em7jA4AyOVBMC KIAmaYkm6LcUFeyR6ektZeCkT0MNoi4irjBC3/hMRyZu+6RL4jXxHLkCgYEA5j13 2nkbV99GtRRjyGB7uMkrhMere2MekANXEm4dW+LZFZUda4YCqdzfjDfBTxsuyGqi DnvI7bZFzIQPiiEzvL2Mpiy7JqxmPLGmwzxDp3z75T5vOrGs4g9IQ7yDjp5WPzjz KCJJHn8Qt9tNZb5h0hBM+NWLT0c1XxtTIVFfgckCgYAfNpTYZjYQcFDB7bqXWjy3 7DNTE3YhF2l94fra8IsIep/9ONaGlVJ4t1mR780Uv6A7oDOgx+fxuET+rb4RTzUN X70ZMKvee9M/kELiK5mHftgUWirtO8N0nhHYYqrPOA/1QSoc0U5XMi2oO96ADHvY i02oh/i63IFMK47OO+/ZqQKBgQCY8bY/Y/nc+o4O1hee0TD+xGvrTXRFh8eSpRVf QdSw6FWKt76OYbw9OGMr0xHPyd/e9K7obiRAfLeLLyLfgETNGSFodghwnU9g/CYq RUsv5J+0XjAnTkXo+Xvouz6tK9NhNiSYwYXPA1uItt6IOtriXz+ygLCFHml+3zju xg5quQKBgQCEL95Di6WD+155gEG2NtqeAOWhgxqAbGjFjfpV+pVBksBCrWOHcBJp QAvAdwDIZpqRWWMcLS7zSDrzn3ZscuHCMxSOe40HbrVdDUee24/I4YQ+R8EcuzcA 3IV9ai+Bxs6PvklhXmarYxJl62LzPLyv0XFscGRes/2yIIxNfNzFug==` env := map[string]string{ "SOMEVAR": "SOMEVAL", "SSH_CLIENT_HOSTS": "github.com,192.30.252.131 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==", "SSH_CLIENT_KEY": fmt.Sprintf("-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----\n", sshKey), } meta := map[string]string{ "github": "true", "github_user": github["user"], "github_repo": github["repo"], } s.deployWithTaffy(t, app, env, meta, github) release, err := client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) t.Assert(release, c.NotNil) t.Assert(release.Meta, c.NotNil) assertMeta(release.Meta, "git", c.Equals, "true") assertMeta(release.Meta, "clone_url", c.Equals, github["clone_url"]) assertMeta(release.Meta, "branch", c.Equals, github["branch"]) assertMeta(release.Meta, "rev", c.Equals, github["rev"]) assertMeta(release.Meta, "taffy_job", c.Not(c.Equals), "") assertMeta(release.Meta, "github", c.Equals, "true") assertMeta(release.Meta, "github_user", c.Equals, github["user"]) assertMeta(release.Meta, "github_repo", c.Equals, github["repo"]) t.Assert(release.Env, c.NotNil) assertMeta(release.Env, "SOMEVAR", c.Equals, "SOMEVAL") }
// This test emulates deploys in the dashboard app func (s *TaffyDeploySuite) TestDeploys(t *c.C) { assertMeta := func(m map[string]string, k string, checker c.Checker, args ...interface{}) { v, ok := m[k] t.Assert(ok, c.Equals, true) t.Assert(v, checker, args...) } client := s.controllerClient(t) github := map[string]string{ "user": "******", "repo": "nodejs-flynn-example", "branch": "master", "rev": "5e177fec38fbde7d0a03e9e8dccf8757c68caa11", "clone_url": "https://github.com/flynn-examples/nodejs-flynn-example.git", } // initial deploy app := &ct.App{} t.Assert(client.CreateApp(app), c.IsNil) debugf(t, "created app %s (%s)", app.Name, app.ID) env := map[string]string{ "SOMEVAR": "SOMEVAL", } meta := map[string]string{ "github": "true", "github_user": github["user"], "github_repo": github["repo"], } s.deployWithTaffy(t, app, env, meta, github) release, err := client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) t.Assert(release, c.NotNil) t.Assert(release.Meta, c.NotNil) assertMeta(release.Meta, "git", c.Equals, "true") assertMeta(release.Meta, "clone_url", c.Equals, github["clone_url"]) assertMeta(release.Meta, "branch", c.Equals, github["branch"]) assertMeta(release.Meta, "rev", c.Equals, github["rev"]) assertMeta(release.Meta, "taffy_job", c.Not(c.Equals), "") assertMeta(release.Meta, "github", c.Equals, "true") assertMeta(release.Meta, "github_user", c.Equals, github["user"]) assertMeta(release.Meta, "github_repo", c.Equals, github["repo"]) t.Assert(release.Env, c.NotNil) assertMeta(release.Env, "SOMEVAR", c.Equals, "SOMEVAL") // second deploy github["rev"] = "4231f8871da2b9fd73a5402753df3dfc5609d7b7" release, err = client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) s.deployWithTaffy(t, app, env, meta, github) newRelease, err := client.GetAppRelease(app.ID) t.Assert(err, c.IsNil) t.Assert(newRelease.ID, c.Not(c.Equals), release.ID) t.Assert(env, c.DeepEquals, newRelease.Env) t.Assert(release.Processes, c.DeepEquals, newRelease.Processes) t.Assert(newRelease, c.NotNil) t.Assert(newRelease.Meta, c.NotNil) assertMeta(newRelease.Meta, "git", c.Equals, "true") assertMeta(newRelease.Meta, "clone_url", c.Equals, github["clone_url"]) assertMeta(newRelease.Meta, "branch", c.Equals, github["branch"]) assertMeta(newRelease.Meta, "rev", c.Equals, github["rev"]) assertMeta(newRelease.Meta, "taffy_job", c.Not(c.Equals), "") assertMeta(newRelease.Meta, "github", c.Equals, "true") assertMeta(newRelease.Meta, "github_user", c.Equals, github["user"]) assertMeta(newRelease.Meta, "github_repo", c.Equals, github["repo"]) }
func (s *ZDomainMigrationSuite) migrateDomain(t *c.C, dm *ct.DomainMigration) *ct.DomainMigration { debugf(t, "migrating domain from %s to %s", dm.OldDomain, dm.Domain) client := s.controllerClient(t) events := make(chan *ct.Event) stream, err := client.StreamEvents(ct.StreamEventsOptions{ ObjectTypes: []ct.EventType{ct.EventTypeDomainMigration}, }, events) t.Assert(err, c.IsNil) defer stream.Close() prevRouterRelease, err := client.GetAppRelease("router") t.Assert(err, c.IsNil) err = client.PutDomain(dm) t.Assert(err, c.IsNil) waitEvent := func(typ string, timeout time.Duration) (event ct.DomainMigrationEvent) { debugf(t, "waiting for %s domain migration event", typ) var e *ct.Event var ok bool select { case e, ok = <-events: if !ok { t.Fatalf("event stream closed unexpectedly: %s", stream.Err()) } debugf(t, "got %s domain migration event", typ) case <-time.After(timeout): t.Fatalf("timed out waiting for %s domain migration event", typ) } t.Assert(e.Data, c.NotNil) t.Assert(json.Unmarshal(e.Data, &event), c.IsNil) return } // created event := waitEvent("initial", 2*time.Minute) t.Assert(event.Error, c.Equals, "") t.Assert(event.DomainMigration, c.NotNil) t.Assert(event.DomainMigration.ID, c.Equals, dm.ID) t.Assert(event.DomainMigration.OldDomain, c.Equals, dm.OldDomain) t.Assert(event.DomainMigration.Domain, c.Equals, dm.Domain) t.Assert(event.DomainMigration.OldTLSCert, c.NotNil) t.Assert(event.DomainMigration.CreatedAt, c.NotNil) t.Assert(event.DomainMigration.CreatedAt.Equal(*dm.CreatedAt), c.Equals, true) t.Assert(event.DomainMigration.FinishedAt, c.IsNil) // complete event = waitEvent("final", 3*time.Minute) t.Assert(event.Error, c.Equals, "") t.Assert(event.DomainMigration, c.NotNil) t.Assert(event.DomainMigration.ID, c.Equals, dm.ID) t.Assert(event.DomainMigration.OldDomain, c.Equals, dm.OldDomain) t.Assert(event.DomainMigration.Domain, c.Equals, dm.Domain) t.Assert(event.DomainMigration.TLSCert, c.NotNil) t.Assert(event.DomainMigration.OldTLSCert, c.NotNil) t.Assert(event.DomainMigration.CreatedAt, c.NotNil) t.Assert(event.DomainMigration.CreatedAt.Equal(*dm.CreatedAt), c.Equals, true) t.Assert(event.DomainMigration.FinishedAt, c.NotNil) cert := event.DomainMigration.TLSCert controllerRelease, err := client.GetAppRelease("controller") t.Assert(err, c.IsNil) t.Assert(controllerRelease.Env["DEFAULT_ROUTE_DOMAIN"], c.Equals, dm.Domain) t.Assert(controllerRelease.Env["CA_CERT"], c.Equals, cert.CACert) routerRelease, err := client.GetAppRelease("router") t.Assert(err, c.IsNil) t.Assert(routerRelease.Env["TLSCERT"], c.Equals, cert.Cert) t.Assert(routerRelease.Env["TLSKEY"], c.Not(c.Equals), "") t.Assert(routerRelease.Env["TLSKEY"], c.Not(c.Equals), prevRouterRelease.Env["TLSKEY"]) dashboardRelease, err := client.GetAppRelease("dashboard") t.Assert(err, c.IsNil) t.Assert(dashboardRelease.Env["DEFAULT_ROUTE_DOMAIN"], c.Equals, dm.Domain) t.Assert(dashboardRelease.Env["CONTROLLER_DOMAIN"], c.Equals, fmt.Sprintf("controller.%s", dm.Domain)) t.Assert(dashboardRelease.Env["URL"], c.Equals, fmt.Sprintf("https://dashboard.%s", dm.Domain)) t.Assert(dashboardRelease.Env["CA_CERT"], c.Equals, cert.CACert) routes, err := client.RouteList("controller") t.Assert(err, c.IsNil) t.Assert(len(routes), c.Equals, 2) // one for both new and old domain var route *router.Route for _, r := range routes { if strings.HasSuffix(r.Domain, dm.Domain) { route = r break } } t.Assert(route, c.Not(c.IsNil)) t.Assert(route.Domain, c.Equals, fmt.Sprintf("controller.%s", dm.Domain)) t.Assert(route.Certificate.Cert, c.Equals, strings.TrimSuffix(cert.Cert, "\n")) var doPing func(string, int) doPing = func(component string, retriesRemaining int) { url := fmt.Sprintf("http://%s.%s/ping", component, dm.Domain) httpClient := &http.Client{Transport: &http.Transport{Dial: dialer.Retry.Dial}} res, err := httpClient.Get(url) if (err != nil || res.StatusCode != 200) && retriesRemaining > 0 { time.Sleep(100 * time.Millisecond) doPing(component, retriesRemaining-1) return } t.Assert(err, c.IsNil) t.Assert(res.StatusCode, c.Equals, 200, c.Commentf("failed to ping %s", component)) } doPing("controller", 3) doPing("dashboard", 3) return event.DomainMigration }
func (s *CLISuite) TestScale(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() assertEventOutput := func(scale *CmdResult, events ct.JobEvents) { var actual []*ct.Job f := func(e *ct.Job) error { actual = append(actual, e) return nil } t.Assert(app.watcher.WaitFor(events, scaleTimeout, f), c.IsNil) for _, e := range actual { id := e.ID if id == "" { id = e.UUID } t.Assert(scale, OutputContains, fmt.Sprintf("==> %s %s %s", e.Type, id, e.State)) } } scale := app.flynn("scale", "echoer=1") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "scaling echoer: 0=>1") t.Assert(scale, SuccessfulOutputContains, "scale completed") assertEventOutput(scale, ct.JobEvents{"echoer": {ct.JobStateUp: 1}}) scale = app.flynn("scale", "echoer=3", "printer=1") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "echoer: 1=>3") t.Assert(scale, SuccessfulOutputContains, "printer: 0=>1") t.Assert(scale, SuccessfulOutputContains, "scale completed") assertEventOutput(scale, ct.JobEvents{"echoer": {ct.JobStateUp: 2}, "printer": {ct.JobStateUp: 1}}) // no args should show current scale scale = app.flynn("scale") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "echoer=3") t.Assert(scale, SuccessfulOutputContains, "printer=1") t.Assert(scale, SuccessfulOutputContains, "crasher=0") t.Assert(scale, SuccessfulOutputContains, "omni=0") // scale should only affect specified processes scale = app.flynn("scale", "printer=2") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "printer: 1=>2") t.Assert(scale, SuccessfulOutputContains, "scale completed") assertEventOutput(scale, ct.JobEvents{"printer": {ct.JobStateUp: 1}}) scale = app.flynn("scale") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "echoer=3") t.Assert(scale, SuccessfulOutputContains, "printer=2") t.Assert(scale, SuccessfulOutputContains, "crasher=0") t.Assert(scale, SuccessfulOutputContains, "omni=0") // unchanged processes shouldn't appear in output scale = app.flynn("scale", "echoer=3", "printer=0") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "printer: 2=>0") t.Assert(scale, c.Not(OutputContains), "echoer") t.Assert(scale, SuccessfulOutputContains, "scale completed") assertEventOutput(scale, ct.JobEvents{"printer": {ct.JobStateDown: 2}}) // --no-wait should not wait for scaling to complete scale = app.flynn("scale", "--no-wait", "echoer=0") t.Assert(scale, Succeeds) t.Assert(scale, SuccessfulOutputContains, "scaling echoer: 3=>0") t.Assert(scale, c.Not(OutputContains), "scale completed") }