func (s *S) TestDeleteAllAppImageNamesSimilarApps(c *check.C) { data := map[string]interface{}{"healthcheck": map[string]interface{}{"path": "/test"}} err := image.AppendAppImageName("myapp", "tsuru/app-myapp:v1") c.Assert(err, check.IsNil) err = image.SaveImageCustomData("tsuru/app-myapp:v1", data) c.Assert(err, check.IsNil) err = image.AppendAppImageName("myapp-dev", "tsuru/app-myapp-dev:v1") c.Assert(err, check.IsNil) err = image.SaveImageCustomData("tsuru/app-myapp-dev:v1", data) c.Assert(err, check.IsNil) err = image.DeleteAllAppImageNames("myapp") c.Assert(err, check.IsNil) _, err = image.ListAppImages("myapp") c.Assert(err, check.ErrorMatches, "not found") _, err = image.ListAppImages("myapp-dev") c.Assert(err, check.IsNil) yamlData, err := image.GetImageTsuruYamlData("tsuru/app-myapp:v1") c.Assert(err, check.IsNil) c.Assert(yamlData, check.DeepEquals, provision.TsuruYamlData{}) yamlData, err = image.GetImageTsuruYamlData("tsuru/app-myapp-dev:v1") c.Assert(err, check.IsNil) c.Assert(yamlData, check.DeepEquals, provision.TsuruYamlData{ Healthcheck: provision.TsuruYamlHealthcheck{Path: "/test"}, }) }
func (s *S) TestDeleteAllAppImageNamesRemovesCustomDataWithoutImages(c *check.C) { imgName := "tsuru/app-myapp:v1" data := map[string]interface{}{"healthcheck": map[string]interface{}{"path": "/test"}} err := image.SaveImageCustomData(imgName, data) c.Assert(err, check.IsNil) err = image.DeleteAllAppImageNames("myapp") c.Assert(err, check.ErrorMatches, "not found") yamlData, err := image.GetImageTsuruYamlData(imgName) c.Assert(err, check.IsNil) c.Assert(yamlData, check.DeepEquals, provision.TsuruYamlData{}) }
func (p *dockerProvisioner) runRestartAfterHooks(cont *container.Container, w io.Writer) error { yamlData, err := image.GetImageTsuruYamlData(cont.Image) if err != nil { return err } cmds := yamlData.Hooks.Restart.After for _, cmd := range cmds { err := cont.Exec(p, w, w, cmd) if err != nil { return errors.Wrapf(err, "couldn't execute restart:after hook %q(%s)", cmd, cont.ShortID()) } } return nil }
func (s *S) TestPullAppImageNamesRemovesCustomData(c *check.C) { img1Name := "tsuru/app-myapp:v1" err := image.AppendAppImageName("myapp", img1Name) c.Assert(err, check.IsNil) err = image.AppendAppImageName("myapp", "tsuru/app-myapp:v2") c.Assert(err, check.IsNil) err = image.AppendAppImageName("myapp", "tsuru/app-myapp:v3") c.Assert(err, check.IsNil) data := map[string]interface{}{"healthcheck": map[string]interface{}{"path": "/test"}} err = image.SaveImageCustomData(img1Name, data) c.Assert(err, check.IsNil) err = image.PullAppImageNames("myapp", []string{img1Name}) c.Assert(err, check.IsNil) images, err := image.ListAppImages("myapp") c.Assert(err, check.IsNil) c.Assert(images, check.DeepEquals, []string{"tsuru/app-myapp:v2", "tsuru/app-myapp:v3"}) yamlData, err := image.GetImageTsuruYamlData(img1Name) c.Assert(err, check.IsNil) c.Assert(yamlData, check.DeepEquals, provision.TsuruYamlData{}) }
func LeanContainerCmdsWithExtra(processName, imageId string, app provision.App, extraCmds []string) ([]string, string, error) { processCmd, processName, err := ProcessCmdForImage(processName, imageId) if err != nil { return nil, "", err } if len(processCmd) == 0 { // Legacy support, no processes are yet registered for this app's // containers. var cmds []string cmds, err = runWithAgentCmds(app) return cmds, "", err } yamlData, err := image.GetImageTsuruYamlData(imageId) if err != nil { return nil, "", err } extraCmds = append(extraCmds, yamlData.Hooks.Restart.Before...) before := strings.Join(extraCmds, " && ") if before != "" { before += " && " } if processName == "" { processName = "web" } allCmds := []string{ "/bin/sh", "-lc", "[ -d /home/application/current ] && cd /home/application/current; " + before, } if len(processCmd) > 1 { allCmds[len(allCmds)-1] += "exec $0 \"$@\"" allCmds = append(allCmds, processCmd...) } else { allCmds[len(allCmds)-1] += "exec " + processCmd[0] } return allCmds, processName, nil }
OnError: rollbackNotice, Forward: func(ctx action.FWContext) (action.Result, error) { args := ctx.Params[0].(changeUnitsPipelineArgs) if err := checkCanceled(args.event); err != nil { return nil, err } newContainers := ctx.Previous.([]container.Container) r, err := getRouterForApp(args.app) if err != nil { return nil, err } hcRouter, ok := r.(router.CustomHealthcheckRouter) if !ok { return newContainers, nil } yamlData, err := image.GetImageTsuruYamlData(args.imageId) if err != nil { return nil, err } writer := args.writer if writer == nil { writer = ioutil.Discard } hcData := yamlData.Healthcheck.ToRouterHC() msg := fmt.Sprintf("Path: %s", hcData.Path) if hcData.Status != 0 { msg = fmt.Sprintf("%s, Status: %d", msg, hcData.Status) } if hcData.Body != "" { msg = fmt.Sprintf("%s, Body: %s", msg, hcData.Body) }
func runHealthcheck(cont *container.Container, w io.Writer) error { yamlData, err := image.GetImageTsuruYamlData(cont.Image) if err != nil { return err } path := yamlData.Healthcheck.Path method := yamlData.Healthcheck.Method match := yamlData.Healthcheck.Match status := yamlData.Healthcheck.Status allowedFailures := yamlData.Healthcheck.AllowedFailures if path == "" { return nil } path = strings.TrimSpace(strings.TrimLeft(path, "/")) if method == "" { method = "get" } method = strings.ToUpper(method) if status == 0 && match == "" { status = 200 } var matchRE *regexp.Regexp if match != "" { match = "(?s)" + match matchRE, err = regexp.Compile(match) if err != nil { return err } } maxWaitTime, _ := config.GetInt("docker:healthcheck:max-time") if maxWaitTime == 0 { maxWaitTime = 120 } maxWaitTime = maxWaitTime * int(time.Second) sleepTime := 3 * time.Second startedTime := time.Now() url := fmt.Sprintf("http://%s:%s/%s", cont.HostAddr, cont.HostPort, path) for { var lastError error = nil req, err := http.NewRequest(method, url, nil) if err != nil { return err } rsp, err := net.Dial5Full60ClientNoKeepAlive.Do(req) if err != nil { lastError = errors.Wrapf(err, "healthcheck fail(%s)", cont.ShortID()) } else { defer rsp.Body.Close() if status != 0 && rsp.StatusCode != status { lastError = errors.Errorf("healthcheck fail(%s): wrong status code, expected %d, got: %d", cont.ShortID(), status, rsp.StatusCode) } else if matchRE != nil { result, err := ioutil.ReadAll(rsp.Body) if err != nil { lastError = err } if !matchRE.Match(result) { lastError = errors.Errorf("healthcheck fail(%s): unexpected result, expected %q, got: %s", cont.ShortID(), match, string(result)) } } if lastError != nil { if allowedFailures == 0 { return lastError } allowedFailures-- } } if lastError == nil { fmt.Fprintf(w, " ---> healthcheck successful(%s)\n", cont.ShortID()) return nil } if time.Since(startedTime) > time.Duration(maxWaitTime) { return lastError } fmt.Fprintf(w, " ---> %s. Trying again in %s\n", lastError.Error(), sleepTime) time.Sleep(sleepTime) } }
func serviceSpecForApp(opts tsuruServiceOpts) (*swarm.ServiceSpec, error) { var envs []string for _, envData := range opts.app.Envs() { envs = append(envs, fmt.Sprintf("%s=%s", envData.Name, envData.Value)) } host, _ := config.GetString("host") envs = append(envs, fmt.Sprintf("%s=%s", "TSURU_HOST", host)) var cmds []string var err error var endpointSpec *swarm.EndpointSpec var networks []swarm.NetworkAttachmentConfig var healthConfig *container.HealthConfig port := dockercommon.WebProcessDefaultPort() portInt, _ := strconv.Atoi(port) if !opts.isDeploy && !opts.isIsolatedRun { envs = append(envs, []string{ fmt.Sprintf("%s=%s", "port", port), fmt.Sprintf("%s=%s", "PORT", port), }...) endpointSpec = &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{ {TargetPort: uint32(portInt), PublishedPort: 0}, }, } networks = []swarm.NetworkAttachmentConfig{ {Target: networkNameForApp(opts.app)}, } extra := []string{extraRegisterCmds(opts.app)} cmds, _, err = dockercommon.LeanContainerCmdsWithExtra(opts.process, opts.image, opts.app, extra) if err != nil { return nil, errors.WithStack(err) } var yamlData provision.TsuruYamlData yamlData, err = image.GetImageTsuruYamlData(opts.image) if err != nil { return nil, errors.WithStack(err) } healthConfig = toHealthConfig(yamlData.Healthcheck, portInt) } restartCount := 0 replicas := 0 if opts.baseSpec != nil { replicas, err = strconv.Atoi(opts.baseSpec.Labels[labelProcessReplicas.String()]) if err != nil && opts.baseSpec.Mode.Replicated != nil { replicas = int(*opts.baseSpec.Mode.Replicated.Replicas) } restartCount, _ = strconv.Atoi(opts.baseSpec.Labels[labelServiceRestart.String()]) } if opts.processState.increment != 0 { replicas += opts.processState.increment if replicas < 0 { return nil, errors.New("cannot have less than 0 units") } } else if replicas == 0 && opts.processState.start { replicas = 1 } routerName, err := opts.app.GetRouter() if err != nil { return nil, errors.WithStack(err) } routerType, _, err := router.Type(routerName) if err != nil { return nil, errors.WithStack(err) } srvName := serviceNameForApp(opts.app, opts.process) if opts.isDeploy { replicas = 1 srvName = fmt.Sprintf("%s-build", srvName) } if opts.isIsolatedRun { replicas = 1 srvName = fmt.Sprintf("%sisolated-run", srvName) } uReplicas := uint64(replicas) if opts.processState.stop { uReplicas = 0 } if opts.processState.restart { restartCount++ } labels := map[string]string{ labelService.String(): strconv.FormatBool(true), labelServiceDeploy.String(): strconv.FormatBool(opts.isDeploy), labelServiceIsolatedRun.String(): strconv.FormatBool(opts.isIsolatedRun), labelServiceBuildImage.String(): opts.buildImage, labelAppName.String(): opts.app.GetName(), labelAppProcess.String(): opts.process, labelAppPlatform.String(): opts.app.GetPlatform(), labelRouterName.String(): routerName, labelRouterType.String(): routerType, labelProcessReplicas.String(): strconv.Itoa(replicas), labelServiceRestart.String(): strconv.Itoa(restartCount), labelPoolName.String(): opts.app.GetPool(), labelProvisionerName.String(): "swarm", } user, err := config.GetString("docker:user") if err != nil { user, _ = config.GetString("docker:ssh:user") } opts.constraints = append(opts.constraints, fmt.Sprintf("node.labels.%s == %s", labelNodePoolName, opts.app.GetPool())) spec := swarm.ServiceSpec{ TaskTemplate: swarm.TaskSpec{ ContainerSpec: swarm.ContainerSpec{ Image: opts.image, Env: envs, Labels: labels, Command: cmds, User: user, Healthcheck: healthConfig, }, Networks: networks, RestartPolicy: &swarm.RestartPolicy{ Condition: swarm.RestartPolicyConditionAny, }, Placement: &swarm.Placement{ Constraints: opts.constraints, }, }, Networks: networks, EndpointSpec: endpointSpec, Annotations: swarm.Annotations{ Name: srvName, Labels: labels, }, Mode: swarm.ServiceMode{ Replicated: &swarm.ReplicatedService{ Replicas: &uReplicas, }, }, } return &spec, nil }