Create private link via iptables to linked containers

Allow active links to be removed
This commit is contained in:
Michael Crosby
2013-09-17 17:43:14 +00:00
parent a712b7467e
commit c7d854af01
6 changed files with 191 additions and 90 deletions
+34 -2
View File
@@ -960,10 +960,42 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
func getLinksJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
out := []APILink{}
name := r.FormValue("name")
rawRm := r.FormValue("rm")
links := srv.runtime.links.Get(name)
rm, err := getBoolParam(rawRm)
if err != nil {
return err
}
if rm {
link := srv.runtime.links.GetById(name)
if link != nil {
if err := srv.runtime.links.removeLink(link); err != nil {
return err
}
w.WriteHeader(http.StatusOK)
return nil
}
w.WriteHeader(http.StatusNotFound)
return nil
}
if name == "" {
return fmt.Errorf("Name cannot be empty for link")
}
container := srv.runtime.Get(name)
if container == nil {
return fmt.Errorf("Container not found %s", name)
}
links := srv.runtime.links.Get(container)
for _, l := range links {
out = append(out, APILink{l.To, l.From, l.IP, fmt.Sprint(l.Port), l.Alias})
out = append(out, APILink{
ID: l.ID(),
To: l.ToID,
From: l.FromID,
Port: fmt.Sprint(l.Port),
Alias: l.Alias,
})
}
w.Header().Add("Content-Type", "application/json")
+1 -1
View File
@@ -122,9 +122,9 @@ type APICopy struct {
}
type APILink struct {
ID string
To string
From string
IP string
Port string
Alias string
}
+13 -3
View File
@@ -1114,6 +1114,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
func (cli *DockerCli) CmdLink(args ...string) error {
cmd := Subcmd("link", "[OPTIONS] CONTAINER", "Get the links for a container")
flRm := cmd.Bool("rm", false, "Remove an existing link by the link ID")
if err := cmd.Parse(args); err != nil {
return err
@@ -1122,21 +1123,30 @@ func (cli *DockerCli) CmdLink(args ...string) error {
v := url.Values{}
v.Set("name", cmd.Arg(0))
body, _, err := cli.call("GET", "/links/json?"+v.Encode(), nil)
if *flRm {
v.Set("rm", "1")
}
body, statusCode, err := cli.call("GET", "/links/json?"+v.Encode(), nil)
if err != nil {
return err
}
if *flRm && statusCode == 200 {
fmt.Printf("Link successfully removed: %s\n", cmd.Arg(0))
return nil
}
var links []APILink
if err := json.Unmarshal(body, &links); err != nil {
return err
}
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
fmt.Fprintf(w, "FROM\tTO\tADDRESS\tALIAS")
fmt.Fprintf(w, "ID\tFROM\tTO\tPORT\tALIAS")
fmt.Fprintf(w, "\n")
for _, l := range links {
fmt.Fprintf(w, "%s\t%s\t%s\t%s", l.From, l.To, fmt.Sprintf("%s:%s", l.IP, l.Port), l.Alias)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s", l.ID, l.From, l.To, l.Port, l.Alias)
fmt.Fprintf(w, "\n")
}
w.Flush()
+27 -35
View File
@@ -91,7 +91,7 @@ type HostConfig struct {
ContainerIDFile string
LxcConf []KeyValuePair
PortBindings map[Port][]PortBinding
Links []Link
Links []string
}
type BindMap struct {
@@ -265,11 +265,6 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
}
}
links, err := parseLinks(flLinks)
if err != nil {
return nil, nil, cmd, err
}
config := &Config{
Hostname: *flHostname,
Domainname: domainname,
@@ -300,7 +295,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
PortBindings: portBindings,
Links: links,
Links: flLinks,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
@@ -827,21 +822,31 @@ func (container *Container) Start(hostConfig *HostConfig) error {
"-e", "HOSTNAME="+container.Config.Hostname,
)
if hostConfig != nil && hostConfig.Links != nil {
if !container.Config.NetworkDisabled && hostConfig != nil && hostConfig.Links != nil {
runtime := container.runtime
for _, l := range hostConfig.Links {
linkedContainer := runtime.Get(l.From)
parts, err := parseLink(l)
if err != nil {
return err
}
p := NewPort(splitProtoPort(parts["port"]))
linkedContainer := runtime.Get(parts["id"])
if linkedContainer == nil {
return fmt.Errorf("Cannot locate container for link: %s AS %s", l.From, l.Alias)
}
if err := linkedContainer.AcceptLink(l); err != nil {
link, err := runtime.links.NewLink(container, linkedContainer, runtime.networkManager.bridgeIface, p, parts["alias"])
if err != nil {
return err
}
if err := container.Link(linkedContainer, &l); err != nil {
if err := link.Enable(); err != nil {
// If we encounter an err, make sure we remove all links
for _, registeredLinks := range runtime.links.Get(container) {
runtime.links.removeLink(registeredLinks)
}
return err
}
params = append(params, "-e", l.ToEnv())
for _, envVar := range link.ToEnv() {
params = append(params, "-e", envVar)
}
}
}
@@ -1080,6 +1085,13 @@ func (container *Container) monitor(hostConfig *HostConfig) {
// Cleanup
container.releaseNetwork()
//Destroy all links
runtime := container.runtime
for _, link := range runtime.links.Get(container) {
runtime.links.removeLink(link)
}
if container.Config.OpenStdin {
if err := container.stdin.Close(); err != nil {
utils.Debugf("%s: Error close stdin: %s", container.ID, err)
@@ -1370,26 +1382,6 @@ func (container *Container) Copy(resource string) (Archive, error) {
return TarFilter(basePath, Uncompressed, filter)
}
func (container *Container) AcceptLink(l Link) error {
if !container.State.Running {
return fmt.Errorf("Cannot accept link on a non running container: %s AS %s", l.From, l.Alias)
}
if !container.Exposes(l.Port) {
return fmt.Errorf("Cannot accept link to %s because %s is not exposed", container.ID, l.Port)
}
return nil
}
func (container *Container) Link(c *Container, l *Link) error {
l.To = utils.TruncateID(container.ID)
l.IP = c.NetworkSettings.IPAddress
if err := container.runtime.links.RegisterLink(*l); err != nil {
return err
}
return nil
}
// Returns true if the container exposes a certain port
func (container *Container) Exposes(p Port) bool {
_, exists := container.Config.ExposedPorts[p]
+114 -25
View File
@@ -2,58 +2,147 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/iptables"
"github.com/dotcloud/docker/utils"
"strings"
)
// A Link represents a connection between two containers
// for a specific port on a specific bridge interface
type Link struct {
From string
To string
IP string
Port Port
Alias string
FromID string
ToID string
FromIP string
ToIP string
BridgeInterface string
Port Port
Alias string
FromEnvironment []string
isEnabled bool
}
type LinkRepository struct {
links map[string]Link
links map[string]*Link
}
func (l *Link) ToEnv() string {
return fmt.Sprintf("%s_ADDR=%s://%s:%s", strings.ToUpper(l.Alias), l.Port.Proto(), l.IP, l.Port.Port())
func (r *LinkRepository) NewLink(to, from *Container, bridgeInterface string, p Port, alias string) (*Link, error) {
if !from.State.Running {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", from.ID, alias)
}
if !from.Exposes(p) {
return nil, fmt.Errorf("Cannot link to %s because %s is not exposed", from.ID, p)
}
l := &Link{
FromID: utils.TruncateID(from.ID),
ToID: utils.TruncateID(to.ID),
BridgeInterface: bridgeInterface,
Alias: alias,
Port: p,
FromIP: from.NetworkSettings.IPAddress,
ToIP: to.NetworkSettings.IPAddress,
FromEnvironment: from.Config.Env,
}
if err := r.registerLink(l); err != nil {
return nil, err
}
return l, nil
}
func (l *Link) ID() string {
return fmt.Sprintf("%s:%s", l.ToID, l.Alias)
}
func (l *Link) ToEnv() []string {
env := []string{fmt.Sprintf("%s_ADDR=%s://%s:%s", strings.ToUpper(l.Alias), l.Port.Proto(), l.FromIP, l.Port.Port())}
if l.FromEnvironment != nil {
for _, v := range l.FromEnvironment {
parts := strings.Split(v, "=")
if len(parts) < 2 {
continue
}
env = append(env, fmt.Sprintf("%s_ENV_%s=%s", strings.ToUpper(l.Alias), parts[0], parts[1]))
}
}
return env
}
func (l *Link) Enable() error {
if err := l.toggle("-I"); err != nil {
return err
}
l.isEnabled = true
return nil
}
func (l *Link) Disable() {
// We do not care about erros here because the link may not
// exist in iptables
l.toggle("-D")
l.isEnabled = false
}
func (l *Link) toggle(action string) error {
if err := iptables.Raw(action, "FORWARD",
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
"-p", l.Port.Proto(),
"-s", l.ToIP,
"--dport", l.Port.Port(),
"-d", l.FromIP,
"-j", "ACCEPT"); err != nil {
return err
}
if err := iptables.Raw(action, "FORWARD",
"-i", l.BridgeInterface, "-o", l.BridgeInterface,
"-p", l.Port.Proto(),
"-s", l.FromIP,
"--sport", l.Port.Port(),
"-d", l.ToIP,
"-j", "ACCEPT"); err != nil {
return err
}
return nil
}
func NewLinkRepository(root string) (*LinkRepository, error) {
r := &LinkRepository{make(map[string]Link)}
r := &LinkRepository{make(map[string]*Link)}
return r, nil
}
// Return all links for a container
func (l *LinkRepository) Get(id string) []Link {
id = strings.Trim(strings.ToLower(id), "")
out := []Link{}
func (l *LinkRepository) Get(c *Container) []*Link {
id := utils.TruncateID(c.ID)
out := []*Link{}
for _, link := range l.links {
if link.To == id || link.From == id {
if link.ToID == id || link.FromID == id {
out = append(out, link)
}
}
return out
}
// Returns the link for a current alias
func (l *LinkRepository) GetByAlias(alias string) (Link, error) {
link, exists := l.links[alias]
if !exists {
return link, fmt.Errorf("Link does not exist for alias: %s", alias)
}
return link, nil
// Get a link based on the link's ID
func (l *LinkRepository) GetById(id string) *Link {
return l.links[id]
}
// Create a new link with a unique alias
func (l *LinkRepository) RegisterLink(link Link) error {
if _, exists := l.links[link.Alias]; exists {
return fmt.Errorf("A link for %s already exists", link.Alias)
func (l *LinkRepository) registerLink(link *Link) error {
if _, exists := l.links[link.ID()]; exists {
return fmt.Errorf("A link for %s already exists", link.ID())
}
utils.Debugf("Registering link: %v", link)
l.links[link.Alias] = link
utils.Debugf("Registering link: %s", link.ID())
l.links[link.ID()] = link
return nil
}
// Disable and remote the link from the repository
func (l *LinkRepository) removeLink(link *Link) error {
link.Disable()
utils.Debugf("Removing link: %s", link.ID())
delete(l.links, link.ID())
return nil
}
+2 -24
View File
@@ -285,28 +285,6 @@ func migratePortMappings(config *Config) error {
// Links come in the format of
// id:port:alias
func parseLink(rawLink string) (Link, error) {
parts, err := utils.PartParser("id:port:alias", rawLink)
if err != nil {
return Link{}, err
}
port := NewPort(splitProtoPort(parts["port"]))
return Link{
From: parts["id"],
Alias: parts["alias"],
Port: port,
}, nil
}
func parseLinks(rawLinks []string) ([]Link, error) {
out := make([]Link, len(rawLinks))
for i, l := range rawLinks {
link, err := parseLink(l)
if err != nil {
return nil, err
}
out[i] = link
}
return out, nil
func parseLink(rawLink string) (map[string]string, error) {
return utils.PartParser("id:port:alias", rawLink)
}