func (s *ControllerSuite) TestRouteEvents(t *c.C) { app := "app-route-events-" + random.String(8) client := s.controllerClient(t) // create and push app r := s.newGitRepo(t, "http") t.Assert(r.flynn("create", app), Succeeds) t.Assert(r.git("push", "flynn", "master"), Succeeds) // wait for it to start service := app + "-web" _, err := s.discoverdClient(t).Instances(service, 10*time.Second) t.Assert(err, c.IsNil) // stream events events := make(chan *ct.Event) stream, err := client.StreamEvents(ct.StreamEventsOptions{ AppID: app, ObjectTypes: []ct.EventType{ct.EventTypeRoute, ct.EventTypeRouteDeletion}, Past: true, }, events) t.Assert(err, c.IsNil) defer stream.Close() assertEventType := func(typ ct.EventType) { select { case event, ok := <-events: t.Assert(ok, c.Equals, true) t.Assert(event.ObjectType, c.Equals, typ, c.Commentf("event: %#v", event)) case <-time.After(30 * time.Second): t.Assert(true, c.Equals, false, c.Commentf("timed out waiting for %s event", string(typ))) } } // default app route assertEventType(ct.EventTypeRoute) // create some routes routes := []string{"baz.example.com"} for _, route := range routes { t.Assert(r.flynn("route", "add", "http", route), Succeeds) assertEventType(ct.EventTypeRoute) } routeList, err := client.RouteList(app) t.Assert(err, c.IsNil) numRoutes := len(routes) + 1 // includes default app route t.Assert(routeList, c.HasLen, numRoutes) // delete app cmd := r.flynn("delete", "--yes") t.Assert(cmd, Succeeds) // check route deletion event assertEventType(ct.EventTypeRouteDeletion) }
func (s *CLISuite) TestLogStderr(t *c.C) { app := s.newCliTestApp(t) defer app.cleanup() t.Assert(app.flynn("run", "-d", "sh", "-c", "echo hello && echo world >&2"), Succeeds) app.waitFor(ct.JobEvents{"": {ct.JobStateUp: 1, ct.JobStateDown: 1}}) runLog := func(split bool) (stdout, stderr bytes.Buffer) { args := []string{"log", "--raw-output"} if split { args = append(args, "--split-stderr") } args = append(args) log := app.flynnCmd(args...) log.Stdout = &stdout log.Stderr = &stderr t.Assert(log.Run(), c.IsNil, c.Commentf("STDERR = %q", stderr.String())) return } stdout, stderr := runLog(false) // non-deterministic order t.Assert(stdout.String(), Matches, "hello") t.Assert(stdout.String(), Matches, "world") t.Assert(stderr.String(), c.Equals, "") stdout, stderr = runLog(true) t.Assert(stdout.String(), c.Equals, "hello\n") t.Assert(stderr.String(), c.Equals, "world\n") }
func (s *ControllerSuite) TestExampleOutput(t *c.C) { examples := s.generateControllerExamples(t) exampleKeys := make([]string, 0, len(examples)) skipExamples := []string{"migrate_cluster_domain"} examplesLoop: for key := range examples { for _, skipKey := range skipExamples { if key == skipKey { continue examplesLoop } } exampleKeys = append(exampleKeys, key) } sort.Strings(exampleKeys) for _, key := range exampleKeys { cacheKey := "https://flynn.io/schema/examples/controller/" + key schema := s.schemaCache[cacheKey] if schema == nil { continue } data := examples[key] errs := schema.Validate(nil, data) var jsonData []byte if len(errs) > 0 { jsonData, _ = json.MarshalIndent(data, "", "\t") } t.Assert(errs, c.HasLen, 0, c.Commentf("%s validation errors: %v\ndata: %v\n", cacheKey, errs, string(jsonData))) } }
func (s *HealthcheckSuite) TestStatus(t *c.C) { routes, err := s.controllerClient(t).RouteList("status") t.Assert(err, c.IsNil) t.Assert(routes, c.HasLen, 1) req, _ := http.NewRequest("GET", "http://"+routerIP, nil) req.Host = routes[0].HTTPRoute().Domain res, err := http.DefaultClient.Do(req) t.Assert(err, c.IsNil) defer res.Body.Close() var data struct { Data struct { Status status.Code Detail map[string]status.Status } } err = json.NewDecoder(res.Body).Decode(&data) t.Assert(err, c.IsNil) t.Assert(data.Data.Status, c.Equals, status.CodeHealthy) t.Assert(data.Data.Detail, c.HasLen, 14) optional := map[string]bool{"mariadb": true, "mongodb": true} for name, s := range data.Data.Detail { if !optional[name] { t.Assert(s.Status, c.Equals, status.CodeHealthy, c.Commentf("name = %s", name)) } } }
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 *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 }