Merge branch 'master' of https://github.com/hashicorp/nomad
This commit is contained in:
commit
171a23cb74
|
@ -20,7 +20,7 @@ func TestCompose(t *testing.T) {
|
|||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{80, 443},
|
||||
ReservedPorts: []Port{{"", 80}, {"", 443}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -83,9 +83,9 @@ func TestCompose(t *testing.T) {
|
|||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{
|
||||
80,
|
||||
443,
|
||||
ReservedPorts: []Port{
|
||||
{"", 80},
|
||||
{"", 443},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -97,7 +97,7 @@ func TestCompose(t *testing.T) {
|
|||
Operand: "=",
|
||||
},
|
||||
},
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
},
|
||||
Meta: map[string]string{
|
||||
|
|
|
@ -10,12 +10,17 @@ type Resources struct {
|
|||
Networks []*NetworkResource
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
Label string
|
||||
Value int
|
||||
}
|
||||
|
||||
// NetworkResource is used to describe required network
|
||||
// resources of a given task.
|
||||
type NetworkResource struct {
|
||||
Public bool
|
||||
CIDR string
|
||||
ReservedPorts []int
|
||||
DynamicPorts []string
|
||||
ReservedPorts []Port
|
||||
DynamicPorts []Port
|
||||
MBits int
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ func (g *TaskGroup) AddTask(t *Task) *TaskGroup {
|
|||
type Task struct {
|
||||
Name string
|
||||
Driver string
|
||||
Config map[string]string
|
||||
Config map[string]interface{}
|
||||
Constraints []*Constraint
|
||||
Env map[string]string
|
||||
Resources *Resources
|
||||
|
@ -84,7 +84,7 @@ func NewTask(name, driver string) *Task {
|
|||
// the task.
|
||||
func (t *Task) SetConfig(key, val string) *Task {
|
||||
if t.Config == nil {
|
||||
t.Config = make(map[string]string)
|
||||
t.Config = make(map[string]interface{})
|
||||
}
|
||||
t.Config[key] = val
|
||||
return t
|
||||
|
|
|
@ -130,7 +130,7 @@ func TestTask_SetConfig(t *testing.T) {
|
|||
|
||||
// Set another config value
|
||||
task.SetConfig("baz", "zip")
|
||||
expect := map[string]string{"foo": "bar", "baz": "zip"}
|
||||
expect := map[string]interface{}{"foo": "bar", "baz": "zip"}
|
||||
if !reflect.DeepEqual(task.Config, expect) {
|
||||
t.Fatalf("expect: %#v, got: %#v", expect, task.Config)
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ func TestTask_Require(t *testing.T) {
|
|||
&NetworkResource{
|
||||
CIDR: "0.0.0.0/0",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{80, 443},
|
||||
ReservedPorts: []Port{{"", 80}, {"", 443}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ var (
|
|||
t1 = &structs.Task{
|
||||
Name: "web",
|
||||
Driver: "exec",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/date",
|
||||
"args": "+%s",
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ var (
|
|||
t2 = &structs.Task{
|
||||
Name: "web2",
|
||||
Driver: "exec",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/date",
|
||||
"args": "+%s",
|
||||
},
|
||||
|
|
|
@ -9,14 +9,15 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/args"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type DockerDriver struct {
|
||||
|
@ -24,6 +25,38 @@ type DockerDriver struct {
|
|||
fingerprint.StaticFingerprinter
|
||||
}
|
||||
|
||||
type DockerAuthConfig struct {
|
||||
UserName string `mapstructure:"auth.username"` // user name of the registry
|
||||
Password string `mapstructure:"auth.password"` // password to access the registry
|
||||
Email string `mapstructure:"auth.email"` // email address of the user who is allowed to access the registry
|
||||
ServerAddress string `mapstructure:"auth.server_address"` // server address of the registry
|
||||
|
||||
}
|
||||
|
||||
type DockerDriverConfig struct {
|
||||
DockerAuthConfig
|
||||
ImageName string `mapstructure:"image"` // Container's Image Name
|
||||
Command string `mapstructure:"command"` // The Command/Entrypoint to run when the container starts up
|
||||
Args string `mapstructure:"args"` // The arguments to the Command/Entrypoint
|
||||
NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host, net and none
|
||||
PortMap []map[string]int `mapstructure:"port_map"` // A map of host port labels and the ports exposed on the container
|
||||
Privileged bool `mapstructure:"privileged"` // Flag to run the container in priviledged mode
|
||||
DNS string `mapstructure:"dns_server"` // DNS Server for containers
|
||||
SearchDomains string `mapstructure:"search_domains"` // DNS Search domains for containers
|
||||
Hostname string `mapstructure:"hostname"` // Hostname for containers
|
||||
}
|
||||
|
||||
func (c *DockerDriverConfig) Validate() error {
|
||||
if c.ImageName == "" {
|
||||
return fmt.Errorf("Docker Driver needs an image name")
|
||||
}
|
||||
|
||||
if len(c.PortMap) > 1 {
|
||||
return fmt.Errorf("Only one port_map block is allowed in the docker driver config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dockerPID struct {
|
||||
ImageID string
|
||||
ContainerID string
|
||||
|
@ -117,7 +150,7 @@ func (d *DockerDriver) containerBinds(alloc *allocdir.AllocDir, task *structs.Ta
|
|||
}
|
||||
|
||||
// createContainer initializes a struct needed to call docker.client.CreateContainer()
|
||||
func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (docker.CreateContainerOptions, error) {
|
||||
func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task, driverConfig *DockerDriverConfig) (docker.CreateContainerOptions, error) {
|
||||
var c docker.CreateContainerOptions
|
||||
if task.Resources == nil {
|
||||
d.logger.Printf("[ERR] driver.docker: task.Resources is empty")
|
||||
|
@ -135,8 +168,8 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (do
|
|||
env.SetTaskLocalDir(filepath.Join("/", allocdir.TaskLocal))
|
||||
|
||||
config := &docker.Config{
|
||||
Env: env.List(),
|
||||
Image: task.Config["image"],
|
||||
Image: driverConfig.ImageName,
|
||||
Hostname: driverConfig.Hostname,
|
||||
}
|
||||
|
||||
hostConfig := &docker.HostConfig{
|
||||
|
@ -185,22 +218,18 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (do
|
|||
return c, fmt.Errorf("Unable to parse docker.privileged.enabled: %s", err)
|
||||
}
|
||||
|
||||
if v, ok := task.Config["privileged"]; ok {
|
||||
taskPrivileged, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("Unable to parse boolean value from task config option 'privileged': %v", err)
|
||||
}
|
||||
if taskPrivileged && !hostPrivileged {
|
||||
if driverConfig.Privileged {
|
||||
if !hostPrivileged {
|
||||
return c, fmt.Errorf(`Unable to set privileged flag since "docker.privileged.enabled" is false`)
|
||||
}
|
||||
|
||||
hostConfig.Privileged = taskPrivileged
|
||||
hostConfig.Privileged = driverConfig.Privileged
|
||||
}
|
||||
|
||||
// set DNS servers
|
||||
dns, ok := task.Config["dns-servers"]
|
||||
dns := driverConfig.DNS
|
||||
|
||||
if ok && dns != "" {
|
||||
if dns != "" {
|
||||
for _, v := range strings.Split(dns, ",") {
|
||||
ip := strings.TrimSpace(v)
|
||||
if net.ParseIP(ip) != nil {
|
||||
|
@ -212,16 +241,16 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (do
|
|||
}
|
||||
|
||||
// set DNS search domains
|
||||
dnsSearch, ok := task.Config["search-domains"]
|
||||
dnsSearch := driverConfig.SearchDomains
|
||||
|
||||
if ok && dnsSearch != "" {
|
||||
if dnsSearch != "" {
|
||||
for _, v := range strings.Split(dnsSearch, ",") {
|
||||
hostConfig.DNSSearch = append(hostConfig.DNSSearch, strings.TrimSpace(v))
|
||||
}
|
||||
}
|
||||
|
||||
mode, ok := task.Config["network_mode"]
|
||||
if !ok || mode == "" {
|
||||
mode := driverConfig.NetworkMode
|
||||
if mode == "" {
|
||||
// docker default
|
||||
d.logger.Printf("[WARN] driver.docker: no mode specified for networking, defaulting to bridge")
|
||||
mode = "bridge"
|
||||
|
@ -246,62 +275,55 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (do
|
|||
publishedPorts := map[docker.Port][]docker.PortBinding{}
|
||||
exposedPorts := map[docker.Port]struct{}{}
|
||||
|
||||
for _, port := range network.ListStaticPorts() {
|
||||
publishedPorts[docker.Port(strconv.Itoa(port)+"/tcp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
publishedPorts[docker.Port(strconv.Itoa(port)+"/udp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)\n", network.IP, port, port)
|
||||
exposedPorts[docker.Port(strconv.Itoa(port)+"/tcp")] = struct{}{}
|
||||
exposedPorts[docker.Port(strconv.Itoa(port)+"/udp")] = struct{}{}
|
||||
d.logger.Printf("[DEBUG] driver.docker: exposed port %d\n", port)
|
||||
for _, port := range network.ReservedPorts {
|
||||
publishedPorts[docker.Port(strconv.Itoa(port.Value)+"/tcp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port.Value)}}
|
||||
publishedPorts[docker.Port(strconv.Itoa(port.Value)+"/udp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port.Value)}}
|
||||
d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (static)\n", network.IP, port.Value, port.Value)
|
||||
exposedPorts[docker.Port(strconv.Itoa(port.Value)+"/tcp")] = struct{}{}
|
||||
exposedPorts[docker.Port(strconv.Itoa(port.Value)+"/udp")] = struct{}{}
|
||||
d.logger.Printf("[DEBUG] driver.docker: exposed port %d\n", port.Value)
|
||||
}
|
||||
|
||||
for label, port := range network.MapDynamicPorts() {
|
||||
// If the label is numeric we expect that there is a service
|
||||
// listening on that port inside the container. In this case we'll
|
||||
// setup a mapping from our random host port to the label port.
|
||||
//
|
||||
// Otherwise we'll setup a direct 1:1 mapping from the host port to
|
||||
// the container, and assume that the process inside will read the
|
||||
// environment variable and bind to the correct port.
|
||||
if _, err := strconv.Atoi(label); err == nil {
|
||||
publishedPorts[docker.Port(label+"/tcp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
publishedPorts[docker.Port(label+"/udp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %s (mapped)", network.IP, port, label)
|
||||
exposedPorts[docker.Port(label+"/tcp")] = struct{}{}
|
||||
exposedPorts[docker.Port(label+"/udp")] = struct{}{}
|
||||
d.logger.Printf("[DEBUG] driver.docker: exposed port %d\n", port)
|
||||
} else {
|
||||
publishedPorts[docker.Port(strconv.Itoa(port)+"/tcp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
publishedPorts[docker.Port(strconv.Itoa(port)+"/udp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: strconv.Itoa(port)}}
|
||||
d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d for label %s\n", network.IP, port, port, label)
|
||||
exposedPorts[docker.Port(strconv.Itoa(port)+"/tcp")] = struct{}{}
|
||||
exposedPorts[docker.Port(strconv.Itoa(port)+"/udp")] = struct{}{}
|
||||
d.logger.Printf("[DEBUG] driver.docker: exposed port %d\n", port)
|
||||
containerToHostPortMap := make(map[string]int)
|
||||
for _, port := range network.DynamicPorts {
|
||||
containerPort, ok := driverConfig.PortMap[0][port.Label]
|
||||
if !ok {
|
||||
containerPort = port.Value
|
||||
}
|
||||
cp := strconv.Itoa(containerPort)
|
||||
hostPort := strconv.Itoa(port.Value)
|
||||
publishedPorts[docker.Port(cp+"/tcp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: hostPort}}
|
||||
publishedPorts[docker.Port(cp+"/udp")] = []docker.PortBinding{docker.PortBinding{HostIP: network.IP, HostPort: hostPort}}
|
||||
d.logger.Printf("[DEBUG] driver.docker: allocated port %s:%d -> %d (mapped)", network.IP, port.Value, containerPort)
|
||||
exposedPorts[docker.Port(cp+"/tcp")] = struct{}{}
|
||||
exposedPorts[docker.Port(cp+"/udp")] = struct{}{}
|
||||
d.logger.Printf("[DEBUG] driver.docker: exposed port %s\n", hostPort)
|
||||
containerToHostPortMap[cp] = port.Value
|
||||
}
|
||||
|
||||
env.SetPorts(containerToHostPortMap)
|
||||
hostConfig.PortBindings = publishedPorts
|
||||
config.ExposedPorts = exposedPorts
|
||||
}
|
||||
|
||||
rawArgs, hasArgs := task.Config["args"]
|
||||
parsedArgs, err := args.ParseAndReplace(rawArgs, env.Map())
|
||||
parsedArgs, err := args.ParseAndReplace(driverConfig.Args, env.Map())
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
// If the user specified a custom command to run as their entrypoint, we'll
|
||||
// inject it here.
|
||||
if command, ok := task.Config["command"]; ok {
|
||||
cmd := []string{command}
|
||||
if hasArgs {
|
||||
if driverConfig.Command != "" {
|
||||
cmd := []string{driverConfig.Command}
|
||||
if driverConfig.Args != "" {
|
||||
cmd = append(cmd, parsedArgs...)
|
||||
}
|
||||
config.Cmd = cmd
|
||||
} else if hasArgs {
|
||||
} else if driverConfig.Args != "" {
|
||||
d.logger.Println("[DEBUG] driver.docker: ignoring args because command not specified")
|
||||
}
|
||||
|
||||
config.Env = env.List()
|
||||
return docker.CreateContainerOptions{
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
|
@ -309,10 +331,14 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task) (do
|
|||
}
|
||||
|
||||
func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
// Get the image from config
|
||||
image, ok := task.Config["image"]
|
||||
if !ok || image == "" {
|
||||
return nil, fmt.Errorf("Image not specified")
|
||||
var driverConfig DockerDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
image := driverConfig.ImageName
|
||||
|
||||
if err := driverConfig.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if task.Resources == nil {
|
||||
return nil, fmt.Errorf("Resources are not specified")
|
||||
|
@ -362,10 +388,10 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle
|
|||
}
|
||||
|
||||
authOptions := docker.AuthConfiguration{
|
||||
Username: task.Config["auth.username"],
|
||||
Password: task.Config["auth.password"],
|
||||
Email: task.Config["auth.email"],
|
||||
ServerAddress: task.Config["auth.server-address"],
|
||||
Username: driverConfig.UserName,
|
||||
Password: driverConfig.Password,
|
||||
Email: driverConfig.Email,
|
||||
ServerAddress: driverConfig.ServerAddress,
|
||||
}
|
||||
|
||||
err = client.PullImage(pullOptions, authOptions)
|
||||
|
@ -385,7 +411,7 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle
|
|||
d.logger.Printf("[DEBUG] driver.docker: using image %s", dockerImage.ID)
|
||||
d.logger.Printf("[INFO] driver.docker: identified image %s as %s", image, dockerImage.ID)
|
||||
|
||||
config, err := d.createContainer(ctx, task)
|
||||
config, err := d.createContainer(ctx, task, &driverConfig)
|
||||
if err != nil {
|
||||
d.logger.Printf("[ERR] driver.docker: %s", err)
|
||||
return nil, fmt.Errorf("Failed to create container config for image %s", image)
|
||||
|
|
|
@ -95,7 +95,7 @@ func TestDockerDriver_StartOpen_Wait(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
},
|
||||
Resources: basicResources,
|
||||
|
@ -132,7 +132,7 @@ func TestDockerDriver_Start_Wait(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
"command": "redis-server",
|
||||
"args": "-v",
|
||||
|
@ -185,7 +185,7 @@ func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
|
|||
file := "output.txt"
|
||||
task := &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
"command": "/bin/bash",
|
||||
"args": fmt.Sprintf(`-c "sleep 1; echo -n %s > $%s/%s"`, string(exp), environment.AllocDir, file),
|
||||
|
@ -238,7 +238,7 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
"command": "/bin/sleep",
|
||||
"args": "10",
|
||||
|
@ -281,7 +281,7 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
|
|||
func taskTemplate() *structs.Task {
|
||||
return &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
},
|
||||
Resources: &structs.Resources{
|
||||
|
@ -290,8 +290,8 @@ func taskTemplate() *structs.Task {
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
IP: "127.0.0.1",
|
||||
ReservedPorts: []int{11110},
|
||||
DynamicPorts: []string{"REDIS"},
|
||||
ReservedPorts: []structs.Port{{"main", 11110}},
|
||||
DynamicPorts: []structs.Port{{"REDIS", 0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -304,13 +304,13 @@ func TestDocker_StartN(t *testing.T) {
|
|||
}
|
||||
|
||||
task1 := taskTemplate()
|
||||
task1.Resources.Networks[0].ReservedPorts[0] = 11111
|
||||
task1.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 11110}
|
||||
|
||||
task2 := taskTemplate()
|
||||
task2.Resources.Networks[0].ReservedPorts[0] = 22222
|
||||
task2.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 22222}
|
||||
|
||||
task3 := taskTemplate()
|
||||
task3.Resources.Networks[0].ReservedPorts[0] = 33333
|
||||
task3.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 33333}
|
||||
|
||||
taskList := []*structs.Task{task1, task2, task3}
|
||||
|
||||
|
@ -356,15 +356,15 @@ func TestDocker_StartNVersions(t *testing.T) {
|
|||
|
||||
task1 := taskTemplate()
|
||||
task1.Config["image"] = "redis"
|
||||
task1.Resources.Networks[0].ReservedPorts[0] = 11111
|
||||
task1.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 11110}
|
||||
|
||||
task2 := taskTemplate()
|
||||
task2.Config["image"] = "redis:latest"
|
||||
task2.Resources.Networks[0].ReservedPorts[0] = 22222
|
||||
task2.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 22222}
|
||||
|
||||
task3 := taskTemplate()
|
||||
task3.Config["image"] = "redis:3.0"
|
||||
task3.Resources.Networks[0].ReservedPorts[0] = 33333
|
||||
task3.Resources.Networks[0].ReservedPorts[0] = structs.Port{"main", 33333}
|
||||
|
||||
taskList := []*structs.Task{task1, task2, task3}
|
||||
|
||||
|
@ -410,7 +410,7 @@ func TestDockerHostNet(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "redis-demo",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "redis",
|
||||
"network_mode": "host",
|
||||
},
|
||||
|
|
|
@ -135,7 +135,7 @@ func TaskEnvironmentVariables(ctx *ExecContext, task *structs.Task) environment.
|
|||
if len(task.Resources.Networks) > 0 {
|
||||
network := task.Resources.Networks[0]
|
||||
env.SetTaskIp(network.IP)
|
||||
env.SetPorts(network.MapDynamicPorts())
|
||||
env.SetPorts(network.MapLabelToValues())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ var basicResources = &structs.Resources{
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
IP: "0.0.0.0",
|
||||
ReservedPorts: []int{12345},
|
||||
DynamicPorts: []string{"HTTP"},
|
||||
ReservedPorts: []structs.Port{{"main", 12345}},
|
||||
DynamicPorts: []structs.Port{{"HTTP", 0}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -60,8 +60,8 @@ func TestDriver_TaskEnvironmentVariables(t *testing.T) {
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
IP: "1.2.3.4",
|
||||
ReservedPorts: []int{80, 443, 8080, 12345},
|
||||
DynamicPorts: []string{"admin", "5000"},
|
||||
ReservedPorts: []structs.Port{{"one", 80}, {"two", 443}, {"three", 8080}, {"four", 12345}},
|
||||
DynamicPorts: []structs.Port{{"admin", 8081}, {"web", 8086}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -76,8 +76,12 @@ func TestDriver_TaskEnvironmentVariables(t *testing.T) {
|
|||
"NOMAD_CPU_LIMIT": "1000",
|
||||
"NOMAD_MEMORY_LIMIT": "500",
|
||||
"NOMAD_IP": "1.2.3.4",
|
||||
"NOMAD_PORT_admin": "8080",
|
||||
"NOMAD_PORT_5000": "12345",
|
||||
"NOMAD_PORT_one": "80",
|
||||
"NOMAD_PORT_two": "443",
|
||||
"NOMAD_PORT_three": "8080",
|
||||
"NOMAD_PORT_four": "12345",
|
||||
"NOMAD_PORT_admin": "8081",
|
||||
"NOMAD_PORT_web": "8086",
|
||||
"NOMAD_META_CHOCOLATE": "cake",
|
||||
"NOMAD_META_STRAWBERRY": "icecream",
|
||||
"HELLO": "world",
|
||||
|
|
|
@ -10,11 +10,11 @@ import (
|
|||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/client/getter"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// ExecDriver fork/execs tasks using as many of the underlying OS's isolation
|
||||
|
@ -23,6 +23,12 @@ type ExecDriver struct {
|
|||
DriverContext
|
||||
fingerprint.StaticFingerprinter
|
||||
}
|
||||
type ExecDriverConfig struct {
|
||||
ArtifactSource string `mapstructure:"artifact_source"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
Command string `mapstructure:"command"`
|
||||
Args string `mapstructure:"args"`
|
||||
}
|
||||
|
||||
// execHandle is returned from Start/Open as a handle to the PID
|
||||
type execHandle struct {
|
||||
|
@ -51,9 +57,13 @@ func (d *ExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool,
|
|||
}
|
||||
|
||||
func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
var driverConfig ExecDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get the command to be ran
|
||||
command, ok := task.Config["command"]
|
||||
if !ok || command == "" {
|
||||
command := driverConfig.Command
|
||||
if command == "" {
|
||||
return nil, fmt.Errorf("missing command for exec driver")
|
||||
}
|
||||
|
||||
|
@ -69,8 +79,8 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
// Proceed to download an artifact to be executed.
|
||||
_, err := getter.GetArtifact(
|
||||
filepath.Join(taskDir, allocdir.TaskLocal),
|
||||
task.Config["artifact_source"],
|
||||
task.Config["checksum"],
|
||||
driverConfig.ArtifactSource,
|
||||
driverConfig.Checksum,
|
||||
d.logger,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -83,8 +93,8 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
|
||||
// Look for arguments
|
||||
var args []string
|
||||
if argRaw, ok := task.Config["args"]; ok {
|
||||
args = append(args, argRaw)
|
||||
if driverConfig.Args != "" {
|
||||
args = append(args, driverConfig.Args)
|
||||
}
|
||||
|
||||
// Setup the command
|
||||
|
|
|
@ -37,7 +37,7 @@ func TestExecDriver_StartOpen_Wait(t *testing.T) {
|
|||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "5",
|
||||
},
|
||||
|
@ -71,7 +71,7 @@ func TestExecDriver_Start_Wait(t *testing.T) {
|
|||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "2",
|
||||
},
|
||||
|
@ -115,7 +115,7 @@ func TestExecDriver_Start_Artifact_basic(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s?checksum=%s", file, checksum),
|
||||
"command": filepath.Join("$NOMAD_TASK_DIR", file),
|
||||
},
|
||||
|
@ -158,7 +158,7 @@ func TestExecDriver_Start_Artifact_expanded(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file),
|
||||
"command": "/bin/bash",
|
||||
"args": fmt.Sprintf("-c '/bin/sleep 1 && %s'", filepath.Join("$NOMAD_TASK_DIR", file)),
|
||||
|
@ -202,7 +202,7 @@ func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
|
|||
file := "output.txt"
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/bash",
|
||||
"args": fmt.Sprintf("-c \"sleep 1; echo -n %s > $%s/%s\"", string(exp), environment.AllocDir, file),
|
||||
},
|
||||
|
@ -248,7 +248,7 @@ func TestExecDriver_Start_Kill_Wait(t *testing.T) {
|
|||
ctestutils.ExecCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "1",
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ var (
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: []string{"http"},
|
||||
DynamicPorts: []structs.Port{{Label: "http"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ import (
|
|||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/client/getter"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// JavaDriver is a simple driver to execute applications packaged in Jars.
|
||||
|
@ -27,6 +27,13 @@ type JavaDriver struct {
|
|||
fingerprint.StaticFingerprinter
|
||||
}
|
||||
|
||||
type JavaDriverConfig struct {
|
||||
JvmOpts string `mapstructure:"jvm_options"`
|
||||
ArtifactSource string `mapstructure:"artifact_source"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
Args string `mapstructure:"args"`
|
||||
}
|
||||
|
||||
// javaHandle is returned from Start/Open as a handle to the PID
|
||||
type javaHandle struct {
|
||||
cmd executor.Executor
|
||||
|
@ -92,6 +99,10 @@ func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool,
|
|||
}
|
||||
|
||||
func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
var driverConfig JavaDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
taskDir, ok := ctx.AllocDir.TaskDirs[d.DriverContext.taskName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Could not find task directory for task: %v", d.DriverContext.taskName)
|
||||
|
@ -100,8 +111,8 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
// Proceed to download an artifact to be executed.
|
||||
path, err := getter.GetArtifact(
|
||||
filepath.Join(taskDir, allocdir.TaskLocal),
|
||||
task.Config["artifact_source"],
|
||||
task.Config["checksum"],
|
||||
driverConfig.ArtifactSource,
|
||||
driverConfig.Checksum,
|
||||
d.logger,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -115,16 +126,15 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
|
||||
args := []string{}
|
||||
// Look for jvm options
|
||||
jvm_options, ok := task.Config["jvm_options"]
|
||||
if ok && jvm_options != "" {
|
||||
d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", jvm_options)
|
||||
args = append(args, jvm_options)
|
||||
if driverConfig.JvmOpts != "" {
|
||||
d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
|
||||
args = append(args, driverConfig.JvmOpts)
|
||||
}
|
||||
|
||||
// Build the argument list.
|
||||
args = append(args, "-jar", filepath.Join(allocdir.TaskLocal, jarName))
|
||||
if argRaw, ok := task.Config["args"]; ok {
|
||||
args = append(args, argRaw)
|
||||
if driverConfig.Args != "" {
|
||||
args = append(args, driverConfig.Args)
|
||||
}
|
||||
|
||||
// Setup the command
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestJavaDriver_StartOpen_Wait(t *testing.T) {
|
|||
ctestutils.JavaCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "demo-app",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar",
|
||||
"jvm_options": "-Xmx2048m -Xms256m",
|
||||
"checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8",
|
||||
|
@ -95,7 +95,7 @@ func TestJavaDriver_Start_Wait(t *testing.T) {
|
|||
ctestutils.JavaCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "demo-app",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar",
|
||||
"jvm_options": "-Xmx2048m -Xms256m",
|
||||
"checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8",
|
||||
|
@ -142,7 +142,7 @@ func TestJavaDriver_Start_Kill_Wait(t *testing.T) {
|
|||
ctestutils.JavaCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "demo-app",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar",
|
||||
},
|
||||
Resources: basicResources,
|
||||
|
|
|
@ -13,11 +13,11 @@ import (
|
|||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/client/getter"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -32,6 +32,13 @@ type QemuDriver struct {
|
|||
fingerprint.StaticFingerprinter
|
||||
}
|
||||
|
||||
type QemuDriverConfig struct {
|
||||
ArtifactSource string `mapstructure:"artifact_source"`
|
||||
Checksum string `mapstructure:"checksum"`
|
||||
Accelerator string `mapstructure:"accelerator"`
|
||||
GuestPorts string `mapstructure:"guest_ports"`
|
||||
}
|
||||
|
||||
// qemuHandle is returned from Start/Open as a handle to the PID
|
||||
type qemuHandle struct {
|
||||
cmd executor.Executor
|
||||
|
@ -71,6 +78,10 @@ func (d *QemuDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool,
|
|||
// Run an existing Qemu image. Start() will pull down an existing, valid Qemu
|
||||
// image and save it to the Drivers Allocation Dir
|
||||
func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
var driverConfig QemuDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get the image source
|
||||
source, ok := task.Config["artifact_source"]
|
||||
if !ok || source == "" {
|
||||
|
@ -92,8 +103,8 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
// Proceed to download an artifact to be executed.
|
||||
vmPath, err := getter.GetArtifact(
|
||||
filepath.Join(taskDir, allocdir.TaskLocal),
|
||||
task.Config["artifact_source"],
|
||||
task.Config["checksum"],
|
||||
driverConfig.ArtifactSource,
|
||||
driverConfig.Checksum,
|
||||
d.logger,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -105,8 +116,8 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
// Parse configuration arguments
|
||||
// Create the base arguments
|
||||
accelerator := "tcg"
|
||||
if acc, ok := task.Config["accelerator"]; ok {
|
||||
accelerator = acc
|
||||
if driverConfig.Accelerator != "" {
|
||||
accelerator = driverConfig.Accelerator
|
||||
}
|
||||
// TODO: Check a lower bounds, e.g. the default 128 of Qemu
|
||||
mem := fmt.Sprintf("%dM", task.Resources.MemoryMB)
|
||||
|
@ -134,7 +145,7 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
// the Reserved ports in the Task Resources
|
||||
// Users can supply guest_hosts as a list of posts to map on the guest vm.
|
||||
// These map 1:1 with the requested Reserved Ports from the hostmachine.
|
||||
ports := strings.Split(task.Config["guest_ports"], ",")
|
||||
ports := strings.Split(driverConfig.GuestPorts, ",")
|
||||
if len(ports) == 0 {
|
||||
return nil, fmt.Errorf("[ERR] driver.qemu: Error parsing required Guest Ports")
|
||||
}
|
||||
|
@ -151,7 +162,7 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
|
|||
reservedPorts := task.Resources.Networks[0].ReservedPorts
|
||||
var forwarding string
|
||||
for i, p := range ports {
|
||||
forwarding = fmt.Sprintf("%s,hostfwd=tcp::%s-:%s", forwarding, strconv.Itoa(reservedPorts[i]), p)
|
||||
forwarding = fmt.Sprintf("%s,hostfwd=tcp::%s-:%s", forwarding, strconv.Itoa(reservedPorts[i].Value), p)
|
||||
}
|
||||
|
||||
if "" == forwarding {
|
||||
|
|
|
@ -37,7 +37,7 @@ func TestQemuDriver_StartOpen_Wait(t *testing.T) {
|
|||
// TODO: use test server to load from a fixture
|
||||
task := &structs.Task{
|
||||
Name: "linux",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/linux-0.2.img",
|
||||
"checksum": "sha256:a5e836985934c3392cbbd9b26db55a7d35a8d7ae1deb7ca559dd9c0159572544",
|
||||
"accelerator": "tcg",
|
||||
|
@ -48,7 +48,7 @@ func TestQemuDriver_StartOpen_Wait(t *testing.T) {
|
|||
MemoryMB: 512,
|
||||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
ReservedPorts: []int{22000, 80},
|
||||
ReservedPorts: []structs.Port{{"main", 22000}, {"web", 80}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -87,7 +87,7 @@ func TestQemuDriver_RequiresMemory(t *testing.T) {
|
|||
// TODO: use test server to load from a fixture
|
||||
task := &structs.Task{
|
||||
Name: "linux",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/linux-0.2.img",
|
||||
"accelerator": "tcg",
|
||||
"host_port": "8080",
|
||||
|
|
|
@ -9,11 +9,11 @@ import (
|
|||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/executor"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/client/getter"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -58,6 +58,10 @@ func (d *RawExecDriver) Fingerprint(cfg *config.Config, node *structs.Node) (boo
|
|||
}
|
||||
|
||||
func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
var driverConfig ExecDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get the tasks local directory.
|
||||
taskName := d.DriverContext.taskName
|
||||
taskDir, ok := ctx.AllocDir.TaskDirs[taskName]
|
||||
|
@ -66,8 +70,8 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl
|
|||
}
|
||||
|
||||
// Get the command to be ran
|
||||
command, ok := task.Config["command"]
|
||||
if !ok || command == "" {
|
||||
command := driverConfig.Command
|
||||
if command == "" {
|
||||
return nil, fmt.Errorf("missing command for Raw Exec driver")
|
||||
}
|
||||
|
||||
|
@ -77,8 +81,8 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl
|
|||
// Proceed to download an artifact to be executed.
|
||||
_, err := getter.GetArtifact(
|
||||
filepath.Join(taskDir, allocdir.TaskLocal),
|
||||
task.Config["artifact_source"],
|
||||
task.Config["checksum"],
|
||||
driverConfig.ArtifactSource,
|
||||
driverConfig.Checksum,
|
||||
d.logger,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -91,8 +95,8 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl
|
|||
|
||||
// Look for arguments
|
||||
var args []string
|
||||
if argRaw, ok := task.Config["args"]; ok {
|
||||
args = append(args, argRaw)
|
||||
if driverConfig.Args != "" {
|
||||
args = append(args, driverConfig.Args)
|
||||
}
|
||||
|
||||
// Setup the command
|
||||
|
|
|
@ -51,7 +51,7 @@ func TestRawExecDriver_Fingerprint(t *testing.T) {
|
|||
func TestRawExecDriver_StartOpen_Wait(t *testing.T) {
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "1",
|
||||
},
|
||||
|
@ -100,7 +100,7 @@ func TestRawExecDriver_Start_Artifact_basic(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file),
|
||||
"command": filepath.Join("$NOMAD_TASK_DIR", file),
|
||||
"checksum": checksum,
|
||||
|
@ -148,7 +148,7 @@ func TestRawExecDriver_Start_Artifact_expanded(t *testing.T) {
|
|||
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file),
|
||||
"command": "/bin/bash",
|
||||
"args": fmt.Sprintf("-c '/bin/sleep 1 && %s'", filepath.Join("$NOMAD_TASK_DIR", file)),
|
||||
|
@ -188,7 +188,7 @@ func TestRawExecDriver_Start_Artifact_expanded(t *testing.T) {
|
|||
func TestRawExecDriver_Start_Wait(t *testing.T) {
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "1",
|
||||
},
|
||||
|
@ -230,7 +230,7 @@ func TestRawExecDriver_Start_Wait_AllocDir(t *testing.T) {
|
|||
file := "output.txt"
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/bash",
|
||||
"args": fmt.Sprintf(`-c "sleep 1; echo -n %s > $%s/%s"`, string(exp), environment.AllocDir, file),
|
||||
},
|
||||
|
@ -275,7 +275,7 @@ func TestRawExecDriver_Start_Wait_AllocDir(t *testing.T) {
|
|||
func TestRawExecDriver_Start_Kill_Wait(t *testing.T) {
|
||||
task := &structs.Task{
|
||||
Name: "sleep",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/sleep",
|
||||
"args": "1",
|
||||
},
|
||||
|
|
|
@ -17,10 +17,10 @@ import (
|
|||
"github.com/hashicorp/nomad/client/allocdir"
|
||||
"github.com/hashicorp/nomad/client/config"
|
||||
"github.com/hashicorp/nomad/client/driver/args"
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/hashicorp/nomad/client/fingerprint"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
||||
cstructs "github.com/hashicorp/nomad/client/driver/structs"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -36,6 +36,11 @@ type RktDriver struct {
|
|||
fingerprint.StaticFingerprinter
|
||||
}
|
||||
|
||||
type RktDriverConfig struct {
|
||||
ImageName string `mapstructure:"image"`
|
||||
Args string `mapstructure:"args"`
|
||||
}
|
||||
|
||||
// rktHandle is returned from Start/Open as a handle to the PID
|
||||
type rktHandle struct {
|
||||
proc *os.Process
|
||||
|
@ -85,9 +90,13 @@ func (d *RktDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, e
|
|||
|
||||
// Run an existing Rkt image.
|
||||
func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, error) {
|
||||
var driverConfig RktDriverConfig
|
||||
if err := mapstructure.WeakDecode(task.Config, &driverConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Validate that the config is valid.
|
||||
img, ok := task.Config["image"]
|
||||
if !ok || img == "" {
|
||||
img := driverConfig.ImageName
|
||||
if img == "" {
|
||||
return nil, fmt.Errorf("Missing ACI image for rkt")
|
||||
}
|
||||
|
||||
|
@ -141,8 +150,8 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e
|
|||
}
|
||||
|
||||
// Add user passed arguments.
|
||||
if userArgs, ok := task.Config["args"]; ok {
|
||||
parsed, err := args.ParseAndReplace(userArgs, envVars.Map())
|
||||
if driverConfig.Args != "" {
|
||||
parsed, err := args.ParseAndReplace(driverConfig.Args, envVars.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ func TestRktDriver_Start(t *testing.T) {
|
|||
// TODO: use test server to load from a fixture
|
||||
task := &structs.Task{
|
||||
Name: "etcd",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"trust_prefix": "coreos.com/etcd",
|
||||
"image": "coreos.com/etcd:v2.0.4",
|
||||
"command": "/etcd",
|
||||
|
@ -115,7 +115,7 @@ func TestRktDriver_Start_Wait(t *testing.T) {
|
|||
ctestutils.RktCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "etcd",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"trust_prefix": "coreos.com/etcd",
|
||||
"image": "coreos.com/etcd:v2.0.4",
|
||||
"command": "/etcd",
|
||||
|
@ -157,7 +157,7 @@ func TestRktDriver_Start_Wait_Skip_Trust(t *testing.T) {
|
|||
ctestutils.RktCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "etcd",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "coreos.com/etcd:v2.0.4",
|
||||
"command": "/etcd",
|
||||
"args": "--version",
|
||||
|
@ -198,7 +198,7 @@ func TestRktDriver_Start_Wait_Logs(t *testing.T) {
|
|||
ctestutils.RktCompatible(t)
|
||||
task := &structs.Task{
|
||||
Name: "etcd",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"trust_prefix": "coreos.com/etcd",
|
||||
"image": "coreos.com/etcd:v2.0.4",
|
||||
"command": "/etcd",
|
||||
|
|
|
@ -35,7 +35,7 @@ func testTaskRunner(restarts bool) (*MockTaskStateUpdater, *TaskRunner) {
|
|||
|
||||
// Initialize the port listing. This should be done by the offer process but
|
||||
// we have a mock so that doesn't happen.
|
||||
task.Resources.Networks[0].ReservedPorts = []int{80}
|
||||
task.Resources.Networks[0].ReservedPorts = []structs.Port{{"", 80}}
|
||||
|
||||
allocDir := allocdir.NewAllocDir(filepath.Join(conf.AllocDir, alloc.ID))
|
||||
allocDir.Build([]*structs.Task{task})
|
||||
|
|
|
@ -2,7 +2,11 @@ package command
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/api"
|
||||
)
|
||||
|
||||
type AllocStatusCommand struct {
|
||||
|
@ -13,14 +17,21 @@ func (c *AllocStatusCommand) Help() string {
|
|||
helpText := `
|
||||
Usage: nomad alloc-status [options] <allocation>
|
||||
|
||||
Display information about existing allocations. This command can
|
||||
be used to inspect the current status of all allocation,
|
||||
including its running status, metadata, and verbose failure
|
||||
messages reported by internal subsystems.
|
||||
Display information about existing allocations and its tasks. This command can
|
||||
be used to inspect the current status of all allocation, including its running
|
||||
status, metadata, and verbose failure messages reported by internal
|
||||
subsystems.
|
||||
|
||||
General Options:
|
||||
|
||||
` + generalOptionsUsage()
|
||||
` + generalOptionsUsage() + `
|
||||
|
||||
Alloc Status Options:
|
||||
|
||||
-short
|
||||
Display short output. Shows only the most recent task event.
|
||||
`
|
||||
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
|
@ -29,8 +40,12 @@ func (c *AllocStatusCommand) Synopsis() string {
|
|||
}
|
||||
|
||||
func (c *AllocStatusCommand) Run(args []string) int {
|
||||
var short bool
|
||||
|
||||
flags := c.Meta.FlagSet("alloc-status", FlagSetClient)
|
||||
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
||||
flags.BoolVar(&short, "short", false, "")
|
||||
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
|
@ -65,7 +80,6 @@ func (c *AllocStatusCommand) Run(args []string) int {
|
|||
fmt.Sprintf("NodeID|%s", alloc.NodeID),
|
||||
fmt.Sprintf("JobID|%s", alloc.JobID),
|
||||
fmt.Sprintf("ClientStatus|%s", alloc.ClientStatus),
|
||||
fmt.Sprintf("ClientDescription|%s", alloc.ClientDescription),
|
||||
fmt.Sprintf("NodesEvaluated|%d", alloc.Metrics.NodesEvaluated),
|
||||
fmt.Sprintf("NodesFiltered|%d", alloc.Metrics.NodesFiltered),
|
||||
fmt.Sprintf("NodesExhausted|%d", alloc.Metrics.NodesExhausted),
|
||||
|
@ -74,9 +88,108 @@ func (c *AllocStatusCommand) Run(args []string) int {
|
|||
}
|
||||
c.Ui.Output(formatKV(basic))
|
||||
|
||||
// Print the state of each task.
|
||||
if short {
|
||||
c.shortTaskStatus(alloc)
|
||||
} else {
|
||||
c.taskStatus(alloc)
|
||||
}
|
||||
|
||||
// Format the detailed status
|
||||
c.Ui.Output("\n==> Status")
|
||||
dumpAllocStatus(c.Ui, alloc)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// shortTaskStatus prints out the current state of each task.
|
||||
func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) {
|
||||
tasks := make([]string, 0, len(alloc.TaskStates)+1)
|
||||
tasks = append(tasks, "Name|State|LastEvent|Time")
|
||||
for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
|
||||
fmt.Println(task)
|
||||
state := alloc.TaskStates[task]
|
||||
lastState := state.State
|
||||
var lastEvent, lastTime string
|
||||
|
||||
l := len(state.Events)
|
||||
if l != 0 {
|
||||
last := state.Events[l-1]
|
||||
lastEvent = last.Type
|
||||
lastTime = c.formatUnixNanoTime(last.Time)
|
||||
}
|
||||
|
||||
tasks = append(tasks, fmt.Sprintf("%s|%s|%s|%s",
|
||||
task, lastState, lastEvent, lastTime))
|
||||
}
|
||||
|
||||
c.Ui.Output("\n==> Tasks")
|
||||
c.Ui.Output(formatList(tasks))
|
||||
}
|
||||
|
||||
// taskStatus prints out the most recent events for each task.
|
||||
func (c *AllocStatusCommand) taskStatus(alloc *api.Allocation) {
|
||||
for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
|
||||
state := alloc.TaskStates[task]
|
||||
events := make([]string, len(state.Events)+1)
|
||||
events[0] = "Time|Type|Description"
|
||||
|
||||
size := len(state.Events)
|
||||
for i, event := range state.Events {
|
||||
formatedTime := c.formatUnixNanoTime(event.Time)
|
||||
|
||||
// Build up the description based on the event type.
|
||||
var desc string
|
||||
switch event.Type {
|
||||
case api.TaskDriverFailure:
|
||||
desc = event.DriverError
|
||||
case api.TaskKilled:
|
||||
desc = event.KillError
|
||||
case api.TaskTerminated:
|
||||
var parts []string
|
||||
parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode))
|
||||
|
||||
if event.Signal != 0 {
|
||||
parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal))
|
||||
}
|
||||
|
||||
if event.Message != "" {
|
||||
parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message))
|
||||
}
|
||||
desc = strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
// Reverse order so we are sorted by time
|
||||
events[size-i] = fmt.Sprintf("%s|%s|%s", formatedTime, event.Type, desc)
|
||||
}
|
||||
|
||||
c.Ui.Output(fmt.Sprintf("\n==> Task %q is %q\nRecent Events:", task, state.State))
|
||||
c.Ui.Output(formatList(events))
|
||||
}
|
||||
}
|
||||
|
||||
// formatUnixNanoTime is a helper for formating time for output.
|
||||
func (c *AllocStatusCommand) formatUnixNanoTime(nano int64) string {
|
||||
t := time.Unix(0, nano)
|
||||
return t.Format("15:04:05 01/02/06")
|
||||
}
|
||||
|
||||
// sortedTaskStateIterator is a helper that takes the task state map and returns a
|
||||
// channel that returns the keys in a sorted order.
|
||||
func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState) <-chan string {
|
||||
output := make(chan string, len(m))
|
||||
keys := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
output <- key
|
||||
}
|
||||
|
||||
close(output)
|
||||
return output
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ job "example" {
|
|||
# Configure Docker driver with the image
|
||||
config {
|
||||
image = "redis:latest"
|
||||
port_map {
|
||||
db = 6379
|
||||
}
|
||||
}
|
||||
|
||||
# We must specify the resources required for
|
||||
|
@ -133,7 +136,8 @@ job "example" {
|
|||
memory = 256 # 256MB
|
||||
network {
|
||||
mbits = 10
|
||||
dynamic_ports = ["6379"]
|
||||
port "db" {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,6 +123,7 @@ func (c *RunCommand) Run(args []string) int {
|
|||
// convertJob is used to take a *structs.Job and convert it to an *api.Job.
|
||||
// This function is just a hammer and probably needs to be revisited.
|
||||
func convertJob(in *structs.Job) (*api.Job, error) {
|
||||
gob.Register([]map[string]interface{}{})
|
||||
var apiJob *api.Job
|
||||
buf := new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(buf).Encode(in); err != nil {
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
var reDynamicPorts *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||
var errDynamicPorts = fmt.Errorf("DynamicPort label does not conform to naming requirements %s", reDynamicPorts.String())
|
||||
var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String())
|
||||
|
||||
// Parse parses the job spec from the given io.Reader.
|
||||
//
|
||||
|
@ -408,6 +408,7 @@ func parseTasks(result *[]*structs.Task, list *ast.ObjectList) error {
|
|||
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -496,21 +497,14 @@ func parseResources(result *structs.Resources, list *ast.ObjectList) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Keep track of labels we've already seen so we can ensure there
|
||||
// are no collisions when we turn them into environment variables.
|
||||
// lowercase:NomalCase so we can get the first for the error message
|
||||
seenLabel := map[string]string{}
|
||||
for _, label := range r.DynamicPorts {
|
||||
if !reDynamicPorts.MatchString(label) {
|
||||
return errDynamicPorts
|
||||
}
|
||||
first, seen := seenLabel[strings.ToLower(label)]
|
||||
if seen {
|
||||
return fmt.Errorf("Found a port label collision: `%s` overlaps with previous `%s`", label, first)
|
||||
var networkObj *ast.ObjectList
|
||||
if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
|
||||
networkObj = ot.List
|
||||
} else {
|
||||
seenLabel[strings.ToLower(label)] = label
|
||||
return fmt.Errorf("resource: should be an object")
|
||||
}
|
||||
|
||||
if err := parsePorts(networkObj, &r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result.Networks = []*structs.NetworkResource{&r}
|
||||
|
@ -519,6 +513,37 @@ func parseResources(result *structs.Resources, list *ast.ObjectList) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func parsePorts(networkObj *ast.ObjectList, nw *structs.NetworkResource) error {
|
||||
portsObjList := networkObj.Filter("port")
|
||||
knownPortLabels := make(map[string]bool)
|
||||
for _, port := range portsObjList.Items {
|
||||
label := port.Keys[0].Token.Value().(string)
|
||||
if !reDynamicPorts.MatchString(label) {
|
||||
return errPortLabel
|
||||
}
|
||||
l := strings.ToLower(label)
|
||||
if knownPortLabels[l] {
|
||||
return fmt.Errorf("Found a port label collision: %s", label)
|
||||
}
|
||||
var p map[string]interface{}
|
||||
var res structs.Port
|
||||
if err := hcl.DecodeObject(&p, port.Val); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := mapstructure.WeakDecode(p, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
res.Label = label
|
||||
if res.Value > 0 {
|
||||
nw.ReservedPorts = append(nw.ReservedPorts, res)
|
||||
} else {
|
||||
nw.DynamicPorts = append(nw.DynamicPorts, res)
|
||||
}
|
||||
knownPortLabels[l] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseUpdate(result *structs.UpdateStrategy, list *ast.ObjectList) error {
|
||||
list = list.Elem()
|
||||
if len(list.Items) > 1 {
|
||||
|
|
|
@ -57,7 +57,7 @@ func TestParse(t *testing.T) {
|
|||
&structs.Task{
|
||||
Name: "outside",
|
||||
Driver: "java",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"jar": "s3://my-cool-store/foo.jar",
|
||||
},
|
||||
Meta: map[string]string{
|
||||
|
@ -91,7 +91,7 @@ func TestParse(t *testing.T) {
|
|||
&structs.Task{
|
||||
Name: "binstore",
|
||||
Driver: "docker",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "hashicorp/binstore",
|
||||
},
|
||||
Env: map[string]string{
|
||||
|
@ -104,8 +104,8 @@ func TestParse(t *testing.T) {
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{1, 2, 3},
|
||||
DynamicPorts: []string{"http", "https", "admin"},
|
||||
ReservedPorts: []structs.Port{{"one", 1}, {"two", 2}, {"three", 3}},
|
||||
DynamicPorts: []structs.Port{{"http", 0}, {"https", 0}, {"admin", 0}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -113,7 +113,7 @@ func TestParse(t *testing.T) {
|
|||
&structs.Task{
|
||||
Name: "storagelocker",
|
||||
Driver: "java",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"image": "hashicorp/storagelocker",
|
||||
},
|
||||
Resources: &structs.Resources{
|
||||
|
@ -225,6 +225,43 @@ func TestParse(t *testing.T) {
|
|||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"task-nested-config.hcl",
|
||||
&structs.Job{
|
||||
Region: "global",
|
||||
ID: "foo",
|
||||
Name: "foo",
|
||||
Type: "service",
|
||||
Priority: 50,
|
||||
|
||||
TaskGroups: []*structs.TaskGroup{
|
||||
&structs.TaskGroup{
|
||||
Name: "bar",
|
||||
Count: 1,
|
||||
RestartPolicy: &structs.RestartPolicy{
|
||||
Attempts: 2,
|
||||
Interval: 1 * time.Minute,
|
||||
Delay: 15 * time.Second,
|
||||
},
|
||||
Tasks: []*structs.Task{
|
||||
&structs.Task{
|
||||
Name: "bar",
|
||||
Driver: "docker",
|
||||
Config: map[string]interface{}{
|
||||
"port_map": []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"db": 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -256,8 +293,8 @@ func TestBadPorts(t *testing.T) {
|
|||
|
||||
_, err = ParseFile(path)
|
||||
|
||||
if !strings.Contains(err.Error(), errDynamicPorts.Error()) {
|
||||
t.Fatalf("\nExpected error\n %s\ngot\n %v", errDynamicPorts, err)
|
||||
if !strings.Contains(err.Error(), errPortLabel.Error()) {
|
||||
t.Fatalf("\nExpected error\n %s\ngot\n %v", errPortLabel, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,8 +42,17 @@ job "binstore-storagelocker" {
|
|||
|
||||
network {
|
||||
mbits = "100"
|
||||
reserved_ports = [1,2,3]
|
||||
dynamic_ports = ["this_is_aport", "this#is$not-a!port"]
|
||||
port "one" {
|
||||
static = 1
|
||||
}
|
||||
port "two" {
|
||||
static = 2
|
||||
}
|
||||
port "three" {
|
||||
static = 3
|
||||
}
|
||||
port "this_is_aport" {}
|
||||
port ""{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,8 +51,18 @@ job "binstore-storagelocker" {
|
|||
|
||||
network {
|
||||
mbits = "100"
|
||||
reserved_ports = [1,2,3]
|
||||
dynamic_ports = ["http", "https", "admin"]
|
||||
port "one" {
|
||||
static = 1
|
||||
}
|
||||
port "two" {
|
||||
static = 2
|
||||
}
|
||||
port "three" {
|
||||
static = 3
|
||||
}
|
||||
port "http" {}
|
||||
port "https" {}
|
||||
port "admin" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,8 +42,18 @@ job "binstore-storagelocker" {
|
|||
|
||||
network {
|
||||
mbits = "100"
|
||||
reserved_ports = [1,2,3]
|
||||
dynamic_ports = ["Http", "http", "HTTP"]
|
||||
port "one" {
|
||||
static = 1
|
||||
}
|
||||
port "two" {
|
||||
static = 2
|
||||
}
|
||||
port "three" {
|
||||
static = 3
|
||||
}
|
||||
port "Http" {}
|
||||
port "http" {}
|
||||
port "HTTP" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
jobspec/test-fixtures/task-nested-config.hcl
Normal file
10
jobspec/test-fixtures/task-nested-config.hcl
Normal file
|
@ -0,0 +1,10 @@
|
|||
job "foo" {
|
||||
task "bar" {
|
||||
driver = "docker"
|
||||
config {
|
||||
port_map {
|
||||
db = 1234
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ func Node() *structs.Node {
|
|||
&structs.NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
ReservedPorts: []structs.Port{{Label: "main", Value: 22}},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ func Job() *structs.Job {
|
|||
&structs.Task{
|
||||
Name: "web",
|
||||
Driver: "exec",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/date",
|
||||
"args": "+%s",
|
||||
},
|
||||
|
@ -97,7 +97,7 @@ func Job() *structs.Job {
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: []string{"http"},
|
||||
DynamicPorts: []structs.Port{{Label: "http"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -149,7 +149,7 @@ func SystemJob() *structs.Job {
|
|||
&structs.Task{
|
||||
Name: "web",
|
||||
Driver: "exec",
|
||||
Config: map[string]string{
|
||||
Config: map[string]interface{}{
|
||||
"command": "/bin/date",
|
||||
"args": "+%s",
|
||||
},
|
||||
|
@ -159,7 +159,7 @@ func SystemJob() *structs.Job {
|
|||
Networks: []*structs.NetworkResource{
|
||||
&structs.NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: []string{"http"},
|
||||
DynamicPorts: []structs.Port{{Label: "http"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -201,9 +201,9 @@ func Alloc() *structs.Allocation {
|
|||
&structs.NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{12345},
|
||||
ReservedPorts: []structs.Port{{Label: "main", Value: 12345}},
|
||||
MBits: 100,
|
||||
DynamicPorts: []string{"http"},
|
||||
DynamicPorts: []structs.Port{{Label: "http"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -215,9 +215,9 @@ func Alloc() *structs.Allocation {
|
|||
&structs.NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{5000},
|
||||
ReservedPorts: []structs.Port{{Label: "main", Value: 5000}},
|
||||
MBits: 50,
|
||||
DynamicPorts: []string{"http"},
|
||||
DynamicPorts: []structs.Port{{Label: "http"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestAllocsFit_PortsOvercommitted(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{8000},
|
||||
ReservedPorts: []Port{{"main", 8000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -111,7 +111,7 @@ func TestAllocsFit(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{80},
|
||||
ReservedPorts: []Port{{"main", 80}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -128,7 +128,7 @@ func TestAllocsFit(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{8000},
|
||||
ReservedPorts: []Port{{"main", 8000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -96,10 +96,10 @@ func (idx *NetworkIndex) AddReserved(n *NetworkResource) (collide bool) {
|
|||
idx.UsedPorts[n.IP] = used
|
||||
}
|
||||
for _, port := range n.ReservedPorts {
|
||||
if _, ok := used[port]; ok {
|
||||
if _, ok := used[port.Value]; ok {
|
||||
collide = true
|
||||
} else {
|
||||
used[port] = struct{}{}
|
||||
used[port.Value] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
|
|||
|
||||
// Check if any of the reserved ports are in use
|
||||
for _, port := range ask.ReservedPorts {
|
||||
if _, ok := idx.UsedPorts[ipStr][port]; ok {
|
||||
if _, ok := idx.UsedPorts[ipStr][port.Value]; ok {
|
||||
err = fmt.Errorf("reserved port collision")
|
||||
return
|
||||
}
|
||||
|
@ -179,10 +179,10 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
|
|||
if _, ok := idx.UsedPorts[ipStr][randPort]; ok {
|
||||
goto PICK
|
||||
}
|
||||
if IntContains(offer.ReservedPorts, randPort) {
|
||||
if isPortReserved(offer.ReservedPorts, randPort) {
|
||||
goto PICK
|
||||
}
|
||||
offer.ReservedPorts = append(offer.ReservedPorts, randPort)
|
||||
offer.DynamicPorts[i].Value = randPort
|
||||
}
|
||||
|
||||
// Stop, we have an offer!
|
||||
|
@ -194,9 +194,9 @@ func (idx *NetworkIndex) AssignNetwork(ask *NetworkResource) (out *NetworkResour
|
|||
}
|
||||
|
||||
// IntContains scans an integer slice for a value
|
||||
func IntContains(haystack []int, needle int) bool {
|
||||
func isPortReserved(haystack []Port, needle int) bool {
|
||||
for _, item := range haystack {
|
||||
if item == needle {
|
||||
if item.Value == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ func TestNetworkIndex_Overcommitted(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 505,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
ReservedPorts: []Port{{"one", 8000}, {"two", 9000}},
|
||||
}
|
||||
collide := idx.AddReserved(reserved)
|
||||
if collide {
|
||||
|
@ -65,7 +65,7 @@ func TestNetworkIndex_SetNode(t *testing.T) {
|
|||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
ReservedPorts: []Port{{"ssh", 22}},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
|
@ -101,7 +101,7 @@ func TestNetworkIndex_AddAllocs(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
ReservedPorts: []Port{{"one", 8000}, {"two", 9000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -115,7 +115,7 @@ func TestNetworkIndex_AddAllocs(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{10000},
|
||||
ReservedPorts: []Port{{"one", 10000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -148,7 +148,7 @@ func TestNetworkIndex_AddReserved(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
ReservedPorts: []Port{{"one", 8000}, {"two", 9000}},
|
||||
}
|
||||
collide := idx.AddReserved(reserved)
|
||||
if collide {
|
||||
|
@ -189,7 +189,7 @@ func TestNetworkIndex_yieldIP(t *testing.T) {
|
|||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
ReservedPorts: []Port{{"ssh", 22}},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
|
@ -227,7 +227,7 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
&NetworkResource{
|
||||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
ReservedPorts: []int{22},
|
||||
ReservedPorts: []Port{{"ssh", 22}},
|
||||
MBits: 1,
|
||||
},
|
||||
},
|
||||
|
@ -244,7 +244,7 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 20,
|
||||
ReservedPorts: []int{8000, 9000},
|
||||
ReservedPorts: []Port{{"one", 8000}, {"two", 9000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -258,7 +258,7 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
Device: "eth0",
|
||||
IP: "192.168.0.100",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{10000},
|
||||
ReservedPorts: []Port{{"main", 10000}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -269,7 +269,7 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
|
||||
// Ask for a reserved port
|
||||
ask := &NetworkResource{
|
||||
ReservedPorts: []int{8000},
|
||||
ReservedPorts: []Port{{"main", 8000}},
|
||||
}
|
||||
offer, err := idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
|
@ -281,13 +281,14 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
if offer.IP != "192.168.0.101" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != 8000 {
|
||||
rp := Port{"main", 8000}
|
||||
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != rp {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
|
||||
// Ask for dynamic ports
|
||||
ask = &NetworkResource{
|
||||
DynamicPorts: []string{"http", "https", "admin"},
|
||||
DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
|
||||
}
|
||||
offer, err = idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
|
@ -299,14 +300,19 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
if offer.IP != "192.168.0.100" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 3 {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
if len(offer.DynamicPorts) != 3 {
|
||||
t.Fatalf("There should be three dynamic ports")
|
||||
}
|
||||
for _, port := range offer.DynamicPorts {
|
||||
if port.Value == 0 {
|
||||
t.Fatalf("Dynamic Port: %v should have been assigned a host port", port.Label)
|
||||
}
|
||||
}
|
||||
|
||||
// Ask for reserved + dynamic ports
|
||||
ask = &NetworkResource{
|
||||
ReservedPorts: []int{12345},
|
||||
DynamicPorts: []string{"http", "https", "admin"},
|
||||
ReservedPorts: []Port{{"main", 2345}},
|
||||
DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
|
||||
}
|
||||
offer, err = idx.AssignNetwork(ask)
|
||||
if err != nil {
|
||||
|
@ -318,7 +324,9 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
if offer.IP != "192.168.0.100" {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
if len(offer.ReservedPorts) != 4 || offer.ReservedPorts[0] != 12345 {
|
||||
|
||||
rp = Port{"main", 2345}
|
||||
if len(offer.ReservedPorts) != 1 || offer.ReservedPorts[0] != rp {
|
||||
t.Fatalf("bad: %#v", offer)
|
||||
}
|
||||
|
||||
|
@ -336,14 +344,14 @@ func TestNetworkIndex_AssignNetwork(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIntContains(t *testing.T) {
|
||||
l := []int{1, 2, 10, 20}
|
||||
if IntContains(l, 50) {
|
||||
l := []Port{{"one", 1}, {"two", 2}, {"ten", 10}, {"twenty", 20}}
|
||||
if isPortReserved(l, 50) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !IntContains(l, 20) {
|
||||
if !isPortReserved(l, 20) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
if !IntContains(l, 1) {
|
||||
if !isPortReserved(l, 1) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -620,6 +621,11 @@ func (r *Resources) GoString() string {
|
|||
return fmt.Sprintf("*%#v", *r)
|
||||
}
|
||||
|
||||
type Port struct {
|
||||
Label string
|
||||
Value int `mapstructure:"static"`
|
||||
}
|
||||
|
||||
// NetworkResource is used to represent available network
|
||||
// resources
|
||||
type NetworkResource struct {
|
||||
|
@ -627,8 +633,8 @@ type NetworkResource struct {
|
|||
CIDR string // CIDR block of addresses
|
||||
IP string // IP address
|
||||
MBits int // Throughput
|
||||
ReservedPorts []int `mapstructure:"reserved_ports"` // Reserved ports
|
||||
DynamicPorts []string `mapstructure:"dynamic_ports"` // Dynamically assigned ports
|
||||
ReservedPorts []Port // Reserved ports
|
||||
DynamicPorts []Port // Dynamically assigned ports
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the network resource
|
||||
|
@ -636,7 +642,7 @@ func (n *NetworkResource) Copy() *NetworkResource {
|
|||
newR := new(NetworkResource)
|
||||
*newR = *n
|
||||
if n.ReservedPorts != nil {
|
||||
newR.ReservedPorts = make([]int, len(n.ReservedPorts))
|
||||
newR.ReservedPorts = make([]Port, len(n.ReservedPorts))
|
||||
copy(newR.ReservedPorts, n.ReservedPorts)
|
||||
}
|
||||
return newR
|
||||
|
@ -656,50 +662,13 @@ func (n *NetworkResource) GoString() string {
|
|||
return fmt.Sprintf("*%#v", *n)
|
||||
}
|
||||
|
||||
// MapDynamicPorts returns a mapping of Label:PortNumber for dynamic ports
|
||||
// allocated on this NetworkResource. The ordering of Label:Port pairs is
|
||||
// random.
|
||||
//
|
||||
// Details:
|
||||
//
|
||||
// The jobspec lets us ask for two types of ports: Reserved ports and Dynamic
|
||||
// ports. Reserved ports are identified by the port number, while Dynamic ports
|
||||
// are identified by a Label.
|
||||
//
|
||||
// When we ask nomad to run a job it checks to see if the Reserved ports we
|
||||
// requested are available. If they are, it then tries to provision any Dynamic
|
||||
// ports that we have requested. When available ports are found to satisfy our
|
||||
// dynamic port requirements, they are APPENDED to the reserved ports list. In
|
||||
// effect, the reserved ports list serves double-duty. First it indicates the
|
||||
// ports we *want*, and then it indicates the ports we are *using*.
|
||||
//
|
||||
// After the the offer process is complete and the job is scheduled we want to
|
||||
// see which ports were made available to us. To see the dynamic ports that
|
||||
// were allocated to us we look at the last N ports in our reservation, where N
|
||||
// is how many dynamic ports we requested.
|
||||
//
|
||||
// MapDynamicPorts matches these port numbers with their labels and gives you
|
||||
// the port mapping.
|
||||
//
|
||||
// Also, be aware that this is intended to be called in the context of
|
||||
// task.Resources after an offer has been made. If you call it in some other
|
||||
// context the behavior is unspecified, including maybe crashing. So don't do that.
|
||||
func (n *NetworkResource) MapDynamicPorts() map[string]int {
|
||||
ports := n.ReservedPorts[len(n.ReservedPorts)-len(n.DynamicPorts):]
|
||||
mapping := make(map[string]int, len(n.DynamicPorts))
|
||||
|
||||
for idx, label := range n.DynamicPorts {
|
||||
mapping[label] = ports[idx]
|
||||
func (n *NetworkResource) MapLabelToValues() map[string]int {
|
||||
labelValues := make(map[string]int)
|
||||
ports := append(n.ReservedPorts, n.DynamicPorts...)
|
||||
for _, port := range ports {
|
||||
labelValues[port.Label] = port.Value
|
||||
}
|
||||
|
||||
return mapping
|
||||
}
|
||||
|
||||
// ListStaticPorts returns the list of Static ports allocated to this
|
||||
// NetworkResource. These are presumed to have known semantics so there is no
|
||||
// mapping information.
|
||||
func (n *NetworkResource) ListStaticPorts() []int {
|
||||
return n.ReservedPorts[:len(n.ReservedPorts)-len(n.DynamicPorts)]
|
||||
return labelValues
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -1032,7 +1001,7 @@ type Task struct {
|
|||
Driver string
|
||||
|
||||
// Config is provided to the driver to initialize
|
||||
Config map[string]string
|
||||
Config map[string]interface{}
|
||||
|
||||
// Map of environment variables to be used by the driver
|
||||
Env map[string]string
|
||||
|
@ -1669,7 +1638,15 @@ func (p *PlanResult) FullCommit(plan *Plan) (bool, int, int) {
|
|||
}
|
||||
|
||||
// msgpackHandle is a shared handle for encoding/decoding of structs
|
||||
var msgpackHandle = &codec.MsgpackHandle{}
|
||||
var msgpackHandle = func() *codec.MsgpackHandle {
|
||||
h := &codec.MsgpackHandle{RawToString: true}
|
||||
|
||||
// Sets the default type for decoding a map into a nil interface{}.
|
||||
// This is necessary in particular because we store the driver configs as a
|
||||
// nil interface{}.
|
||||
h.MapType = reflect.TypeOf(map[string]interface{}(nil))
|
||||
return h
|
||||
}()
|
||||
|
||||
// Decode is used to decode a MsgPack encoded object
|
||||
func Decode(buf []byte, out interface{}) error {
|
||||
|
|
|
@ -246,7 +246,7 @@ func TestResource_Add(t *testing.T) {
|
|||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
MBits: 100,
|
||||
ReservedPorts: []int{22},
|
||||
ReservedPorts: []Port{{"ssh", 22}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func TestResource_Add(t *testing.T) {
|
|||
&NetworkResource{
|
||||
IP: "10.0.0.1",
|
||||
MBits: 50,
|
||||
ReservedPorts: []int{80},
|
||||
ReservedPorts: []Port{{"web", 80}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ func TestResource_Add(t *testing.T) {
|
|||
&NetworkResource{
|
||||
CIDR: "10.0.0.0/8",
|
||||
MBits: 150,
|
||||
ReservedPorts: []int{22, 80},
|
||||
ReservedPorts: []Port{{"ssh", 22}, {"web", 80}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ func TestResource_Add_Network(t *testing.T) {
|
|||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 50,
|
||||
DynamicPorts: []string{"http", "https"},
|
||||
DynamicPorts: []Port{{"http", 0}, {"https", 0}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -302,7 +302,7 @@ func TestResource_Add_Network(t *testing.T) {
|
|||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 25,
|
||||
DynamicPorts: []string{"admin"},
|
||||
DynamicPorts: []Port{{"admin", 0}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ func TestResource_Add_Network(t *testing.T) {
|
|||
Networks: []*NetworkResource{
|
||||
&NetworkResource{
|
||||
MBits: 75,
|
||||
DynamicPorts: []string{"http", "https", "admin"},
|
||||
DynamicPorts: []Port{{"http", 0}, {"https", 0}, {"admin", 0}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -330,124 +330,6 @@ func TestResource_Add_Network(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMapDynamicPorts(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{80, 443, 3306, 8080},
|
||||
DynamicPorts: []string{"mysql", "admin"},
|
||||
}
|
||||
|
||||
expected := map[string]int{
|
||||
"mysql": 3306,
|
||||
"admin": 8080,
|
||||
}
|
||||
actual := resources.MapDynamicPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapDynamicPortsEmpty(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{},
|
||||
DynamicPorts: []string{},
|
||||
}
|
||||
|
||||
expected := map[string]int{}
|
||||
actual := resources.MapDynamicPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapDynamicPortsStaticOnly(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{80, 443},
|
||||
DynamicPorts: []string{},
|
||||
}
|
||||
|
||||
expected := map[string]int{}
|
||||
actual := resources.MapDynamicPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapDynamicPortsOnly(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{3306, 8080},
|
||||
DynamicPorts: []string{"mysql", "admin"},
|
||||
}
|
||||
|
||||
expected := map[string]int{
|
||||
"mysql": 3306,
|
||||
"admin": 8080,
|
||||
}
|
||||
actual := resources.MapDynamicPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListStaticPorts(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{80, 443, 3306, 8080},
|
||||
DynamicPorts: []string{"mysql", "admin"},
|
||||
}
|
||||
|
||||
expected := []int{80, 443}
|
||||
actual := resources.ListStaticPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListStaticPortsEmpty(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{},
|
||||
DynamicPorts: []string{},
|
||||
}
|
||||
|
||||
expected := []int{}
|
||||
actual := resources.ListStaticPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListStaticPortsOnly(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{80, 443},
|
||||
DynamicPorts: []string{},
|
||||
}
|
||||
|
||||
expected := []int{80, 443}
|
||||
actual := resources.ListStaticPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListStaticPortsDynamicOnly(t *testing.T) {
|
||||
resources := &NetworkResource{
|
||||
ReservedPorts: []int{3306, 8080},
|
||||
DynamicPorts: []string{"mysql", "admin"},
|
||||
}
|
||||
|
||||
expected := []int{}
|
||||
actual := resources.ListStaticPorts()
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %#v; found %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecode(t *testing.T) {
|
||||
type FooRequest struct {
|
||||
Foo string
|
||||
|
|
|
@ -397,9 +397,10 @@ func TestServiceSched_JobModify_InPlace(t *testing.T) {
|
|||
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
||||
|
||||
// Verify the network did not change
|
||||
rp := structs.Port{"main", 5000}
|
||||
for _, alloc := range out {
|
||||
for _, resources := range alloc.TaskResources {
|
||||
if resources.Networks[0].ReservedPorts[0] != 5000 {
|
||||
if resources.Networks[0].ReservedPorts[0] != rp {
|
||||
t.Fatalf("bad: %#v", alloc)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -463,9 +463,10 @@ func TestSystemSched_JobModify_InPlace(t *testing.T) {
|
|||
h.AssertEvalStatus(t, structs.EvalStatusComplete)
|
||||
|
||||
// Verify the network did not change
|
||||
rp := structs.Port{"main", 5000}
|
||||
for _, alloc := range out {
|
||||
for _, resources := range alloc.TaskResources {
|
||||
if resources.Networks[0].ReservedPorts[0] != 5000 {
|
||||
if resources.Networks[0].ReservedPorts[0] != rp {
|
||||
t.Fatalf("bad: %#v", alloc)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -343,7 +343,7 @@ func TestTasksUpdated(t *testing.T) {
|
|||
}
|
||||
|
||||
j6 := mock.Job()
|
||||
j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []string{"http", "https", "admin"}
|
||||
j6.TaskGroups[0].Tasks[0].Resources.Networks[0].DynamicPorts = []structs.Port{{"http", 0}, {"https", 0}, {"admin", 0}}
|
||||
if !tasksUpdated(j1.TaskGroups[0], j6.TaskGroups[0]) {
|
||||
t.Fatalf("bad")
|
||||
}
|
||||
|
|
|
@ -3,14 +3,15 @@ layout: "docs"
|
|||
page_title: "Commands: alloc-status"
|
||||
sidebar_current: "docs-commands-alloc-status"
|
||||
description: >
|
||||
Display status and metadata about existing allocations
|
||||
Display status and metadata about existing allocations and their tasks.
|
||||
---
|
||||
|
||||
# Command: alloc-status
|
||||
|
||||
The `alloc-status` command displays status information and metadata about
|
||||
an existing allocation. It can be useful while debugging to reveal the
|
||||
underlying reasons for scheduling decisions or failures.
|
||||
The `alloc-status` command displays status information and metadata about an
|
||||
existing allocation and its tasks. It can be useful while debugging to reveal
|
||||
the underlying reasons for scheduling decisions or failures, as well as the
|
||||
current state of its tasks.
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -24,25 +25,71 @@ and detailed information for it will be dumped.
|
|||
## General Options
|
||||
|
||||
<%= general_options_usage %>
|
||||
#
|
||||
## Status Options
|
||||
|
||||
* `-short`: Display short output. Shows only the most recent task event.
|
||||
|
||||
## Examples
|
||||
|
||||
Short status of an alloc:
|
||||
|
||||
```
|
||||
nomad alloc-status 9f3276d6-c873-c0a3-81ae-247e8c665cbe
|
||||
ID = 9f3276d6-c873-c0a3-81ae-247e8c665cbe
|
||||
EvalID = dc186cc2-a9b2-218e-cc00-eea3d4eaccf4
|
||||
$ nomad alloc-status --short a7365fe4-8b9f-4284-612d-a101fb41e773
|
||||
ID = a7365fe4-8b9f-4284-612d-a101fb41e773
|
||||
EvalID = 44c2d9ed-6377-ca3d-14a8-b2e6327230ce
|
||||
Name = example.cache[0]
|
||||
NodeID = <none>
|
||||
NodeID = e55859b1-4330-f00b-da49-8a292432ead3
|
||||
JobID = example
|
||||
ClientStatus = failed
|
||||
ClientDescription = <none>
|
||||
ClientStatus = running
|
||||
NodesEvaluated = 1
|
||||
NodesFiltered = 1
|
||||
NodesFiltered = 0
|
||||
NodesExhausted = 0
|
||||
AllocationTime = 15.242µs
|
||||
AllocationTime = 911.026µs
|
||||
CoalescedFailures = 0
|
||||
redis
|
||||
web
|
||||
|
||||
==> Tasks
|
||||
Name State LastEvent Time
|
||||
redis running Started 02:29:40 11/17/15
|
||||
web running Started 02:30:41 11/17/15
|
||||
|
||||
==> Status
|
||||
Allocation "9f3276d6-c873-c0a3-81ae-247e8c665cbe" status "failed" (1/1 nodes filtered)
|
||||
* Constraint "$attr.kernel.name = linux" filtered 1 nodes
|
||||
Allocation "a7365fe4-8b9f-4284-612d-a101fb41e773" status "running" (0/1 nodes filtered)
|
||||
* Score "e55859b1-4330-f00b-da49-8a292432ead3.binpack" = 10.334026
|
||||
```
|
||||
|
||||
Full status of an alloc, which shows one of the tasks dying and then being restarted:
|
||||
|
||||
```
|
||||
$ nomad alloc-status a7365fe4-8b9f-4284-612d-a101fb41e773
|
||||
ID = a7365fe4-8b9f-4284-612d-a101fb41e773
|
||||
EvalID = 44c2d9ed-6377-ca3d-14a8-b2e6327230ce
|
||||
Name = example.cache[0]
|
||||
NodeID = e55859b1-4330-f00b-da49-8a292432ead3
|
||||
JobID = example
|
||||
ClientStatus = running
|
||||
NodesEvaluated = 1
|
||||
NodesFiltered = 0
|
||||
NodesExhausted = 0
|
||||
AllocationTime = 911.026µs
|
||||
CoalescedFailures = 0
|
||||
|
||||
==> Task "redis" is "running"
|
||||
Recent Events:
|
||||
Time Type Description
|
||||
02:29:40 11/17/15 Started <none>
|
||||
|
||||
==> Task "web" is "running"
|
||||
Recent Events:
|
||||
Time Type Description
|
||||
02:30:41 11/17/15 Started <none>
|
||||
02:30:02 11/17/15 Terminated Exit Code: 137, Exit Message: "Docker container exited with non-zero exit code: 137"
|
||||
02:29:40 11/17/15 Started <none>
|
||||
|
||||
==> Status
|
||||
Allocation "a7365fe4-8b9f-4284-612d-a101fb41e773" status "running" (0/1 nodes filtered)
|
||||
* Score "e55859b1-4330-f00b-da49-8a292432ead3.binpack" = 10.334026
|
||||
|
||||
```
|
||||
|
|
|
@ -43,6 +43,10 @@ The `docker` driver supports the following configuration in the job specificatio
|
|||
* `search-domains` - (optional) A comma separated list of DNS search domains for the
|
||||
container to use.
|
||||
|
||||
* `hostname` - (optional) The hostname to assign to the container. When launching more
|
||||
than one of a task (using `count`) with this option set, every container the task
|
||||
starts will have the same hostname.
|
||||
|
||||
**Authentication**
|
||||
Registry authentication can be set per task with the following authentication
|
||||
parameters. These options can provide access to private repositories that
|
||||
|
|
Loading…
Reference in a new issue