Pass dynamic ports to the exec driver via environment variables

This commit is contained in:
Chris Bednarski 2015-09-22 22:33:29 -07:00
parent 09249d877c
commit 3e90379f05
4 changed files with 106 additions and 0 deletions

View File

@ -3,6 +3,7 @@ package driver
import (
"fmt"
"log"
"strings"
"sync"
"github.com/hashicorp/nomad/client/config"
@ -101,3 +102,31 @@ func NewExecContext() *ExecContext {
ctx := &ExecContext{}
return ctx
}
// PopulateEnvironment converts exec context and task configuration into
// environment variables so they can be passed along to a driver.
//
// The output is a list of strings with NAME=value pairs.
func PopulateEnvironment(ctx *ExecContext, task *structs.Task) []string {
env := []string{}
env = append(env, fmt.Sprintf("NOMAD_ALLOC_DIR=%s", ctx.AllocDir))
env = append(env, fmt.Sprintf("NOMAD_MEMORY_LIMIT=%d", task.Resources.MemoryMB))
env = append(env, fmt.Sprintf("NOMAD_CPU_LIMIT=%d", task.Resources.CPU))
// Meta values
for key, value := range task.Meta {
env = append(env, fmt.Sprintf("NOMAD_META_%s=%s", strings.ToUpper(key), value))
}
// Named Ports
// TODO make this work with multiple network interfaces.
network := task.Resources.Networks[0]
env = append(env, fmt.Sprintf("NOMAD_HOST_IP=%d", network.IP))
for idx, port := range network.ListDynamicPorts() {
env = append(env, fmt.Sprintf("NOMAD_PORT_%s=%d", strings.ToUpper(network.DynamicPorts[idx]), port))
}
return env
}

View File

@ -55,6 +55,9 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
return nil, fmt.Errorf("failed to constrain resources: %s", err)
}
// Add the environment
cmd.Command().Env = PopulateEnvironment(ctx, task)
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start command: %v", err)
}

View File

@ -645,6 +645,49 @@ func (n *NetworkResource) GoString() string {
return fmt.Sprintf("*%#v", *n)
}
// ListDynamicPorts returns a list of integers that correspond to the labels in
// .DynamicPorts. This is a little bit strange and bears some explanation:
//
// When we request dynamic ports nomad checks to see which ports are available.
// Once it finds enough to satisfy our requirements, it sends us an offer and
// marks those ports as reserved (or if it can't find enough we get an error).
// The complete reserved list includes reserved ports we requested, along with
// dynamic ports that nomad found for us. This list is merged together and
// becomes the list of ALL reserved ports. (Reserved has a double-meaning.)
//
// Later when we want to use our dynamic port range, we can call ListDynamicPorts
// to get a list of ints that correspond to the list of DyanmicPorts requested
// in the jobspec. The indexes in both DynamicPorts and ListDynamicPorts() will
// match. DynamicPorts actually holds the Labels while ListDynamicPorts tells us
// the port numbers (sorry, a bit confusing).
//
// 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) ListDynamicPorts() []int {
var ports []int
for i := len(n.ReservedPorts) - len(n.DynamicPorts); i < len(n.ReservedPorts); i++ {
ports = append(ports, n.ReservedPorts[i])
}
return ports
}
// MapDynamicPorts returns a mapping of Label:PortNumber for dynamic ports
// allocated on this NetworkResource. If you need the port order you should use
// ListDynamicPorts instead.
//
// See caveats in ListDynamicPorts
func (n *NetworkResource) MapDynamicPorts() map[string]int {
ports := n.ListDynamicPorts()
mapping := map[string]int{}
for idx, label := range n.DynamicPorts {
mapping[label] = ports[idx]
}
return mapping
}
const (
// JobTypeNomad is reserved for internal system tasks and is
// always handled by the CoreScheduler.

View File

@ -266,6 +266,37 @@ func TestResource_Add_Network(t *testing.T) {
}
}
func TestListDynamicPorts(t *testing.T) {
resources := &NetworkResource{
ReservedPorts: []int{80, 443, 3306, 8080},
DynamicPorts: []string{"mysql", "admin"},
}
expected := []int{3306, 8080}
actual := resources.ListDynamicPorts()
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("Expected %#v; found %#v", expected, actual)
}
}
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 TestEncodeDecode(t *testing.T) {
type FooRequest struct {
Foo string