From 4aaa815eaf9a057d990f1d31303777de4febc049 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Wed, 9 Sep 2015 16:20:54 -0700 Subject: [PATCH] Support to provide external key to sandbox Signed-off-by: Madhu Venugopal --- api/api.go | 3 + api/types.go | 1 + controller.go | 2 +- endpoint.go | 18 +++++- libnetwork_test.go | 111 +++++++++++++++++++++++++++++++++++ osl/namespace_linux.go | 34 +++++++++-- osl/namespace_unsupported.go | 4 ++ osl/namespace_windows.go | 4 ++ sandbox.go | 100 +++++++++++++++++++++++++------ 9 files changed, 250 insertions(+), 27 deletions(-) diff --git a/api/api.go b/api/api.go index 12cefe1..dcf070a 100644 --- a/api/api.go +++ b/api/api.go @@ -245,6 +245,9 @@ func (sc *sandboxCreate) parseOptions() []libnetwork.SandboxOption { if sc.UseDefaultSandbox { setFctList = append(setFctList, libnetwork.OptionUseDefaultSandbox()) } + if sc.UseExternalKey { + setFctList = append(setFctList, libnetwork.OptionUseExternalKey()) + } if sc.DNS != nil { for _, d := range sc.DNS { setFctList = append(setFctList, libnetwork.OptionDNS(d)) diff --git a/api/types.go b/api/types.go index d734a97..6218932 100644 --- a/api/types.go +++ b/api/types.go @@ -57,6 +57,7 @@ type sandboxCreate struct { DNS []string `json:"dns"` ExtraHosts []extraHost `json:"extra_hosts"` UseDefaultSandbox bool `json:"use_default_sandbox"` + UseExternalKey bool `json:"use_external_key"` } // endpointJoin represents the expected body of the "join endpoint" or "leave endpoint" http request messages diff --git a/controller.go b/controller.go index 178c57b..d84f5d4 100644 --- a/controller.go +++ b/controller.go @@ -414,7 +414,7 @@ func (c *controller) NewSandbox(containerID string, options ...SandboxOption) (S return nil, err } - if sb.osSbox == nil { + if sb.osSbox == nil && !sb.config.useExternalKey { if sb.osSbox, err = osl.NewSandbox(sb.Key(), !sb.config.useDefaultSandBox); err != nil { return nil, fmt.Errorf("failed to create new osl sandbox: %v", err) } diff --git a/endpoint.go b/endpoint.go index 0ec9f1d..376302e 100644 --- a/endpoint.go +++ b/endpoint.go @@ -1,6 +1,7 @@ package libnetwork import ( + "container/heap" "encoding/json" "fmt" "net" @@ -289,10 +290,25 @@ func (ep *endpoint) Join(sbox Sandbox, options ...EndpointOption) error { return err } + sb.Lock() + heap.Push(&sb.endpoints, ep) + sb.Unlock() + defer func() { + if err != nil { + for i, e := range sb.getConnectedEndpoints() { + if e == ep { + sb.Lock() + heap.Remove(&sb.endpoints, i) + sb.Unlock() + return + } + } + } + }() + if err = sb.populateNetworkResources(ep); err != nil { return err } - return nil } diff --git a/libnetwork_test.go b/libnetwork_test.go index 7bcf0f3..6dbcf7e 100644 --- a/libnetwork_test.go +++ b/libnetwork_test.go @@ -1188,6 +1188,117 @@ func (f *fakeSandbox) Delete() error { return nil } +func (f *fakeSandbox) SetKey(key string) error { + return nil +} + +func TestExternalKey(t *testing.T) { + if !netutils.IsRunningInContainer() { + defer osl.SetupTestOSContext(t)() + } + + n, err := createTestNetwork(bridgeNetType, "testnetwork", options.Generic{ + netlabel.GenericData: options.Generic{ + "BridgeName": "testnetwork", + "AllowNonDefaultBridge": true, + }, + }) + if err != nil { + t.Fatal(err) + } + defer func() { + if err := n.Delete(); err != nil { + t.Fatal(err) + } + }() + + ep, err := n.CreateEndpoint("ep1") + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep.Delete() + if err != nil { + t.Fatal(err) + } + }() + + ep2, err := n.CreateEndpoint("ep2") + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep2.Delete() + if err != nil { + t.Fatal(err) + } + }() + + cnt, err := controller.NewSandbox(containerID, + libnetwork.OptionHostname("test"), + libnetwork.OptionDomainname("docker.io"), + libnetwork.OptionUseExternalKey(), + libnetwork.OptionExtraHost("web", "192.168.0.1")) + defer func() { + if err := cnt.Delete(); err != nil { + t.Fatal(err) + } + }() + + // Join endpoint to sandbox before SetKey + err = ep.Join(cnt) + runtime.LockOSThread() + if err != nil { + t.Fatal(err) + } + defer func() { + err = ep.Leave(cnt) + runtime.LockOSThread() + if err != nil { + t.Fatal(err) + } + }() + + sbox := ep.Info().Sandbox() + if sbox == nil { + t.Fatalf("Expected to have a valid Sandbox") + } + + // Setting an non-existing key (namespace) must fail + if err := sbox.SetKey("this-must-fail"); err == nil { + t.Fatalf("Setkey must fail if the corresponding namespace is not created") + } + + // Create a new OS sandbox using the osl API before using it in SetKey + if _, err := osl.NewSandbox("ValidKey", true); err != nil { + t.Fatalf("Failed to create new osl sandbox") + } + + if err := sbox.SetKey("ValidKey"); err != nil { + t.Fatalf("Setkey failed with %v", err) + } + + // Join endpoint to sandbox after SetKey + err = ep2.Join(sbox) + if err != nil { + t.Fatal(err) + } + runtime.LockOSThread() + defer func() { + err = ep2.Leave(sbox) + runtime.LockOSThread() + if err != nil { + t.Fatal(err) + } + }() + + if ep.Info().Sandbox().Key() != ep2.Info().Sandbox().Key() { + t.Fatalf("ep1 and ep2 returned different container sandbox key") + } + + checkSandbox(t, ep.Info()) +} + func TestEndpointDeleteWithActiveContainer(t *testing.T) { if !netutils.IsRunningInContainer() { defer osl.SetupTestOSContext(t)() diff --git a/osl/namespace_linux.go b/osl/namespace_linux.go index 55e57a1..bd454b8 100644 --- a/osl/namespace_linux.go +++ b/osl/namespace_linux.go @@ -157,16 +157,38 @@ func (n *networkNamespace) NeighborOptions() NeighborOptionSetter { return n } +func mountNetworkNamespace(basePath string, lnPath string) error { + if err := syscall.Mount(basePath, lnPath, "bind", syscall.MS_BIND, ""); err != nil { + return err + } + + if err := loopbackUp(); err != nil { + return err + } + return nil +} + +// GetSandboxForExternalKey returns sandbox object for the supplied path +func GetSandboxForExternalKey(basePath string, key string) (Sandbox, error) { + var err error + if err = createNamespaceFile(key); err != nil { + return nil, err + } + n := &networkNamespace{path: basePath} + n.InvokeFunc(func() { + err = mountNetworkNamespace(basePath, key) + }) + if err != nil { + return nil, err + } + return &networkNamespace{path: key}, nil +} + func reexecCreateNamespace() { if len(os.Args) < 2 { log.Fatal("no namespace path provided") } - - if err := syscall.Mount("/proc/self/ns/net", os.Args[1], "bind", syscall.MS_BIND, ""); err != nil { - log.Fatal(err) - } - - if err := loopbackUp(); err != nil { + if err := mountNetworkNamespace("/proc/self/ns/net", os.Args[1]); err != nil { log.Fatal(err) } } diff --git a/osl/namespace_unsupported.go b/osl/namespace_unsupported.go index 77f286e..dbd8d9d 100644 --- a/osl/namespace_unsupported.go +++ b/osl/namespace_unsupported.go @@ -6,3 +6,7 @@ package osl // and waits for it. func GC() { } + +func GetSandboxForExternalKey(path string, key string) (Sandbox, error) { + return nil, nil +} diff --git a/osl/namespace_windows.go b/osl/namespace_windows.go index 36bd6c8..912d4a2 100644 --- a/osl/namespace_windows.go +++ b/osl/namespace_windows.go @@ -19,6 +19,10 @@ func NewSandbox(key string, osCreate bool) (Sandbox, error) { return nil, nil } +func GetSandboxForExternalKey(path string, key string) (Sandbox, error) { + return nil, nil +} + // GC triggers garbage collection of namespace path right away // and waits for it. func GC() { diff --git a/sandbox.go b/sandbox.go index 8972abc..93d4a2f 100644 --- a/sandbox.go +++ b/sandbox.go @@ -32,6 +32,8 @@ type Sandbox interface { // Refresh leaves all the endpoints, resets and re-apply the options, // re-joins all the endpoints without destroying the osl sandbox Refresh(options ...SandboxOption) error + // SetKey updates the Sandbox Key + SetKey(key string) error // Delete destroys this container after detaching it from all connected endpoints. Delete() error } @@ -102,6 +104,7 @@ type containerConfig struct { resolvConfPathConfig generic map[string]interface{} useDefaultSandBox bool + useExternalKey bool prio int // higher the value, more the priority } @@ -241,8 +244,14 @@ func (sb *sandbox) getConnectedEndpoints() []*endpoint { } func (sb *sandbox) updateGateway(ep *endpoint) error { - sb.osSbox.UnsetGateway() - sb.osSbox.UnsetGatewayIPv6() + sb.Lock() + osSbox := sb.osSbox + sb.Unlock() + if osSbox == nil { + return nil + } + osSbox.UnsetGateway() + osSbox.UnsetGatewayIPv6() if ep == nil { return nil @@ -252,18 +261,60 @@ func (sb *sandbox) updateGateway(ep *endpoint) error { joinInfo := ep.joinInfo ep.Unlock() - if err := sb.osSbox.SetGateway(joinInfo.gw); err != nil { + if err := osSbox.SetGateway(joinInfo.gw); err != nil { return fmt.Errorf("failed to set gateway while updating gateway: %v", err) } - if err := sb.osSbox.SetGatewayIPv6(joinInfo.gw6); err != nil { + if err := osSbox.SetGatewayIPv6(joinInfo.gw6); err != nil { return fmt.Errorf("failed to set IPv6 gateway while updating gateway: %v", err) } return nil } +func (sb *sandbox) SetKey(basePath string) error { + var err error + if basePath == "" { + return types.BadRequestErrorf("invalid sandbox key") + } + + sb.Lock() + if sb.osSbox != nil { + sb.Unlock() + return types.ForbiddenErrorf("failed to set sandbox key : already assigned") + } + sb.Unlock() + osSbox, err := osl.GetSandboxForExternalKey(basePath, sb.Key()) + if err != nil { + return err + } + sb.Lock() + sb.osSbox = osSbox + sb.Unlock() + defer func() { + if err != nil { + sb.Lock() + sb.osSbox = nil + sb.Unlock() + } + }() + + for _, ep := range sb.getConnectedEndpoints() { + if err = sb.populateNetworkResources(ep); err != nil { + return err + } + } + return nil +} + func (sb *sandbox) populateNetworkResources(ep *endpoint) error { + sb.Lock() + if sb.osSbox == nil { + sb.Unlock() + return nil + } + sb.Unlock() + ep.Lock() joinInfo := ep.joinInfo ifaces := ep.iFaces @@ -292,7 +343,6 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { } sb.Lock() - heap.Push(&sb.endpoints, ep) highEp := sb.endpoints[0] sb.Unlock() if ep == highEp { @@ -305,24 +355,28 @@ func (sb *sandbox) populateNetworkResources(ep *endpoint) error { } func (sb *sandbox) clearNetworkResources(ep *endpoint) error { - - for _, i := range sb.osSbox.Info().Interfaces() { - // Only remove the interfaces owned by this endpoint from the sandbox. - if ep.hasInterface(i.SrcName()) { - if err := i.Remove(); err != nil { - log.Debugf("Remove interface failed: %v", err) + sb.Lock() + osSbox := sb.osSbox + sb.Unlock() + if osSbox != nil { + for _, i := range osSbox.Info().Interfaces() { + // Only remove the interfaces owned by this endpoint from the sandbox. + if ep.hasInterface(i.SrcName()) { + if err := i.Remove(); err != nil { + log.Debugf("Remove interface failed: %v", err) + } } } - } - ep.Lock() - joinInfo := ep.joinInfo - ep.Unlock() + ep.Lock() + joinInfo := ep.joinInfo + ep.Unlock() - // Remove non-interface routes. - for _, r := range joinInfo.StaticRoutes { - if err := sb.osSbox.RemoveStaticRoute(r); err != nil { - log.Debugf("Remove route failed: %v", err) + // Remove non-interface routes. + for _, r := range joinInfo.StaticRoutes { + if err := osSbox.RemoveStaticRoute(r); err != nil { + log.Debugf("Remove route failed: %v", err) + } } } @@ -670,6 +724,14 @@ func OptionUseDefaultSandbox() SandboxOption { } } +// OptionUseExternalKey function returns an option setter for using provided namespace +// instead of creating one. +func OptionUseExternalKey() SandboxOption { + return func(sb *sandbox) { + sb.config.useExternalKey = true + } +} + // OptionGeneric function returns an option setter for Generic configuration // that is not managed by libNetwork but can be used by the Drivers during the call to // net container creation method. Container Labels are a good example.