func (s *S) createLogTestApp(c *C, name string, stream io.Reader) (*ct.App, string, string) { app := s.createTestApp(c, &ct.App{Name: name}) hostID, jobID := utils.UUID(), utils.UUID() hc := tu.NewFakeHostClient(hostID) hc.SetAttach(jobID, newFakeLog(stream)) s.cc.SetHostClient(hostID, hc) return app, hostID, jobID }
func (s *S) TestKillJob(c *C) { app := s.createTestApp(c, &ct.App{Name: "killjob"}) hostID, jobID := utils.UUID(), utils.UUID() hc := tu.NewFakeHostClient(hostID) s.cc.SetHostClient(hostID, hc) res, err := s.Delete("/apps/" + app.ID + "/jobs/" + hostID + "-" + jobID) c.Assert(err, IsNil) c.Assert(res.StatusCode, Equals, 200) c.Assert(hc.IsStopped(jobID), Equals, true) }
func (s *S) TestPutResource(c *C) { app := s.createTestApp(c, &ct.App{Name: "put-resource"}) provider := s.createTestProvider(c, &ct.Provider{URL: "https://example.ca", Name: "put-resource"}) resource := &ct.Resource{ ExternalID: "/foo/bar", Env: map[string]string{"FOO": "BAR"}, Apps: []string{app.ID}, } id := utils.UUID() path := fmt.Sprintf("/providers/%s/resources/%s", provider.ID, id) created := &ct.Resource{} _, err := s.Put(path, resource, created) c.Assert(err, IsNil) c.Assert(created.ID, Equals, id) c.Assert(created.ProviderID, Equals, provider.ID) c.Assert(created.Env, DeepEquals, resource.Env) c.Assert(created.Apps, DeepEquals, resource.Apps) c.Assert(created.CreatedAt, Not(IsNil)) gotResource := &ct.Resource{} _, err = s.Get(path, gotResource) c.Assert(err, IsNil) c.Assert(gotResource, DeepEquals, created) }
func (s *S) TestCreateArtifact(c *C) { for i, id := range []string{"", utils.UUID()} { in := &ct.Artifact{ ID: id, Type: "docker-image", URI: fmt.Sprintf("docker://flynn/host?id=adsf%d", i), } out := s.createTestArtifact(c, in) c.Assert(out.Type, Equals, in.Type) c.Assert(out.URI, Equals, in.URI) c.Assert(out.ID, Not(Equals), "") if id != "" { c.Assert(out.ID, Equals, id) } gotArtifact := &ct.Artifact{} res, err := s.Get("/artifacts/"+out.ID, gotArtifact) c.Assert(err, IsNil) c.Assert(gotArtifact, DeepEquals, out) res, err = s.Get("/artifacts/fail"+out.ID, gotArtifact) c.Assert(res.StatusCode, Equals, 404) } }
func (s *S) TestCreateApp(c *C) { // app with no name returns 400 res, err := s.Post("/apps", &ct.App{}, &ct.App{}) c.Assert(err, IsNil) c.Assert(res.StatusCode, Equals, 400) body, err := s.body(res) c.Assert(err, IsNil) c.Assert(body, Equals, `{"field":"name","message":"must not be blank"}`) for i, id := range []string{"", utils.UUID()} { name := fmt.Sprintf("create-app-%d", i) app := s.createTestApp(c, &ct.App{ID: id, Name: name, Protected: true, Meta: map[string]string{"foo": "bar"}}) c.Assert(app.Name, Equals, name) c.Assert(app.ID, Not(Equals), "") if id != "" { c.Assert(app.ID, Equals, id) } c.Assert(app.Protected, Equals, true) c.Assert(app.Meta["foo"], Equals, "bar") gotApp := &ct.App{} res, err := s.Get("/apps/"+app.ID, gotApp) c.Assert(err, IsNil) c.Assert(gotApp, DeepEquals, app) res, err = s.Get("/apps/"+app.Name, gotApp) c.Assert(err, IsNil) c.Assert(gotApp, DeepEquals, app) res, err = s.Get("/apps/fail"+app.ID, gotApp) c.Assert(res.StatusCode, Equals, 404) } }
func (r *AppRepo) Add(data interface{}) error { app := data.(*ct.App) if app.Name == "" { return ct.ValidationError{"name", "must not be blank"} } if len(app.Name) > 30 || !appNamePattern.MatchString(app.Name) { return ct.ValidationError{"name", "is invalid"} } if app.ID == "" { app.ID = utils.UUID() } var meta hstore.Hstore if len(app.Meta) > 0 { meta.Map = make(map[string]sql.NullString, len(app.Meta)) for k, v := range app.Meta { meta.Map[k] = sql.NullString{String: v, Valid: true} } } err := r.db.QueryRow("INSERT INTO apps (app_id, name, protected, meta) VALUES ($1, $2, $3, $4) RETURNING created_at, updated_at", app.ID, app.Name, app.Protected, meta).Scan(&app.CreatedAt, &app.UpdatedAt) app.ID = cleanUUID(app.ID) if !app.Protected && r.defaultDomain != "" { route := (&strowger.HTTPRoute{ Domain: fmt.Sprintf("%s.%s", app.Name, r.defaultDomain), Service: app.Name + "-web", }).ToRoute() route.ParentRef = routeParentRef(app) if err := r.router.CreateRoute(route); err != nil { log.Printf("Error creating default route for %s: %s", app.Name, err) } } return err }
func (r *fakeRouter) CreateRoute(route *strowger.Route) error { r.mtx.Lock() defer r.mtx.Unlock() route.ID = route.Type + "/" + utils.UUID() now := time.Now() route.CreatedAt = &now route.UpdatedAt = &now r.routes[route.ID] = route return nil }
func (s *S) TestCreateRelease(c *C) { for _, id := range []string{"", utils.UUID()} { in := &ct.Release{ID: id} out := s.createTestRelease(c, in) c.Assert(out.ArtifactID, Equals, in.ArtifactID) if id != "" { c.Assert(out.ID, Equals, id) } gotRelease := &ct.Release{} res, err := s.Get("/releases/"+out.ID, gotRelease) c.Assert(err, IsNil) c.Assert(gotRelease, DeepEquals, out) res, err = s.Get("/releases/fail"+out.ID, gotRelease) c.Assert(res.StatusCode, Equals, 404) } }
func (r *ArtifactRepo) Add(data interface{}) error { a := data.(*ct.Artifact) // TODO: actually validate if a.ID == "" { a.ID = utils.UUID() } err := r.db.QueryRow("INSERT INTO artifacts (artifact_id, type, uri) VALUES ($1, $2, $3) RETURNING created_at", a.ID, a.Type, a.URI).Scan(&a.CreatedAt) if e, ok := err.(*pq.Error); ok && e.Code.Name() == "unique_violation" { err = r.db.QueryRow("SELECT artifact_id, created_at FROM artifacts WHERE type = $1 AND uri = $2", a.Type, a.URI).Scan(&a.ID, &a.CreatedAt) if err != nil { return err } } a.ID = cleanUUID(a.ID) return err }
func (s *S) TestRunJobDetached(c *C) { app := s.createTestApp(c, &ct.App{Name: "run-detached"}) hostID := utils.UUID() s.cc.SetHosts(map[string]host.Host{hostID: host.Host{}}) artifact := s.createTestArtifact(c, &ct.Artifact{Type: "docker", URI: "docker://foo/bar"}) release := s.createTestRelease(c, &ct.Release{ ArtifactID: artifact.ID, Env: map[string]string{"RELEASE": "true", "FOO": "bar"}, }) cmd := []string{"foo", "bar"} req := &ct.NewJob{ ReleaseID: release.ID, Cmd: cmd, Env: map[string]string{"JOB": "true", "FOO": "baz"}, } res := &ct.Job{} _, err := s.Post(fmt.Sprintf("/apps/%s/jobs", app.ID), req, res) c.Assert(err, IsNil) c.Assert(res.ID, Not(Equals), "") c.Assert(res.ReleaseID, Equals, release.ID) c.Assert(res.Type, Equals, "") c.Assert(res.Cmd, DeepEquals, cmd) job := s.cc.GetHost(hostID).Jobs[0] c.Assert(res.ID, Equals, hostID+"-"+job.ID) c.Assert(job.Attributes, DeepEquals, map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.release": release.ID, }) c.Assert(job.Config.Cmd, DeepEquals, []string{"foo", "bar"}) sort.Strings(job.Config.Env) c.Assert(job.Config.Env, DeepEquals, []string{"FOO=baz", "JOB=true", "RELEASE=true"}) c.Assert(job.Config.AttachStdout, Equals, true) c.Assert(job.Config.AttachStderr, Equals, true) c.Assert(job.Config.AttachStdin, Equals, false) c.Assert(job.Config.StdinOnce, Equals, false) c.Assert(job.Config.OpenStdin, Equals, false) }
func (r *ReleaseRepo) Add(data interface{}) error { release := data.(*ct.Release) releaseCopy := *release releaseCopy.ID = "" releaseCopy.ArtifactID = "" releaseCopy.CreatedAt = nil data, err := json.Marshal(&releaseCopy) if err != nil { return err } if release.ID == "" { release.ID = utils.UUID() } err = r.db.QueryRow("INSERT INTO releases (release_id, artifact_id, data) VALUES ($1, $2, $3) RETURNING created_at", release.ID, release.ArtifactID, data).Scan(&release.CreatedAt) release.ID = cleanUUID(release.ID) release.ArtifactID = cleanUUID(release.ArtifactID) return err }
func (rr *ResourceRepo) Add(r *ct.Resource) error { if r.ID == "" { r.ID = utils.UUID() } tx, err := rr.db.Begin() if err != nil { return err } err = tx.QueryRow(`INSERT INTO resources (resource_id, provider_id, external_id, env) VALUES ($1, $2, $3, $4) RETURNING created_at`, r.ID, r.ProviderID, r.ExternalID, envHstore(r.Env)).Scan(&r.CreatedAt) if err != nil { tx.Rollback() return err } for i, appID := range r.Apps { var filterSQL string var args []interface{} if idPattern.MatchString(appID) { filterSQL = "app_id = $1 OR name = $2), $3)" args = []interface{}{appID, appID, r.ID} } else { filterSQL = "name = $1), $2)" args = []interface{}{appID, r.ID} } err = tx.QueryRow("INSERT INTO app_resources (app_id, resource_id) VALUES ((SELECT app_id FROM apps WHERE "+ filterSQL+" RETURNING app_id", args...).Scan(&r.Apps[i]) if err != nil { tx.Rollback() return err } r.Apps[i] = cleanUUID(r.Apps[i]) } r.ID = cleanUUID(r.ID) return tx.Commit() }
func (s *S) TestRunJobAttached(c *C) { app := s.createTestApp(c, &ct.App{Name: "run-attached"}) hostID := utils.UUID() hc := tu.NewFakeHostClient(hostID) done := make(chan struct{}) var jobID string hc.SetAttachFunc("*", func(req *host.AttachReq, wait bool) (cluster.ReadWriteCloser, func() error, error) { c.Assert(wait, Equals, true) c.Assert(req.JobID, Not(Equals), "") c.Assert(req, DeepEquals, &host.AttachReq{ JobID: req.JobID, Flags: host.AttachFlagStdout | host.AttachFlagStderr | host.AttachFlagStdin | host.AttachFlagStream, Height: 20, Width: 10, }) jobID = req.JobID piper, pipew := io.Pipe() go func() { stdin, err := ioutil.ReadAll(piper) c.Assert(err, IsNil) c.Assert(string(stdin), Equals, "test in") close(done) }() return &fakeAttachStream{strings.NewReader("test out"), pipew}, func() error { return nil }, nil }) s.cc.SetHostClient(hostID, hc) s.cc.SetHosts(map[string]host.Host{hostID: host.Host{}}) artifact := s.createTestArtifact(c, &ct.Artifact{Type: "docker", URI: "docker://foo/bar"}) release := s.createTestRelease(c, &ct.Release{ ArtifactID: artifact.ID, Env: map[string]string{"RELEASE": "true", "FOO": "bar"}, }) data, _ := json.Marshal(&ct.NewJob{ ReleaseID: release.ID, Cmd: []string{"foo", "bar"}, Env: map[string]string{"JOB": "true", "FOO": "baz"}, TTY: true, Columns: 10, Lines: 20, }) req, err := http.NewRequest("POST", s.srv.URL+"/apps/"+app.ID+"/jobs", bytes.NewBuffer(data)) c.Assert(err, IsNil) req.SetBasicAuth("", authKey) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/vnd.flynn.attach") _, rwc, err := utils.HijackRequest(req, nil) c.Assert(err, IsNil) _, err = rwc.Write([]byte("test in")) c.Assert(err, IsNil) rwc.CloseWrite() stdout, err := ioutil.ReadAll(rwc) c.Assert(err, IsNil) c.Assert(string(stdout), Equals, "test out") rwc.Close() job := s.cc.GetHost(hostID).Jobs[0] c.Assert(job.ID, Equals, jobID) c.Assert(job.Attributes, DeepEquals, map[string]string{ "flynn-controller.app": app.ID, "flynn-controller.release": release.ID, }) c.Assert(job.Config.Cmd, DeepEquals, []string{"foo", "bar"}) sort.Strings(job.Config.Env) c.Assert(job.Config.Env, DeepEquals, []string{"FOO=baz", "JOB=true", "RELEASE=true"}) c.Assert(job.Config.AttachStdout, Equals, true) c.Assert(job.Config.AttachStderr, Equals, true) c.Assert(job.Config.AttachStdin, Equals, true) c.Assert(job.Config.StdinOnce, Equals, true) c.Assert(job.Config.OpenStdin, Equals, true) }
func (a *RunAppAction) Run(s *State) error { if a.AppStep != "" { data, err := getAppStep(s, a.AppStep) if err != nil { return err } a.App = data.App procs := a.Processes a.ExpandedFormation = data.ExpandedFormation a.Processes = procs } as := &RunAppState{ ExpandedFormation: a.ExpandedFormation, Resources: make([]*resource.Resource, 0, len(a.Resources)), Providers: make([]*ct.Provider, 0, len(a.Resources)), } s.StepData[a.ID] = as if a.App == nil || a.App.ID == "" { a.App = &ct.App{ID: utils.UUID()} } if a.Artifact == nil { return errors.New("bootstrap: artifact must be set") } if a.Artifact.ID == "" { a.Artifact.ID = utils.UUID() } if a.Release == nil { return errors.New("bootstrap: release must be set") } if a.Release.ID == "" { a.Release.ID = utils.UUID() } a.Release.ArtifactID = a.Artifact.ID if a.Release.Env == nil { a.Release.Env = make(map[string]string) } interpolateRelease(s, a.Release) for _, p := range a.Resources { server, err := resource.NewServer(p.URL) if err != nil { return err } res, err := server.Provision(nil) server.Close() if err != nil { return err } as.Providers = append(as.Providers, p) as.Resources = append(as.Resources, res) for k, v := range res.Env { a.Release.Env[k] = v } } cc, err := s.ClusterClient() if err != nil { return err } hosts, err := cc.ListHosts() if err != nil { return err } hostIDs := make([]string, 0, len(hosts)) for id := range hosts { hostIDs = append(hostIDs, id) } for typ, count := range a.Processes { for i := 0; i < count; i++ { config, err := utils.JobConfig(a.ExpandedFormation, typ) if err != nil { return err } job, err := startJob(s, hostIDs[i%len(hosts)], config) if err != nil { return err } as.Jobs = append(as.Jobs, *job) } } return nil }
func (a *AddAppAction) Run(s *State) error { data, ok := s.StepData[a.FromStep].(*RunAppState) if !ok { return fmt.Errorf("bootstrap: unable to find step %q", a.FromStep) } as := &AppState{ ExpandedFormation: &ct.ExpandedFormation{}, Resources: make([]*ct.Resource, 0, len(data.Resources)), } s.StepData[a.ID] = as client, err := s.ControllerClient() if err != nil { return err } a.App.ID = data.App.ID if err := client.CreateApp(a.App); err != nil { return err } as.App = a.App if err := client.CreateArtifact(data.Artifact); err != nil { return err } as.Artifact = data.Artifact if err := client.CreateRelease(data.Release); err != nil { return err } as.Release = data.Release for i, p := range data.Providers { if provider, ok := s.Providers[p.Name]; ok { p = provider } else { if err := client.CreateProvider(p); err != nil { return err } s.Providers[p.Name] = p } resource := &ct.Resource{ ID: utils.UUID(), ProviderID: p.ID, ExternalID: data.Resources[i].ID, Env: data.Resources[i].Env, } if err := client.PutResource(resource); err != nil { return err } as.Resources = append(as.Resources, resource) } formation := &ct.Formation{ AppID: data.App.ID, ReleaseID: data.Release.ID, Processes: data.Processes, } if err := client.PutFormation(formation); err != nil { return err } as.Formation = formation if err := client.SetAppRelease(data.App.ID, data.Release.ID); err != nil { return err } return nil }