harness-drone/scheduler/nomad/nomad.go
2019-02-19 15:56:41 -08:00

251 lines
6.8 KiB
Go

// Copyright 2019 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by the Drone Non-Commercial License
// that can be found in the LICENSE file.
// +build !oss
package nomad
import (
"context"
"errors"
"fmt"
"runtime"
"strings"
"time"
"github.com/drone/drone/core"
"github.com/drone/drone/scheduler/internal"
"github.com/dchest/uniuri"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/nomad/api"
"github.com/sirupsen/logrus"
)
var _ core.Scheduler = (*nomadScheduler)(nil)
// Docker host.
const (
dockerHostPosix = "/var/run/docker.sock"
dockerHostWindows = "////./pipe/docker_engine"
)
// Config is the configuration for the Nomad scheduler.
type Config struct {
Datacenter []string
Namespace string
Region string
DockerImage string
DockerImagePull bool
DockerImagePriv []string
DockerHost string
DockerHostWin string
LimitMemory int
LimitCompute int
RequestMemory int
RequestCompute int
CallbackHost string
CallbackProto string
CallbackSecret string
SecretToken string
SecretEndpoint string
SecretInsecure bool
RegistryToken string
RegistryEndpoint string
RegistryInsecure bool
LogDebug bool
LogTrace bool
LogPretty bool
LogText bool
}
type nomadScheduler struct {
client *api.Client
config Config
}
// FromConfig returns a new Nomad scheduler.
func FromConfig(conf Config) (core.Scheduler, error) {
config := api.DefaultConfig()
client, err := api.NewClient(config)
if err != nil {
return nil, err
}
return &nomadScheduler{client: client, config: conf}, nil
}
// Schedule schedules the stage for execution.
func (s *nomadScheduler) Schedule(ctx context.Context, stage *core.Stage) error {
env := map[string]string{
"DRONE_RUNNER_PRIVILEGED_IMAGES": strings.Join(s.config.DockerImagePriv, ","),
"DRONE_LIMIT_MEM": fmt.Sprint(s.config.LimitMemory),
"DRONE_LIMIT_CPU": fmt.Sprint(s.config.LimitCompute),
"DRONE_STAGE_ID": fmt.Sprint(stage.ID),
"DRONE_LOGS_DEBUG": fmt.Sprint(s.config.LogDebug),
"DRONE_LOGS_TRACE": fmt.Sprint(s.config.LogTrace),
"DRONE_LOGS_PRETTY": fmt.Sprint(s.config.LogPretty),
"DRONE_LOGS_TEXT": fmt.Sprint(s.config.LogText),
"DRONE_RPC_PROTO": s.config.CallbackProto,
"DRONE_RPC_HOST": s.config.CallbackHost,
"DRONE_RPC_SECRET": s.config.CallbackSecret,
"DRONE_RPC_DEBUG": fmt.Sprint(s.config.LogTrace),
"DRONE_REGISTRY_ENDPOINT": s.config.RegistryEndpoint,
"DRONE_REGISTRY_SECRET": s.config.RegistryToken,
"DRONE_REGISTRY_SKIP_VERIFY": fmt.Sprint(s.config.RegistryInsecure),
"DRONE_SECRET_ENDPOINT": s.config.SecretEndpoint,
"DRONE_SECRET_SECRET": s.config.SecretToken,
"DRONE_SECRET_SKIP_VERIFY": fmt.Sprint(s.config.SecretInsecure),
}
volume := "/var/run/docker.sock:/var/run/docker.sock"
if stage.OS == "windows" {
volume = "////./pipe/docker_engine:////./pipe/docker_engine"
}
task := &api.Task{
Name: "stage",
Driver: "docker",
Env: env,
Resources: &api.Resources{},
Config: map[string]interface{}{
"image": internal.DefaultImage(s.config.DockerImage),
"force_pull": s.config.DockerImagePull,
"volumes": []string{volume},
},
}
if i := s.config.RequestCompute; i != 0 {
task.Resources.CPU = intToPtr(i)
}
if i := s.config.RequestMemory; i != 0 {
task.Resources.MemoryMB = intToPtr(i)
}
rand := uniuri.NewLen(12)
name := fmt.Sprintf("drone-job-%d-%s", stage.BuildID, rand)
job := &api.Job{
ID: stringToPtr(name),
Name: stringToPtr(name),
Type: stringToPtr("batch"),
Datacenters: s.config.Datacenter,
TaskGroups: []*api.TaskGroup{
&api.TaskGroup{
Name: stringToPtr("pipeline"),
Tasks: []*api.Task{task},
RestartPolicy: &api.RestartPolicy{
Mode: stringToPtr("fail"),
},
},
},
Meta: map[string]string{
"io.drone": "true",
"io.core.stage.created": time.Unix(stage.Created, 0).String(),
"io.core.stage.scheduled": time.Now().String(),
"io.core.stage.id": fmt.Sprint(stage.ID),
"io.core.stage.number": fmt.Sprint(stage.Number),
"io.core.stage.os": fmt.Sprint(stage.OS),
"io.core.stage.arch": fmt.Sprint(stage.Arch),
"io.core.build.id": fmt.Sprint(stage.BuildID),
"io.core.repo.id": fmt.Sprint(stage.RepoID),
},
}
if s := s.config.Namespace; s != "" {
job.Namespace = stringToPtr(s)
}
if s := s.config.Region; s != "" {
job.Region = stringToPtr(s)
}
// if we are running on darwin we disable os and arch
// constraints, since it is possible nomad is running
// on the host machine and reports a darwin os, instead
// of a linux os.
if runtime.GOOS != "darwin" {
job.Constraints = []*api.Constraint{
{
LTarget: "${attr.kernel.name}",
RTarget: stage.OS,
Operand: "=",
},
{
LTarget: "${attr.cpu.arch}",
RTarget: stage.Arch,
Operand: "=",
},
}
}
for k, v := range stage.Labels {
job.Constraints = append(job.Constraints, &api.Constraint{
LTarget: fmt.Sprintf("${meta.%s}", k),
RTarget: v,
Operand: "=",
})
}
log := logrus.WithFields(logrus.Fields{
"stage-id": stage.ID,
"stage-number": stage.Number,
"stage-name": stage.Name,
"repo-id": stage.RepoID,
"build-id": stage.BuildID,
})
log.Debugf("nomad: creating job")
_, _, err := s.client.Jobs().RegisterOpts(job, &api.RegisterOptions{}, nil)
if err != nil {
log.WithError(err).Errorln("nomad: cannot create job")
} else {
log.WithField("job-id", job.ID).Debugf("nomad: successfully created job")
}
return err
}
func (s *nomadScheduler) Request(context.Context, core.Filter) (*core.Stage, error) {
return nil, errors.New("not implemented")
}
// Cancel cancels a scheduled or running stage.
func (s *nomadScheduler) Cancel(ctx context.Context, id int64) error {
prefix := fmt.Sprintf("drone-job-%d-", id)
jobs, _, err := s.client.Jobs().PrefixList(prefix)
if err != nil {
return err
}
var result error
for _, job := range jobs {
_, _, err := s.client.Jobs().Deregister(job.ID, false, nil)
if err != nil {
result = multierror.Append(result, err)
}
}
return result
}
func (s *nomadScheduler) Cancelled(context.Context, int64) (bool, error) {
return false, errors.New("not implemented")
}
func (s *nomadScheduler) Stats(context.Context) (interface{}, error) {
return nil, errors.New("not implemented")
}
// stringToPtr returns the pointer to a string
func stringToPtr(str string) *string {
return &str
}
// intToPtr returns the pointer to a int
func intToPtr(i int) *int {
return &i
}
// durationToPtr returns the pointer to a duration
func durationToPtr(dur time.Duration) *time.Duration {
return &dur
}