This commit is contained in:
Alex Dadgar 2015-11-17 12:04:10 -08:00
commit 171a23cb74
41 changed files with 648 additions and 430 deletions

View File

@ -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{

View File

@ -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
}

View File

@ -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

View File

@ -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}},
},
},
}

View File

@ -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",
},

View File

@ -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)

View File

@ -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",
},

View File

@ -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())
}
}

View File

@ -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",

View File

@ -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

View File

@ -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",
},

View File

@ -21,7 +21,7 @@ var (
Networks: []*structs.NetworkResource{
&structs.NetworkResource{
MBits: 50,
DynamicPorts: []string{"http"},
DynamicPorts: []structs.Port{{Label: "http"}},
},
},
}

View File

@ -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

View File

@ -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,

View File

@ -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 {

View File

@ -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",

View File

@ -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

View File

@ -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",
},

View File

@ -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
}

View File

@ -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",

View File

@ -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})

View File

@ -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
}

View File

@ -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" {
}
}
}
}

View File

@ -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 {

View File

@ -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)
} else {
seenLabel[strings.ToLower(label)] = label
}
var networkObj *ast.ObjectList
if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
networkObj = ot.List
} else {
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 {

View File

@ -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)
}
}

View File

@ -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 ""{}
}
}
}

View File

@ -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" {}
}
}
}

View File

@ -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" {}
}
}
}

View File

@ -0,0 +1,10 @@
job "foo" {
task "bar" {
driver = "docker"
config {
port_map {
db = 1234
}
}
}
}

View File

@ -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"}},
},
},
},

View File

@ -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}},
},
},
},

View File

@ -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
}
}

View File

@ -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")
}
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strings"
"time"
@ -620,15 +621,20 @@ 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 {
Device string // Name of the device
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
Device string // Name of the device
CIDR string // CIDR block of addresses
IP string // IP address
MBits int // Throughput
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 {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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
```

View File

@ -42,6 +42,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