├── Dockerfile ├── .dockerignore ├── utils ├── math │ └── math.go ├── os │ └── utils.go ├── log │ └── log.go ├── netns │ └── netns.go └── test │ └── test_utils.go ├── Dockerfile.amd64 ├── Dockerfile.s390x ├── Dockerfile.arm64 ├── Dockerfile.ppc64le ├── .gitignore ├── tests ├── custom_if_prefix │ ├── libnetwork_suite_test.go │ └── libnetwork_env_var_test.go ├── default_environment │ ├── libnetwork_suite_test.go │ └── libnetwork_test.go └── custom_wep_labelling │ ├── libnetwork_suite_test.go │ └── libnetwork_env_var_test.go ├── glide.yaml ├── driver ├── package.go ├── ipam_driver.go └── network_driver.go ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── RELEASING.md ├── main.go ├── README.md ├── LICENSE ├── Makefile └── glide.lock /Dockerfile: -------------------------------------------------------------------------------- 1 | Dockerfile.amd64 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | vendor 3 | *.tar 4 | build 5 | default.etcd 6 | .semaphore-cache 7 | -------------------------------------------------------------------------------- /utils/math/math.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | func MinInt(a, b int) int { 4 | if a < b { 5 | return a 6 | } 7 | return b 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile.amd64: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | MAINTAINER Tom Denham 3 | ADD dist/libnetwork-plugin-amd64 /libnetwork-plugin 4 | ENTRYPOINT ["/libnetwork-plugin"] 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile.s390x: -------------------------------------------------------------------------------- 1 | FROM s390x/alpine 2 | MAINTAINER Tom Denham 3 | ADD dist/libnetwork-plugin-s390x /libnetwork-plugin 4 | ENTRYPOINT ["/libnetwork-plugin"] 5 | -------------------------------------------------------------------------------- /Dockerfile.arm64: -------------------------------------------------------------------------------- 1 | FROM arm64v8/alpine 2 | MAINTAINER Tom Denham 3 | ADD dist/libnetwork-plugin-arm64 /libnetwork-plugin 4 | ENTRYPOINT ["/libnetwork-plugin"] 5 | -------------------------------------------------------------------------------- /Dockerfile.ppc64le: -------------------------------------------------------------------------------- 1 | FROM ppc64le/alpine 2 | MAINTAINER Tom Denham 3 | ADD dist/libnetwork-plugin-ppc64le /libnetwork-plugin 4 | ENTRYPOINT ["/libnetwork-plugin"] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docker 2 | build 3 | vendor 4 | certs 5 | dist 6 | env 7 | .idea 8 | .vscode 9 | *.pyc 10 | *~ 11 | .coverage 12 | cover 13 | calicoctl 14 | *.tar 15 | *.created 16 | libnetwork-plugin 17 | .go-pkg-cache/ 18 | -------------------------------------------------------------------------------- /utils/os/utils.go: -------------------------------------------------------------------------------- 1 | package os 2 | 3 | import "os" 4 | 5 | const hostnameEnv = "HOSTNAME" 6 | 7 | func GetHostname() (string, error) { 8 | hostnameFromEnv := os.Getenv(hostnameEnv) 9 | if hostname, err := os.Hostname(); hostnameFromEnv == "" { 10 | return hostname, err 11 | } 12 | return hostnameFromEnv, nil 13 | } 14 | -------------------------------------------------------------------------------- /utils/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | logger "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func JSONMessage(formattedMessage string, data interface{}) { 10 | requestJSON, err := json.Marshal(data) 11 | if err != nil { 12 | logger.Fatal(err) 13 | return 14 | } 15 | logger.WithField("JSON", string(requestJSON)).Info(formattedMessage) 16 | } 17 | -------------------------------------------------------------------------------- /tests/custom_if_prefix/libnetwork_suite_test.go: -------------------------------------------------------------------------------- 1 | package custom_if_prefix 2 | 3 | // Tests in this suite are for when the plugin has been run with a custom IF prefix 4 | 5 | import ( 6 | . "github.com/onsi/ginkgo" 7 | . "github.com/onsi/gomega" 8 | 9 | "testing" 10 | ) 11 | 12 | func TestLibnetwork(t *testing.T) { 13 | RegisterFailHandler(Fail) 14 | RunSpecs(t, "Custom IF Prefix Libnetwork Suite") 15 | } 16 | -------------------------------------------------------------------------------- /tests/default_environment/libnetwork_suite_test.go: -------------------------------------------------------------------------------- 1 | package default_environment 2 | 3 | // Tests in this suite are for testing when the plugin without additinal 4 | // environemnt variables 5 | 6 | import ( 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "testing" 11 | ) 12 | 13 | func TestLibnetwork(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Default Libnetwork Suite") 16 | } 17 | -------------------------------------------------------------------------------- /tests/custom_wep_labelling/libnetwork_suite_test.go: -------------------------------------------------------------------------------- 1 | package custom_wep_labelling 2 | 3 | // Tests in this suite are for when the plugin has been configured to label 4 | // workload endpoints 5 | 6 | import ( 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | 10 | "testing" 11 | ) 12 | 13 | func TestLibnetwork(t *testing.T) { 14 | RegisterFailHandler(Fail) 15 | RunSpecs(t, "Custom WEP labelling Libnetwork Suite") 16 | } 17 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/projectcalico/libnetwork-plugin 2 | import: 3 | - package: github.com/sirupsen/logrus 4 | version: ^1.0.5 5 | - package: github.com/codegangsta/cli 6 | version: ^1.20.0 7 | - package: github.com/docker/go-plugins-helpers 8 | subpackages: 9 | - ipam 10 | - network 11 | - package: github.com/projectcalico/libcalico-go 12 | version: master 13 | - package: github.com/docker/docker 14 | version: master 15 | - package: github.com/onsi/ginkgo 16 | - package: github.com/onsi/gomega 17 | subpackages: 18 | - gbytes 19 | - gexec 20 | -------------------------------------------------------------------------------- /driver/package.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | const ( 9 | // Calico IPAM module does not allow selection of pools from which to allocate 10 | // IP addresses. The pool ID, which has to be supplied in the libnetwork IPAM 11 | // API is therefore fixed. We use different values for IPv4 and IPv6 so that 12 | // during allocation we know which IP version to use. 13 | PoolIDV4 = "CalicoPoolIPv4" 14 | PoolIDV6 = "CalicoPoolIPv6" 15 | 16 | CalicoLocalAddressSpace = "CalicoLocalAddressSpace" 17 | CalicoGlobalAddressSpace = "CalicoGlobalAddressSpace" 18 | ) 19 | 20 | var IFPrefix = "cali" 21 | 22 | func init() { 23 | if os.Getenv("CALICO_LIBNETWORK_IFPREFIX") != "" { 24 | IFPrefix = os.Getenv("CALICO_LIBNETWORK_IFPREFIX") 25 | log.Println("Updated CALICO_LIBNETWORK_IFPREFIX to ", IFPrefix) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | A few sentences describing the overall goals of the pull request's commits. 3 | Please include 4 | - the type of fix - (e.g. bug fix, new feature, documentation) 5 | - some details on _why_ this PR should be merged 6 | - the details of the testing you've done on it (both manual and automated) 7 | - which components are affected by this PR 8 | 9 | ## Todos 10 | - [ ] Tests 11 | - [ ] Documentation 12 | - [ ] Release note 13 | 14 | ## Release Note 15 | 21 | 22 | ```release-note 23 | None required 24 | ``` 25 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | ## Resulting artifacts 4 | Creating a new release creates the following artifacts 5 | * `calico/libnetwork-plugin:$VERSION` and `calico/libnetwork-plugin:latest` container images (and the quay.io variants) 6 | * `libnetwork-plugin` binary (stored in the `dist` directory. 7 | 8 | ## Preparing for a release 9 | Ensure that the branch you want to release from (typically master) is in a good state. 10 | e.g. Update the libcalico-go pin to the latest release in glide.yaml and run `glide up -v`, create PR, ensure test pass and merge. 11 | 12 | You should have no local changes and tests should be passing. 13 | 14 | ## Creating the release 15 | 1. Choose a version e.g. `v1.0.0` 16 | 2. Create the release artifacts repositories `make release VERSION=v1.0.0`. 17 | 3. Follow the instructions to push the artifacts and git tag. 18 | 4. Create a release on Github, using the tag which was just pushed. Attach the binary. 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Docker version: 30 | * Libnetwork-plugin version: 31 | * How you deployed the libnetwork plugin: 32 | * Operating System and version: 33 | * Link to your project (optional): 34 | 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/docker/go-plugins-helpers/ipam" 7 | "github.com/docker/go-plugins-helpers/network" 8 | "github.com/projectcalico/libcalico-go/lib/apiconfig" 9 | "github.com/projectcalico/libcalico-go/lib/clientv3" 10 | "github.com/projectcalico/libnetwork-plugin/driver" 11 | log "github.com/sirupsen/logrus" 12 | 13 | "flag" 14 | 15 | "fmt" 16 | ) 17 | 18 | const ( 19 | ipamPluginName = "calico-ipam" 20 | networkPluginName = "calico" 21 | ) 22 | 23 | var ( 24 | config *apiconfig.CalicoAPIConfig 25 | client clientv3.Interface 26 | ) 27 | 28 | func init() { 29 | // Output to stderr instead of stdout, could also be a file. 30 | log.SetOutput(os.Stderr) 31 | } 32 | 33 | func initializeClient() { 34 | var err error 35 | 36 | if config, err = apiconfig.LoadClientConfig(""); err != nil { 37 | panic(err) 38 | } 39 | if client, err = clientv3.New(*config); err != nil { 40 | panic(err) 41 | } 42 | 43 | if os.Getenv("CALICO_DEBUG") != "" { 44 | log.SetLevel(log.DebugLevel) 45 | log.Debugln("Debug logging enabled") 46 | } 47 | } 48 | 49 | // VERSION is filled out during the build process (using git describe output) 50 | var VERSION string 51 | 52 | const ( 53 | // The root user group id is 0 54 | ROOT_GID = 0 55 | ) 56 | 57 | func main() { 58 | // Display the version on "-v" 59 | // Use a new flag set so as not to conflict with existing libraries which use "flag" 60 | flagSet := flag.NewFlagSet("Calico", flag.ExitOnError) 61 | 62 | version := flagSet.Bool("v", false, "Display version") 63 | err := flagSet.Parse(os.Args[1:]) 64 | if err != nil { 65 | log.Fatalln(err) 66 | } 67 | if *version { 68 | fmt.Println(VERSION) 69 | os.Exit(0) 70 | } 71 | 72 | initializeClient() 73 | 74 | errChannel := make(chan error) 75 | networkHandler := network.NewHandler(driver.NewNetworkDriver(client)) 76 | ipamHandler := ipam.NewHandler(driver.NewIpamDriver(client)) 77 | 78 | go func(c chan error) { 79 | log.Infoln("calico-net has started.") 80 | err := networkHandler.ServeUnix(networkPluginName, ROOT_GID) 81 | log.Infoln("calico-net has stopped working.") 82 | c <- err 83 | }(errChannel) 84 | 85 | go func(c chan error) { 86 | log.Infoln("calico-ipam has started.") 87 | err := ipamHandler.ServeUnix(ipamPluginName, ROOT_GID) 88 | log.Infoln("calico-ipam has stopped working.") 89 | c <- err 90 | }(errChannel) 91 | 92 | err = <-errChannel 93 | 94 | log.Fatalln(err) 95 | } 96 | -------------------------------------------------------------------------------- /utils/netns/netns.go: -------------------------------------------------------------------------------- 1 | package netns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "syscall" 7 | 8 | log "github.com/sirupsen/logrus" 9 | 10 | "github.com/pkg/errors" 11 | "github.com/vishvananda/netlink" 12 | ) 13 | 14 | func CreateVeth(vethNameHost, vethNameNSTemp string, vethMTU uint16) error { 15 | veth := &netlink.Veth{ 16 | LinkAttrs: netlink.LinkAttrs{ 17 | Name: vethNameHost, 18 | MTU: int(vethMTU), 19 | }, 20 | PeerName: vethNameNSTemp, 21 | } 22 | if err := netlink.LinkAdd(veth); err != nil { 23 | return err 24 | } 25 | vethTemp := &netlink.Veth{ 26 | LinkAttrs: netlink.LinkAttrs{ 27 | Name: vethNameNSTemp, 28 | }, 29 | PeerName: vethNameHost, 30 | } 31 | // Set link state to up for both host and temp, 32 | if err := netlink.LinkSetUp(vethTemp); err != nil { 33 | return err 34 | } 35 | return netlink.LinkSetUp(veth) 36 | } 37 | 38 | func SetVethMac(vethNameHost, mac string) error { 39 | addr, err := net.ParseMAC(mac) 40 | if err != nil { 41 | return errors.Wrap(err, "Veth setting error") 42 | } 43 | return netlink.LinkSetHardwareAddr(&netlink.Veth{ 44 | LinkAttrs: netlink.LinkAttrs{ 45 | Name: vethNameHost, 46 | }, 47 | }, addr) 48 | } 49 | 50 | func RemoveVeth(vethNameHost string) error { 51 | if ok, err := IsVethExists(vethNameHost); err != nil { 52 | return errors.Wrap(err, "Veth removal error") 53 | } else if !ok { 54 | return nil 55 | } 56 | return netlink.LinkDel(&netlink.Veth{ 57 | LinkAttrs: netlink.LinkAttrs{ 58 | Name: vethNameHost, 59 | }, 60 | }) 61 | } 62 | 63 | func IsVethExists(vethHostName string) (bool, error) { 64 | links, err := netlink.LinkList() 65 | if err != nil { 66 | return false, errors.Wrap(err, "Veth existing check error") 67 | } 68 | for _, link := range links { 69 | if link.Attrs().Name == vethHostName { 70 | return true, nil 71 | } 72 | } 73 | return false, nil 74 | } 75 | 76 | // GetLinkLocalAddr Get the IPv6 link local address of interfaceName 77 | func GetLinkLocalAddr(interfaceName string) net.IP { 78 | link, err := netlink.LinkByName(interfaceName) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | addrs, err := netlink.AddrList(link, netlink.FAMILY_V6) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | var linkLocalAddr net.IP 87 | for _, addr := range addrs { 88 | if addr.Scope == syscall.RT_SCOPE_LINK { 89 | linkLocalAddr = addr.IP 90 | break 91 | } 92 | } 93 | if linkLocalAddr == nil { 94 | log.Warn(fmt.Sprintf("No IPv6 link local address found for interface: %s", interfaceName)) 95 | } 96 | return linkLocalAddr 97 | } 98 | -------------------------------------------------------------------------------- /tests/custom_if_prefix/libnetwork_env_var_test.go: -------------------------------------------------------------------------------- 1 | package custom_if_prefix 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | api "github.com/projectcalico/libcalico-go/lib/apis/v3" 12 | mathutils "github.com/projectcalico/libnetwork-plugin/utils/math" 13 | . "github.com/projectcalico/libnetwork-plugin/utils/test" 14 | ) 15 | 16 | var _ = Describe("Running plugin with custom ENV", func() { 17 | Describe("docker run", func() { 18 | It("creates a container on a network with correct IFPREFIX", func() { 19 | // Run the plugin with custom IFPREFIX 20 | RunPlugin("-e CALICO_LIBNETWORK_IFPREFIX=test") 21 | 22 | pool := "test" 23 | subnet := "192.169.0.0/16" 24 | // Since running the plugin starts etcd, the pool needs to be created after. 25 | CreatePool(pool, subnet) 26 | 27 | name := fmt.Sprintf("run%d", rand.Uint32()) 28 | nid := DockerString(fmt.Sprintf("docker network create --driver calico --ipam-driver calico-ipam --subnet %s %s ", subnet, pool)) 29 | UpdatePool(pool, subnet, nid) 30 | 31 | // Create a container that will just sit in the background 32 | DockerString(fmt.Sprintf("docker run --net %s -tid --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 33 | 34 | // Gather information for assertions 35 | dockerEndpoint := GetDockerEndpoint(name, pool) 36 | ip := dockerEndpoint.IPAddress 37 | mac := dockerEndpoint.MacAddress 38 | endpointID := dockerEndpoint.EndpointID 39 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 40 | 41 | // Check that the endpoint is created in etcd 42 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 43 | endpointJSON := GetEtcd(key) 44 | wep := api.NewWorkloadEndpoint() 45 | json.Unmarshal(endpointJSON, &wep) 46 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 47 | 48 | // Check profile 49 | profile := api.NewProfile() 50 | json.Unmarshal(GetEtcd(fmt.Sprintf("/calico/resources/v3/projectcalico.org/profiles/%s", pool)), &profile) 51 | Expect(profile.Name).Should(Equal(pool)) 52 | Expect(len(profile.Labels)).Should(Equal(0)) 53 | //tags deprecated 54 | Expect(profile.Spec.Ingress[0].Action).Should(Equal(api.Allow)) 55 | Expect(profile.Spec.Egress[0].Action).Should(Equal(api.Allow)) 56 | 57 | // Check the interface exists on the Host - it has an autoassigned 58 | // mac and ip, so don't check anything! 59 | DockerString(fmt.Sprintf("ip addr show %s", vethName)) 60 | 61 | // Make sure the interface in the container exists and has the assigned ip and mac 62 | containerNICString := DockerString(fmt.Sprintf("docker exec -i %s ip addr", name)) 63 | Expect(containerNICString).Should(ContainSubstring(ip)) 64 | Expect(containerNICString).Should(ContainSubstring(mac)) 65 | 66 | // Make sure the container has the routes we expect 67 | routes := DockerString(fmt.Sprintf("docker exec -i %s ip route", name)) 68 | Expect(routes).Should(Equal("default via 169.254.1.1 dev test0 \n169.254.1.1 dev test0 scope link")) 69 | 70 | // Delete container 71 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /utils/test/test_utils.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | "time" 12 | 13 | etcdclient "github.com/coreos/etcd/clientv3" 14 | "github.com/docker/docker/api/types/network" 15 | dockerclient "github.com/docker/docker/client" 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega/gexec" 18 | ) 19 | 20 | var kapi etcdclient.KV 21 | 22 | func init() { 23 | // Create a random seed 24 | rand.Seed(time.Now().UTC().UnixNano()) 25 | 26 | cfg := etcdclient.Config{Endpoints: []string{"http://127.0.0.1:2379"}} 27 | c, _ := etcdclient.New(cfg) 28 | kapi = etcdclient.NewKV(c) 29 | } 30 | 31 | // GetDockerEndpoint gets the endpoint information from Docker 32 | func GetDockerEndpoint(container, network string) *network.EndpointSettings { 33 | os.Setenv("DOCKER_API_VERSION", "1.24") 34 | os.Setenv("DOCKER_HOST", "http://localhost:5375") 35 | defer os.Setenv("DOCKER_HOST", "") 36 | cli, err := dockerclient.NewEnvClient() 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | info, err := cli.ContainerInspect(context.Background(), container) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | return info.NetworkSettings.Networks[network] 47 | } 48 | 49 | func GetNotExists(path string) bool { 50 | resp, err := kapi.Get(context.Background(), path) 51 | if err != nil { 52 | panic(err) 53 | } 54 | return len(resp.Kvs) == 0 55 | } 56 | 57 | // GetEtcd gets a string for a given etcd path 58 | func GetEtcd(path string) []byte { 59 | // TODO - would be better to use libcalico to get data rather than talking to etcd direct 60 | resp, err := kapi.Get(context.Background(), path) 61 | if err != nil { 62 | panic(err) 63 | } 64 | if len(resp.Kvs) != 1 { 65 | panic(errors.New("no answer")) 66 | } 67 | return resp.Kvs[0].Value 68 | } 69 | 70 | // GetEtcdString gets a string for a given etcd path 71 | func GetEtcdString(path string) string { 72 | return string(GetEtcd(path)) 73 | } 74 | 75 | // CreatePool creates a pool in etcd 76 | func CreatePool(pool, cidr string) { 77 | data := fmt.Sprintf(`{"kind":"IPPool","apiVersion":"projectcalico.org/v3","metadata":{"name":"%s", "creationTimestamp":"2018-06-05T11:47:45Z", "uid": "431f5c3e-68b6-11e8-8f6c-08002749ff23"},"spec":{"cidr":"%s","ipipMode":"Never","natOutgoing":true}}`, pool, cidr) 78 | key := fmt.Sprintf(`/calico/resources/v3/projectcalico.org/ippools/%s`, pool) 79 | _, err := kapi.Put(context.Background(), key, data) 80 | if err != nil { 81 | panic(err) 82 | } 83 | } 84 | 85 | // Update pool with network id 86 | func UpdatePool(pool, cidr, nid string) { 87 | data := fmt.Sprintf(`{"kind":"IPPool","apiVersion":"projectcalico.org/v3","metadata":{"name":"%s","uid":"431f5c3e-68b6-11e8-8f6c-08002749ff23","creationTimestamp":"2018-06-05T11:47:45Z","annotations":{"org.projectcalico.label.network.ID":"%s"}, "uid": "431f5c3e-68b6-11e8-8f6c-08002749ff23"},"spec":{"cidr":"%s","ipipMode":"Nerver","natOutgoing":true}}`, pool, nid, cidr) 88 | key := fmt.Sprintf(`/calico/resources/v3/projectcalico.org/ippools/%s`, pool) 89 | _, err := kapi.Put(context.Background(), key, data) 90 | if err != nil { 91 | panic(err) 92 | } 93 | } 94 | 95 | // WipeEtcd deletes everything under /calico from etcd 96 | func WipeEtcd() { 97 | r, err := kapi.Get(context.Background(), "/calico", etcdclient.WithFromKey()) 98 | if err != nil { 99 | panic(err) 100 | } 101 | for _, kv := range r.Kvs { 102 | kapi.Delete(context.Background(), string(kv.Key)) 103 | } 104 | } 105 | 106 | // DockerString runs a command on the Docker in Docker host returning a string 107 | func DockerString(cmd string) string { 108 | GinkgoWriter.Write([]byte(fmt.Sprintf("Running command [%s]\n", cmd))) 109 | command := exec.Command("bash", "-c", fmt.Sprintf("docker exec -i dind sh -c '%s'", cmd)) 110 | _, _ = command.StdinPipe() 111 | out, err := command.Output() 112 | if err != nil { 113 | GinkgoWriter.Write(out) 114 | GinkgoWriter.Write(err.(*exec.ExitError).Stderr) 115 | Fail("Command failed") 116 | } 117 | return strings.TrimSpace(string(out)) 118 | } 119 | 120 | // DockerSession runs a docker command returning the Session 121 | func DockerSession(cmd string) *Session { 122 | GinkgoWriter.Write([]byte(fmt.Sprintf("Running command [%s]\n", cmd))) 123 | command := exec.Command("bash", "-c", fmt.Sprintf("docker exec -i dind sh -c '%s'", cmd)) 124 | _, _ = command.StdinPipe() 125 | session, err := Start(command, GinkgoWriter, GinkgoWriter) 126 | if err != nil { 127 | Fail("Command failed") 128 | } 129 | return session 130 | } 131 | 132 | // RunPlugin uses "make" to run the plugin in a DIND container 133 | func RunPlugin(additional_args string) { 134 | cmd := exec.Command("make", "run-plugin", fmt.Sprintf("ADDITIONAL_DIND_ARGS=%s", additional_args)) 135 | cmd.Dir = "../../" 136 | output, err := cmd.Output() 137 | if err != nil { 138 | // On failure, print the stdout and stderr, then bail out 139 | fmt.Println(string(output)) 140 | fmt.Println(string(err.(*exec.ExitError).Stderr)) 141 | panic(err) 142 | } 143 | 144 | // Make sure the plugin has started by running a command against it. 145 | // This command should fail (we don't actually want to create a network). 146 | _, _ = exec.Command("bash", "-c", fmt.Sprintf("docker exec -i dind sh -c 'docker network create willfail -d calico'")).Output() 147 | } 148 | -------------------------------------------------------------------------------- /tests/custom_wep_labelling/libnetwork_env_var_test.go: -------------------------------------------------------------------------------- 1 | package custom_wep_labelling 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "time" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | api "github.com/projectcalico/libcalico-go/lib/apis/v3" 13 | mathutils "github.com/projectcalico/libnetwork-plugin/utils/math" 14 | . "github.com/projectcalico/libnetwork-plugin/utils/test" 15 | ) 16 | 17 | var _ = Describe("Running plugin with custom ENV", func() { 18 | Describe("docker run", func() { 19 | It("creates a container on a network with WEP labelling enabled", func() { 20 | RunPlugin("-e CALICO_LIBNETWORK_LABEL_ENDPOINTS=true") 21 | 22 | pool := "test" 23 | subnet := "192.169.1.0/24" 24 | // Since running the plugin starts etcd, the pool needs to be created after. 25 | CreatePool(pool, subnet) 26 | 27 | name := fmt.Sprintf("run%d", rand.Uint32()) 28 | nid := DockerString(fmt.Sprintf("docker network create -d calico --ipam-driver calico-ipam --subnet %s %s ", subnet, pool)) 29 | UpdatePool(pool, subnet, nid) 30 | 31 | // Create a container that will just sit in the background 32 | DockerString(fmt.Sprintf("docker run --net %s -tid --label not=expected --label org.projectcalico.label.foo=bar --label org.projectcalico.label.baz=quux --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 33 | 34 | // Gather information for assertions 35 | dockerEndpoint := GetDockerEndpoint(name, pool) 36 | endpointID := dockerEndpoint.EndpointID 37 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 38 | 39 | // Sleep to allow the plugin to query the started container and update the WEP 40 | // Alternative: query etcd until we hit jackpot or timeout 41 | time.Sleep(time.Second) 42 | 43 | // Check that the endpoint is created in etcd 44 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 45 | endpointJSON := GetEtcd(key) 46 | wep := api.NewWorkloadEndpoint() 47 | json.Unmarshal(endpointJSON, &wep) 48 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 49 | v, ok := wep.ObjectMeta.Labels["foo"] 50 | Expect(ok).Should(Equal(true)) 51 | Expect(v).Should(Equal("bar")) 52 | v, ok = wep.ObjectMeta.Labels["baz"] 53 | Expect(ok).Should(Equal(true)) 54 | Expect(v).Should(Equal("quux")) 55 | _, ok = wep.ObjectMeta.Labels["not"] 56 | Expect(ok).Should(Equal(false)) 57 | 58 | // Check profile 59 | profile := api.NewProfile() 60 | json.Unmarshal(GetEtcd(fmt.Sprintf("/calico/resources/v3/projectcalico.org/profiles/%s", pool)), &profile) 61 | Expect(profile.Name).Should(Equal(pool)) 62 | Expect(len(profile.Labels)).Should(Equal(0)) 63 | //tags deprecated 64 | Expect(profile.Spec.Ingress[0].Action).Should(Equal(api.Allow)) 65 | Expect(profile.Spec.Egress[0].Action).Should(Equal(api.Allow)) 66 | 67 | // Delete container 68 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 69 | }) 70 | }) 71 | 72 | Describe("docker run", func() { 73 | It("creates a container on a network with WEP labelling enabled and profile creation disabled", func() { 74 | // Run the plugin with custom IFPREFIX 75 | RunPlugin("-e CALICO_LIBNETWORK_LABEL_ENDPOINTS=true -e CALICO_LIBNETWORK_CREATE_PROFILES=false") 76 | 77 | pool := "test2" 78 | subnet := "192.169.2.0/24" 79 | // Since running the plugin starts etcd, the pool needs to be created after. 80 | CreatePool(pool, subnet) 81 | 82 | name := fmt.Sprintf("run%d", rand.Uint32()) 83 | nid := DockerString(fmt.Sprintf("docker network create -d calico --ipam-driver calico-ipam --subnet %s %s ", subnet, pool)) 84 | UpdatePool(pool, subnet, nid) 85 | 86 | // Create a container that will just sit in the background 87 | DockerString(fmt.Sprintf("docker run --net %s -tid --label not=expected --label org.projectcalico.label.foo=bar --label org.projectcalico.label.baz=quux --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 88 | 89 | // Gather information for assertions 90 | dockerEndpoint := GetDockerEndpoint(name, pool) 91 | endpointID := dockerEndpoint.EndpointID 92 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 93 | 94 | // Sleep to allow the plugin to query the started container and update the WEP 95 | // Alternative: query etcd until we hit jackpot or timeout 96 | time.Sleep(time.Second) 97 | 98 | // Check that the endpoint is created in etcd 99 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 100 | endpointJSON := GetEtcd(key) 101 | wep := api.NewWorkloadEndpoint() 102 | json.Unmarshal(endpointJSON, &wep) 103 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 104 | v, ok := wep.ObjectMeta.Labels["foo"] 105 | Expect(ok).Should(Equal(true)) 106 | Expect(v).Should(Equal("bar")) 107 | v, ok = wep.ObjectMeta.Labels["baz"] 108 | Expect(ok).Should(Equal(true)) 109 | Expect(v).Should(Equal("quux")) 110 | _, ok = wep.ObjectMeta.Labels["not"] 111 | Expect(ok).Should(Equal(false)) 112 | 113 | // Chech profile not created 114 | notExists := GetNotExists(fmt.Sprintf("/calico/resources/v3/projectcalico.org/profiles/%s", pool)) 115 | Expect(notExists).Should(BeTrue()) 116 | 117 | // Check that the endpoint is created in etcd 118 | // Delete container 119 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 120 | }) 121 | }) 122 | }) 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://semaphoreci.com/api/v1/projects/d51a0276-7939-409e-80ac-aa5df9421fef/510521/badge.svg)](https://semaphoreci.com/calico/libnetwork-plugin) 2 | 3 | # Repository status 4 | 5 | The libnetwork plugin integration is no longer included as a core component in Calico releases, and is not being actively developed or supported by the core Calico team. The latest release of Calico which 6 | includes this plugin is the v2.6.x series of releases. 7 | 8 | This repository relies on community contributions for enhancements and bug fixes, which will be reviewed and merged by the Calico team on a best-effort basis. 9 | 10 | If you would like to become a maintainer of this project, please reach out to the Calico team in the #calico-dev slack channel. 11 | 12 | # Libnetwork plugin for Calico 13 | 14 | This plugin for Docker networking ([libnetwork](https://github.com/docker/libnetwork)) is intended for use with [Project Calico](http://www.projectcalico.org). For Calico's Kubernetes integration, see the [CNI plugin](https://github.com/projectcalico/cni-plugin). 15 | 16 | ## Supported options for confguration 17 | 18 | ### Working with Networks 19 | * When creating a network, the `--subnet` option can be passed to `docker network create`. The subnet must match an existing Calico pool, and any containers created on that network will use an IP address from that Calico Pool. 20 | * Other than `--driver` and `--ipam-driver`, no other options are supported on the `docker network create` command. 21 | 22 | ### Working with Containers 23 | When creating containers, use the `--net` option to connect them to a network previously created with `docker network create` 24 | 25 | * The `--ip` option can be passed to `docker run` to assign a specific IP to a container. 26 | * The `--mac` and `--link-local` options are currently unsupported. 27 | 28 | ## Working with the code 29 | 30 | * Clone the repo (clone it into your GOPATH and make sure you use projectcalico in the path, not your fork name). 31 | * Create the vendor directory (`make vendor`). This uses `glide` in a docker container to create the vendor directory. 32 | * Build it in a container using `make dist/libnetwork-plugin`. The plugin binary will appear in the `dist` directory. 33 | * Running tests can be done in a container using `make test-containerized`. Note: This works on linux, but can require additional steps on Mac. 34 | * Submit PRs through GitHub. Before merging, you'll be asked to squash your commits together, so 1 PR = 1 commit. 35 | * Before submitting your PR, please make sure tests pass and run `make static-checks`. Both these will be done by the CI system too though. 36 | 37 | ## How to Run It During Development 38 | `make run-plugin` 39 | 40 | Running the plugin in a container requires a few specific options 41 | `docker run --rm --net=host --privileged -e CALICO_ETCD_AUTHORITY=$(LOCAL_IP_ENV):2379 -v /run/docker/plugins:/run/docker/plugins -v /var/run/docker.sock:/var/run/docker.sock --name calico-node-libnetwork calico/node-libnetwork /calico` 42 | 43 | - `--net=host` Host network is used since the network changes need to occur in the host namespace 44 | - `privileged` since the plugin creates network interfaces 45 | - `-e CALICO_ETCD_AUTHORITY=a.b.c.d:2379` to allow the plugin to find a backend datastore for storing information 46 | - `-v /run/docker/plugins:/run/docker/plugins` allows the docker daemon to discover the plugin 47 | - `-v /var/run/docker.sock:/var/run/docker.sock` allows the plugin to query the docker daemon 48 | 49 | ## How to Test It 50 | 51 | ### On Linux 52 | 53 | `make test` is all you need. 54 | 55 | ### On OSX/Windows 56 | 57 | On OSX/Windows you can't run Docker natively. To allow the Makefile to write the build libnetwork-plugin to your host's filesystem and to allow the test to access the Docker daemon via the unix socket, the user id and group id of the docker user are needed. For boot2docker the user id is 1000 and group id 100. 58 | 59 | Run `make test` like this: `LOCAL_USER_ID=1000 LOCAL_GROUP_ID=100 make test-containerized` 60 | 61 | 62 | 63 | ## IPv6 Usage 64 | 65 | *Note: IPv4 can't be disabled, IPv6 is enabled in addition to IPv4.* 66 | 67 | Docker IPv6 support must be enabled e.g. 68 | ``` 69 | dockerd --cluster-store=etcd://127.0.0.1:2379 --ipv6 --fixed-cidr-v6="2001:db8:1::/64" 70 | ``` 71 | 72 | ### Start the libnetwork-plugin 73 | 74 | ``` 75 | sudo dist/libnetwork-plugin 76 | ``` 77 | 78 | ### Add an IPv6 address to the host 79 | 80 | ``` 81 | sudo ip addr add fd80:24e2:f998:72d7::1/112 dev eth1 82 | ``` 83 | 84 | ### Start calico/node, without using the calico/node libnetwork-plugin, also pass in the host IPv6 address 85 | 86 | ``` 87 | sudo calicoctl node run --disable-docker-networking --ip6=fd80:24e2:f998:72d7::1 88 | ``` 89 | 90 | ### Create an IPv6 network 91 | 92 | ``` 93 | docker network create --ipv6 -d calico --ipam-driver calico-ipam my_net 94 | ``` 95 | 96 | ### Run containers on the IPv6 network 97 | 98 | ``` 99 | docker run --net my_net --name workload-A -tid busybox 100 | docker run --net my_net --name workload-B -tid busybox 101 | ``` 102 | 103 | 104 | ### Check IPv6 network connectivity 105 | 106 | ``` 107 | docker exec workload-A ping -6 -c 4 workload-B.my_net 108 | docker exec workload-B ping -6 -c 4 workload-A.my_net 109 | ``` 110 | 111 | ### Check IPv4 network connectivity 112 | 113 | ``` 114 | docker exec workload-A ping -4 -c 4 workload-B.my_net 115 | docker exec workload-B ping -4 -c 4 workload-A.my_net 116 | ``` 117 | 118 | 119 | ## Known limitations 120 | The following is a list of known limitations when using the Calico libnetwork 121 | driver: 122 | - It is not possible to add multiple networks to a single container. However, 123 | once a container endpoint is created, it is possible to manually add 124 | additional Calico profiles to that endpoint (effectively adding the 125 | container into another network). 126 | 127 | ## Configuring 128 | 129 | To change the prefix used for the interface in containers that Docker runs, set the `CALICO_LIBNETWORK_IFPREFIX` environment variable. 130 | 131 | * The default value is "cali" 132 | 133 | To enable debug logging set the `CALICO_DEBUG` environment variable. 134 | 135 | The plugin creates a Calico profile resource for the Docker network used (e.g. `docker run --net ...`). This is enabled by default. It can be disabled by setting the environment: `CALICO_LIBNETWORK_CREATE_PROFILES=false`. 136 | 137 | The plugin can copy Docker container labels to the corresponding Calico workloadendpoint. This feature is disabled by default. It can be enabled by setting the environment: `CALICO_LIBNETWORK_LABEL_ENDPOINTS=true`. 138 | 139 | ## Workloadendpoint labelling 140 | If you want to use Calico policies you need labels on the Calico workloadendpoint. The plugin can set labels by copying a subset of the Docker container labels. 141 | 142 | To enable this feature you need to set the environment: `CALICO_LIBNETWORK_LABEL_ENDPOINTS=true`. 143 | 144 | Only container labels starting with `org.projectcalico.label.` are used. This prefix is removed and the remaining key is used a label key in the workloadendpoint. 145 | 146 | Example: `docker run --label org.projectcalico.label.foo=bar --net ...` will create a workloadendpoint with label `foo=bar`. Of course you can use multiple `--label org.projectcalico.label.=` options. 147 | 148 | 149 | *NOTE:* the labels are added to the workloadendpoint using an update, because the container information is not available at the moment the workloadendpoint resource is created. 150 | 151 | ## Troubleshooting 152 | 153 | ### Logging 154 | Logs are sent to STDOUT. If using Docker these can be viewed with the 155 | `docker logs` command. 156 | 157 | ### Monitoring 158 | 159 | Check the plugin health by executing API calls. 160 | 161 | NetworkDriver: 162 | 163 | ``` 164 | # echo -e "GET /NetworkDriver.GetCapabilities HTTP/1.0\r\n\r\n" | nc -U /run/docker/plugins/calico.sock 165 | HTTP/1.0 200 OK 166 | Content-Type: application/vnd.docker.plugins.v1.1+json 167 | Date: Thu, 08 Dec 2016 10:00:41 GMT 168 | Content-Length: 19 169 | 170 | {"Scope":"global"} 171 | ``` 172 | 173 | IpamDriver: 174 | 175 | ``` 176 | # echo -e "GET /IpamDriver.GetCapabilities HTTP/1.0\r\n\r\n" | nc -U /run/docker/plugins/calico-ipam.sock 177 | HTTP/1.0 200 OK 178 | Content-Type: application/vnd.docker.plugins.v1.1+json 179 | Date: Thu, 08 Dec 2016 10:02:51 GMT 180 | Content-Length: 29 181 | 182 | {"RequiresMACAddress":false} 183 | ``` 184 | 185 | [![Analytics](https://calico-ga-beacon.appspot.com/UA-52125893-3/libnetwork-plugin/README.md?pixel)](https://github.com/igrigorik/ga-beacon) 186 | -------------------------------------------------------------------------------- /driver/ipam_driver.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | 8 | "github.com/pkg/errors" 9 | log "github.com/sirupsen/logrus" 10 | 11 | "github.com/docker/go-plugins-helpers/ipam" 12 | "github.com/projectcalico/libcalico-go/lib/clientv3" 13 | calicoipam "github.com/projectcalico/libcalico-go/lib/ipam" 14 | caliconet "github.com/projectcalico/libcalico-go/lib/net" 15 | "github.com/projectcalico/libcalico-go/lib/options" 16 | logutils "github.com/projectcalico/libnetwork-plugin/utils/log" 17 | osutils "github.com/projectcalico/libnetwork-plugin/utils/os" 18 | ) 19 | 20 | type IpamDriver struct { 21 | client clientv3.Interface 22 | 23 | poolIDV4 string 24 | poolIDV6 string 25 | } 26 | 27 | func NewIpamDriver(client clientv3.Interface) ipam.Ipam { 28 | return IpamDriver{ 29 | client: client, 30 | 31 | poolIDV4: PoolIDV4, 32 | poolIDV6: PoolIDV6, 33 | } 34 | } 35 | 36 | func (i IpamDriver) GetCapabilities() (*ipam.CapabilitiesResponse, error) { 37 | resp := ipam.CapabilitiesResponse{} 38 | logutils.JSONMessage("GetCapabilities response", resp) 39 | return &resp, nil 40 | } 41 | 42 | func (i IpamDriver) GetDefaultAddressSpaces() (*ipam.AddressSpacesResponse, error) { 43 | resp := &ipam.AddressSpacesResponse{ 44 | LocalDefaultAddressSpace: CalicoLocalAddressSpace, 45 | GlobalDefaultAddressSpace: CalicoGlobalAddressSpace, 46 | } 47 | logutils.JSONMessage("GetDefaultAddressSpace response", resp) 48 | return resp, nil 49 | } 50 | 51 | func (i IpamDriver) RequestPool(request *ipam.RequestPoolRequest) (*ipam.RequestPoolResponse, error) { 52 | logutils.JSONMessage("RequestPool", request) 53 | 54 | // Calico IPAM does not allow you to request a SubPool. 55 | if request.SubPool != "" { 56 | err := errors.New( 57 | "Calico IPAM does not support sub pool configuration " + 58 | "on 'docker create network'. Calico IP Pools " + 59 | "should be configured first and IP assignment is " + 60 | "from those pre-configured pools.", 61 | ) 62 | log.Errorln(err) 63 | return nil, err 64 | } 65 | 66 | if len(request.Options) != 0 { 67 | err := errors.New("Arbitrary options are not supported") 68 | log.Errorln(err) 69 | return nil, err 70 | } 71 | var poolID string 72 | var pool string 73 | var gateway string 74 | if request.V6 { 75 | // Default the poolID to the fixed value. 76 | poolID = i.poolIDV6 77 | pool = "::/0" 78 | gateway = "::/0" 79 | } else { 80 | // Default the poolID to the fixed value. 81 | poolID = i.poolIDV4 82 | pool = "0.0.0.0/0" 83 | gateway = "0.0.0.0/0" 84 | } 85 | 86 | // If a pool (subnet on the CLI) is specified, it must match one of the 87 | // preconfigured Calico pools. 88 | if request.Pool != "" { 89 | poolsClient := i.client.IPPools() 90 | _, ipNet, err := caliconet.ParseCIDR(request.Pool) 91 | if err != nil { 92 | err := errors.New("Invalid CIDR") 93 | log.Errorln(err) 94 | return nil, err 95 | } 96 | 97 | pools, err := poolsClient.List(context.Background(), options.ListOptions{}) 98 | if err != nil { 99 | log.Errorln(err) 100 | return nil, err 101 | } 102 | 103 | f := false 104 | for _, p := range pools.Items { 105 | if p.Spec.CIDR == ipNet.String() { 106 | f = true 107 | pool = p.Spec.CIDR 108 | poolID = p.Name 109 | break 110 | } 111 | } 112 | 113 | if !f { 114 | err := errors.New("The requested subnet must match the CIDR of a " + 115 | "configured Calico IP Pool.", 116 | ) 117 | log.Errorln(err) 118 | return nil, err 119 | } 120 | } 121 | 122 | // We use static pool ID and CIDR. We don't need to signal the 123 | // The meta data includes a dummy gateway address. This prevents libnetwork 124 | // from requesting a gateway address from the pool since for a Calico 125 | // network our gateway is set to a special IP. 126 | resp := &ipam.RequestPoolResponse{ 127 | PoolID: poolID, 128 | Pool: pool, 129 | Data: map[string]string{"com.docker.network.gateway": gateway}, 130 | } 131 | 132 | logutils.JSONMessage("RequestPool response", resp) 133 | 134 | return resp, nil 135 | } 136 | 137 | func (i IpamDriver) ReleasePool(request *ipam.ReleasePoolRequest) error { 138 | logutils.JSONMessage("ReleasePool", request) 139 | return nil 140 | } 141 | 142 | func (i IpamDriver) RequestAddress(request *ipam.RequestAddressRequest) (*ipam.RequestAddressResponse, error) { 143 | logutils.JSONMessage("RequestAddress", request) 144 | 145 | hostname, err := osutils.GetHostname() 146 | if err != nil { 147 | log.Errorln(err) 148 | return nil, err 149 | } 150 | 151 | // Calico IPAM does not allow you to choose a gateway. 152 | if request.Options["RequestAddressType"] == "com.docker.network.gateway" { 153 | err := errors.New("Calico IPAM does not support specifying a gateway.") 154 | log.Errorln(err) 155 | return nil, err 156 | } 157 | 158 | var IPs []caliconet.IP 159 | 160 | if request.Address == "" { 161 | // No address requested, so auto assign from our pools. 162 | log.Println("Auto assigning IP from Calico pools") 163 | 164 | // If the poolID isn't the fixed one then find the pool to assign from. 165 | // poolV4 defaults to nil to assign from across all pools. 166 | var poolV4 []caliconet.IPNet 167 | 168 | var poolV6 []caliconet.IPNet 169 | var numIPv4, numIPv6, version int 170 | if request.PoolID == PoolIDV4 { 171 | version = 4 172 | numIPv4 = 1 173 | numIPv6 = 0 174 | } else if request.PoolID == PoolIDV6 { 175 | version = 6 176 | numIPv4 = 0 177 | numIPv6 = 1 178 | } else { 179 | poolsClient := i.client.IPPools() 180 | ipPool, err := poolsClient.Get(context.Background(), request.PoolID, options.GetOptions{}) 181 | if err != nil { 182 | err = errors.Wrapf(err, "Invalid Pool - %v", request.PoolID) 183 | log.Errorln(err) 184 | return nil, err 185 | } 186 | 187 | _, ipNet, err := caliconet.ParseCIDR(ipPool.Spec.CIDR) 188 | if err != nil { 189 | err = errors.Wrapf(err, "Invalid CIDR - %v", request.PoolID) 190 | log.Errorln(err) 191 | return nil, err 192 | } 193 | 194 | version = ipNet.Version() 195 | if version == 4 { 196 | poolV4 = []caliconet.IPNet{caliconet.IPNet{IPNet: ipNet.IPNet}} 197 | numIPv4 = 1 198 | log.Debugln("Using specific pool ", poolV4) 199 | } else if version == 6 { 200 | poolV6 = []caliconet.IPNet{caliconet.IPNet{IPNet: ipNet.IPNet}} 201 | numIPv6 = 1 202 | log.Debugln("Using specific pool ", poolV6) 203 | } 204 | } 205 | 206 | // Auto assign an IP address. 207 | // IPv4/v6 pool will be nil if the docker network doesn't have a subnet associated with. 208 | // Otherwise, it will be set to the Calico pool to assign from. 209 | IPsV4, IPsV6, err := i.client.IPAM().AutoAssign( 210 | context.Background(), 211 | calicoipam.AutoAssignArgs{ 212 | Num4: numIPv4, 213 | Num6: numIPv6, 214 | Hostname: hostname, 215 | IPv4Pools: poolV4, 216 | IPv6Pools: poolV6, 217 | }, 218 | ) 219 | 220 | if err != nil { 221 | err = errors.Wrapf(err, "IP assignment error") 222 | log.Errorln(err) 223 | return nil, err 224 | } 225 | IPs = append(IPsV4, IPsV6...) 226 | } else { 227 | // Docker allows the users to specify any address. 228 | // We'll return an error if the address isn't in a Calico pool, but we don't care which pool it's in 229 | // (i.e. it doesn't need to match the subnet from the docker network). 230 | log.Debugln("Reserving a specific address in Calico pools") 231 | ip := net.ParseIP(request.Address) 232 | ipArgs := calicoipam.AssignIPArgs{ 233 | IP: caliconet.IP{IP: ip}, 234 | Hostname: hostname, 235 | } 236 | err := i.client.IPAM().AssignIP(context.Background(), ipArgs) 237 | if err != nil { 238 | err = errors.Wrapf(err, "IP assignment error, data: %+v", ipArgs) 239 | log.Errorln(err) 240 | return nil, err 241 | } 242 | IPs = []caliconet.IP{{IP: ip}} 243 | } 244 | 245 | // We should only have one IP address assigned at this point. 246 | if len(IPs) != 1 { 247 | err := errors.New(fmt.Sprintf("Unexpected number of assigned IP addresses. "+ 248 | "A single address should be assigned. Got %v", IPs)) 249 | log.Errorln(err) 250 | return nil, err 251 | } 252 | 253 | // Return the IP as a CIDR. 254 | var respAddr string 255 | if IPs[0].Version() == 4 { 256 | // IPv4 address 257 | respAddr = fmt.Sprintf("%v/%v", IPs[0], "32") 258 | } else { 259 | // IPv6 address 260 | respAddr = fmt.Sprintf("%v/%v", IPs[0], "128") 261 | } 262 | resp := &ipam.RequestAddressResponse{ 263 | Address: respAddr, 264 | } 265 | 266 | logutils.JSONMessage("RequestAddress response", resp) 267 | 268 | return resp, nil 269 | } 270 | 271 | func (i IpamDriver) ReleaseAddress(request *ipam.ReleaseAddressRequest) error { 272 | logutils.JSONMessage("ReleaseAddress", request) 273 | 274 | ip := caliconet.IP{IP: net.ParseIP(request.Address)} 275 | 276 | // Unassign the address. This handles the address already being unassigned 277 | // in which case it is a no-op. 278 | _, err := i.client.IPAM().ReleaseIPs(context.Background(), []caliconet.IP{ip}) 279 | if err != nil { 280 | err = errors.Wrapf(err, "IP releasing error, ip: %v", ip) 281 | log.Errorln(err) 282 | return err 283 | } 284 | 285 | return nil 286 | } 287 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Both native and cross architecture builds are supported. 3 | # The target architecture is select by setting the ARCH variable. 4 | # When ARCH is undefined it is set to the detected host architecture. 5 | # When ARCH differs from the host architecture a crossbuild will be performed. 6 | ARCHES=$(patsubst Dockerfile.%,%,$(wildcard Dockerfile.*)) 7 | 8 | # BUILDARCH is the host architecture 9 | # ARCH is the target architecture 10 | # we need to keep track of them separately 11 | BUILDARCH ?= $(shell uname -m) 12 | BUILDOS ?= $(shell uname -s | tr A-Z a-z) 13 | 14 | # canonicalized names for host architecture 15 | ifeq ($(BUILDARCH),aarch64) 16 | BUILDARCH=arm64 17 | endif 18 | ifeq ($(BUILDARCH),x86_64) 19 | BUILDARCH=amd64 20 | endif 21 | 22 | # unless otherwise set, I am building for my own architecture, i.e. not cross-compiling 23 | ARCH ?= $(BUILDARCH) 24 | 25 | # canonicalized names for target architecture 26 | ifeq ($(ARCH),aarch64) 27 | override ARCH=arm64 28 | endif 29 | ifeq ($(ARCH),x86_64) 30 | override ARCH=amd64 31 | endif 32 | 33 | GO_BUILD_VER ?= v0.16 34 | # for building, we use the go-build image for the *host* architecture, even if the target is different 35 | # the one for the host should contain all the necessary cross-compilation tools. 36 | # cross-compilation is only supported on amd64. 37 | GO_BUILD_CONTAINER ?= calico/go-build:$(GO_BUILD_VER)-$(BUILDARCH) 38 | 39 | # quay.io not following naming convention for amd64 images. 40 | ifeq ($(BUILDARCH),amd64) 41 | ETCD_IMAGE ?= quay.io/coreos/etcd:v3.2.5 42 | else 43 | ETCD_IMAGE ?= quay.io/coreos/etcd:v3.2.5-$(BUILDARCH) 44 | endif 45 | 46 | BUSYBOX_IMAGE_VERSION ?= latest 47 | BUSYBOX_IMAGE ?= $(BUILDARCH)/busybox:$(BUSYBOX_IMAGE_VERSION) 48 | 49 | DIND_IMAGE_VERSION ?= 18.05.0-dind 50 | DIND_IMAGE ?= $(BUILDARCH)/docker:$(DIND_IMAGE_VERSION) 51 | 52 | # Disable make's implicit rules, which are not useful for golang, and slow down the build 53 | # considerably. 54 | .SUFFIXES: 55 | 56 | SRC_FILES=$(shell find . -type f -name '*.go') 57 | 58 | # These variables can be overridden by setting an environment variable. 59 | LOCAL_IP_ENV?=$(shell ip route get 8.8.8.8 | head -1 | awk '{print $$7}') 60 | 61 | # Can choose different docker versions see list here - https://hub.docker.com/_/docker/ 62 | HOST_CHECKOUT_DIR?=$(CURDIR) 63 | CONTAINER_NAME?=calico/libnetwork-plugin 64 | PLUGIN_LOCATION?=$(CURDIR)/dist/libnetwork-plugin-$(ARCH) 65 | 66 | # To run with non-native docker (e.g. on Windows or OSX) you might need to overide this variable 67 | LOCAL_USER_ID?=$(shell id -u $$USER) 68 | 69 | help: 70 | @echo "Makefile for libnetwork-plugin." 71 | @echo 72 | @echo "For any target, set ARCH= to build for a given target." 73 | @echo "For example, to build for arm64:" 74 | @echo 75 | @echo " make image ARCH=arm64" 76 | @echo 77 | @echo "Builds:" 78 | @echo " make build Run the build in a container for the current docker OS and ARCH." 79 | @echo " make build-all Run the build in a container for all archs" 80 | @echo " make image Build the calico/libnetwork-plugin image." 81 | @echo " make image-all Build the calico/libnetwork-plugin image for all archs." 82 | @echo " make all Builds the image and runs tests." 83 | @echo 84 | @echo "Tests:" 85 | @echo " make test-containerized Run tests in a container" 86 | @echo 87 | @echo "Maintenance:" 88 | @echo " make clean Remove binary files." 89 | @echo " make help Display this help text." 90 | @echo "-----------------------------------------" 91 | @echo "ARCH (target): $(ARCH)" 92 | @echo "BUILDARCH (host): $(BUILDARCH)" 93 | @echo "GO_BUILD_CONTAINER: $(GO_BUILD_CONTAINER)" 94 | @echo "BUSYBOX_IMAGE: $(BUSYBOX_IMAGE)" 95 | @echo "DIND_IMAGE: $(DIND_IMAGE)" 96 | @echo "ETCDIMAGE: $(ETCDIMAGE)" 97 | @echo "-----------------------------------------" 98 | 99 | default: all 100 | all: image test-containerized 101 | 102 | # Use this to populate the vendor directory after checking out the repository. 103 | # To update upstream dependencies, delete the glide.lock file first. 104 | vendor: glide.yaml 105 | # Ensure that the glide cache directory exists. 106 | mkdir -p $(HOME)/.glide 107 | 108 | # To build without Docker just run "glide install -strip-vendor" 109 | docker run --rm \ 110 | -v $(CURDIR):/go/src/github.com/projectcalico/libnetwork-plugin:rw \ 111 | -v $(HOME)/.glide:/home/user/.glide:rw \ 112 | -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ 113 | $(GO_BUILD_CONTAINER) /bin/sh -c ' \ 114 | cd /go/src/github.com/projectcalico/libnetwork-plugin && \ 115 | glide install -strip-vendor' 116 | 117 | install: 118 | CGO_ENABLED=0 go install github.com/projectcalico/libnetwork-plugin 119 | 120 | ############################################################################### 121 | # Building the binary 122 | ############################################################################### 123 | build: dist/libnetwork-plugin-$(ARCH) 124 | build-all: $(addprefix sub-build-,$(ARCHES)) 125 | sub-build-%: 126 | $(MAKE) build ARCH=$* 127 | 128 | # Run the build in a container. Useful for CI 129 | dist/libnetwork-plugin-$(ARCH): vendor 130 | -mkdir -p dist 131 | -mkdir -p .go-pkg-cache 132 | docker run --rm \ 133 | -v $(CURDIR):/go/src/github.com/projectcalico/libnetwork-plugin:ro \ 134 | -v $(CURDIR)/dist:/go/src/github.com/projectcalico/libnetwork-plugin/dist \ 135 | -v $(CURDIR)/.go-pkg-cache:/go/pkg/:rw \ 136 | -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ 137 | -e ARCH=$(ARCH) \ 138 | $(GO_BUILD_CONTAINER) sh -c '\ 139 | cd /go/src/github.com/projectcalico/libnetwork-plugin && \ 140 | make binary' 141 | 142 | binary: $(SRC_FILES) vendor 143 | CGO_ENABLED=0 GOARCH=$(ARCH) go build -v -i -o dist/libnetwork-plugin-$(ARCH) -ldflags "-X main.VERSION=$(shell git describe --tags --dirty) -s -w" main.go 144 | 145 | ############################################################################### 146 | # Building the image 147 | ############################################################################### 148 | $(CONTAINER_NAME): image 149 | image: image.created-$(ARCH) 150 | image-all: $(addprefix sub-image-,$(ARCHES)) 151 | sub-image-%: 152 | $(MAKE) image ARCH=$* 153 | 154 | image.created-$(ARCH): dist/libnetwork-plugin-$(ARCH) 155 | docker build -t $(CONTAINER_NAME):latest-$(ARCH) -f Dockerfile.$(ARCH) . 156 | ifeq ($(ARCH),amd64) 157 | # Need amd64 builds tagged as :latest because Semaphore depends on that 158 | docker tag $(CONTAINER_NAME):latest-$(ARCH) $(CONTAINER_NAME):latest 159 | endif 160 | touch $@ 161 | 162 | # ensure we have a real imagetag 163 | imagetag: 164 | ifndef IMAGETAG 165 | $(error IMAGETAG is undefined - run using make IMAGETAG=X.Y.Z) 166 | endif 167 | 168 | 169 | ############################################################################### 170 | # CI/CD 171 | ############################################################################### 172 | .PHONY: ci cd 173 | ## Builds the code and runs all tests. 174 | ci: image test-containerized 175 | 176 | ## Deploys images to registry 177 | cd: 178 | ifndef CONFIRM 179 | $(error CONFIRM is undefined - run using make CONFIRM=true) 180 | endif 181 | ifndef BRANCH_NAME 182 | $(error BRANCH_NAME is undefined - run using make BRANCH_NAME=var or set an environment variable) 183 | endif 184 | $(MAKE) tag-images push IMAGETAG=${BRANCH_NAME} 185 | $(MAKE) tag-images push IMAGETAG=$(shell git describe --tags --dirty --always --long) 186 | 187 | 188 | ############################################################################### 189 | # tag and push images of any tag 190 | ############################################################################### 191 | 192 | ## push all arches 193 | push-all: imagetag $(addprefix sub-push-,$(ARCHES)) 194 | sub-push-%: 195 | $(MAKE) push ARCH=$* IMAGETAG=$(IMAGETAG) 196 | 197 | ## push one arch 198 | push: imagetag 199 | docker push $(CONTAINER_NAME):$(IMAGETAG)-$(ARCH) 200 | docker push quay.io/$(CONTAINER_NAME):$(IMAGETAG)-$(ARCH) 201 | ifeq ($(ARCH),amd64) 202 | docker push $(CONTAINER_NAME):$(IMAGETAG) 203 | docker push quay.io/$(CONTAINER_NAME):$(IMAGETAG) 204 | endif 205 | 206 | ## tag images of one arch 207 | tag-images: imagetag 208 | docker tag $(CONTAINER_NAME):latest-$(ARCH) $(CONTAINER_NAME):$(IMAGETAG)-$(ARCH) 209 | docker tag $(CONTAINER_NAME):latest-$(ARCH) quay.io/$(CONTAINER_NAME):$(IMAGETAG)-$(ARCH) 210 | ifeq ($(ARCH),amd64) 211 | docker tag $(CONTAINER_NAME):latest-$(ARCH) $(CONTAINER_NAME):$(IMAGETAG) 212 | docker tag $(CONTAINER_NAME):latest-$(ARCH) quay.io/$(CONTAINER_NAME):$(IMAGETAG) 213 | endif 214 | 215 | ## tag images of all archs 216 | tag-images-all: imagetag $(addprefix sub-tag-images-,$(ARCHES)) 217 | sub-tag-images-%: 218 | $(MAKE) tag-images ARCH=$* IMAGETAG=$(IMAGETAG) 219 | 220 | # Perform static checks on the code. The golint checks are allowed to fail, the others must pass. 221 | .PHONY: static-checks 222 | static-checks: vendor 223 | docker run --rm \ 224 | -e LOCAL_USER_ID=$(LOCAL_USER_ID) \ 225 | -v $(CURDIR):/go/src/github.com/projectcalico/libnetwork-plugin \ 226 | $(GO_BUILD_CONTAINER) sh -c '\ 227 | cd /go/src/github.com/projectcalico/libnetwork-plugin && \ 228 | gometalinter --deadline=30s --disable-all --enable=goimports --enable=vet --enable=errcheck --enable=varcheck --enable=unused --enable=dupl $$(glide nv)' 229 | 230 | run-etcd: 231 | @-docker rm -f calico-etcd 232 | docker run --detach \ 233 | --net=host \ 234 | --name calico-etcd $(ETCD_IMAGE) \ 235 | etcd \ 236 | --advertise-client-urls "http://$(LOCAL_IP_ENV):2379,http://127.0.0.1:2379" \ 237 | --listen-client-urls "http://0.0.0.0:2379" 238 | 239 | ############################################################################### 240 | # Release 241 | ############################################################################### 242 | release: clean 243 | ifndef VERSION 244 | $(error VERSION is undefined - run using make release VERSION=vX.Y.Z) 245 | endif 246 | git tag $(VERSION) 247 | $(MAKE) image 248 | $(MAKE) tag-images IMAGETAG=$(VERSION) 249 | # Generate the `latest` images. 250 | $(MAKE) tag-images IMAGETAG=latest 251 | 252 | # Check that the version output appears on a line of its own (the -x option to grep). 253 | # Tests that the "git tag" makes it into the binary. Main point is to catch "-dirty" builds 254 | @echo "Checking if the tag made it into the binary" 255 | docker run --rm $(CONTAINER_NAME):$(VERSION) -v | grep -x $(VERSION) || (echo "Reported version:" `docker run --rm $(CONTAINER_NAME):$(VERSION) -v` "\nExpected version: $(VERSION)" && exit 1) 256 | 257 | @echo "Now push the tag and images. Then create a release on Github and attach the dist/libnetwork-plugin binary" 258 | @echo "git push origin $(VERSION)" 259 | 260 | # Push images. 261 | $(MAKE) push IMAGETAG=$(VERSION) ARCH=$(ARCH) 262 | 263 | clean: 264 | rm -rf dist image.created-* 265 | -docker rmi $(CONTAINER_NAME) 266 | 267 | run-plugin: run-etcd dist/libnetwork-plugin-$(ARCH) 268 | -docker rm -f dind 269 | docker run -tid -h test --name dind --privileged $(ADDITIONAL_DIND_ARGS) \ 270 | -e ETCD_ENDPOINTS=http://$(LOCAL_IP_ENV):2379 \ 271 | -p 5375:2375 \ 272 | -v $(PLUGIN_LOCATION):/libnetwork-plugin \ 273 | $(DIND_IMAGE) --cluster-store=etcd://$(LOCAL_IP_ENV):2379 274 | # View the logs by running 'docker exec dind cat plugin.log' 275 | docker exec -tid --privileged dind sh -c 'sysctl -w net.ipv6.conf.default.disable_ipv6=0' 276 | docker exec -tid --privileged dind sh -c '/libnetwork-plugin 2>>/plugin.log' 277 | # To speak to this docker: 278 | # export DOCKER_HOST=localhost:5375 279 | 280 | .PHONY: test 281 | # Run the unit tests. 282 | test: 283 | CGO_ENABLED=0 ginkgo -v tests/* 284 | 285 | test-containerized: dist/libnetwork-plugin-$(ARCH) 286 | ifeq ($(BUILDARCH),$(ARCH)) 287 | docker run -t --rm --net=host \ 288 | -v $(CURDIR):/go/src/github.com/projectcalico/libnetwork-plugin \ 289 | -v /var/run/docker.sock:/var/run/docker.sock \ 290 | -e PLUGIN_LOCATION=$(CURDIR)/dist/libnetwork-plugin-$(ARCH) \ 291 | -e LOCAL_USER_ID=0 \ 292 | -e ARCH=$(ARCH) \ 293 | -e BUSYBOX_IMAGE=$(BUSYBOX_IMAGE) \ 294 | $(GO_BUILD_CONTAINER) sh -c '\ 295 | cd /go/src/github.com/projectcalico/libnetwork-plugin && \ 296 | make test' 297 | else 298 | @echo Test-containerized is not supported when cross building. 299 | endif 300 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: eb9e4c379ebcbbd831d0a2ddfef1d67649da854f86215045261963be6d66e4f8 2 | updated: 2018-06-21T09:55:01.317369+08:00 3 | imports: 4 | - name: cloud.google.com/go 5 | version: 3b1ae45394a234c385be014e9a488f2bb6eef821 6 | subpackages: 7 | - compute/metadata 8 | - internal 9 | - name: github.com/Azure/go-autorest 10 | version: 58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d 11 | subpackages: 12 | - autorest 13 | - autorest/adal 14 | - autorest/azure 15 | - autorest/date 16 | - name: github.com/codegangsta/cli 17 | version: cfb38830724cc34fedffe9a2a29fb54fa9169cd1 18 | - name: github.com/coreos/etcd 19 | version: 33245c6b5b49130ca99280408fadfab01aac0e48 20 | subpackages: 21 | - auth/authpb 22 | - client 23 | - clientv3 24 | - etcdserver/api/v3rpc/rpctypes 25 | - etcdserver/etcdserverpb 26 | - mvcc/mvccpb 27 | - pkg/pathutil 28 | - pkg/srv 29 | - pkg/tlsutil 30 | - pkg/transport 31 | - pkg/types 32 | - version 33 | - name: github.com/coreos/go-semver 34 | version: 8ab6407b697782a06568d4b7f1db25550ec2e4c6 35 | subpackages: 36 | - semver 37 | - name: github.com/coreos/go-systemd 38 | version: d2196463941895ee908e13531a23a39feb9e1243 39 | subpackages: 40 | - activation 41 | - daemon 42 | - journal 43 | - util 44 | - name: github.com/davecgh/go-spew 45 | version: 782f4967f2dc4564575ca782fe2d04090b5faca8 46 | subpackages: 47 | - spew 48 | - name: github.com/dgrijalva/jwt-go 49 | version: d2709f9f1f31ebcda9651b03077758c1f3a0018c 50 | - name: github.com/docker/distribution 51 | version: f4118485915abb8b163442717326597908eee6aa 52 | subpackages: 53 | - digestset 54 | - reference 55 | - name: github.com/docker/docker 56 | version: e2593239d949eee454935daea7a5fe025477322f 57 | subpackages: 58 | - api 59 | - api/types 60 | - api/types/blkiodev 61 | - api/types/container 62 | - api/types/events 63 | - api/types/filters 64 | - api/types/image 65 | - api/types/mount 66 | - api/types/network 67 | - api/types/registry 68 | - api/types/strslice 69 | - api/types/swarm 70 | - api/types/swarm/runtime 71 | - api/types/time 72 | - api/types/versions 73 | - api/types/volume 74 | - client 75 | - name: github.com/docker/go-connections 76 | version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d 77 | subpackages: 78 | - nat 79 | - sockets 80 | - tlsconfig 81 | - name: github.com/docker/go-plugins-helpers 82 | version: bd8c600f0cdd76c7a57ff6aa86bd2b423868c688 83 | subpackages: 84 | - ipam 85 | - network 86 | - sdk 87 | - name: github.com/docker/go-units 88 | version: 47565b4f722fb6ceae66b95f853feed578a4a51c 89 | - name: github.com/emicklei/go-restful 90 | version: ff4f55a206334ef123e4f79bbf348980da81ca46 91 | subpackages: 92 | - log 93 | - name: github.com/emicklei/go-restful-swagger12 94 | version: dcef7f55730566d41eae5db10e7d6981829720f6 95 | - name: github.com/ghodss/yaml 96 | version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee 97 | - name: github.com/go-openapi/jsonpointer 98 | version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 99 | - name: github.com/go-openapi/jsonreference 100 | version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 101 | - name: github.com/go-openapi/spec 102 | version: 6aced65f8501fe1217321abf0749d354824ba2ff 103 | - name: github.com/go-openapi/swag 104 | version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 105 | - name: github.com/gogo/protobuf 106 | version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 107 | subpackages: 108 | - gogoproto 109 | - proto 110 | - protoc-gen-gogo/descriptor 111 | - sortkeys 112 | - name: github.com/golang/glog 113 | version: 44145f04b68cf362d9c4df2182967c2275eaefed 114 | - name: github.com/golang/protobuf 115 | version: 4bd1920723d7b7c925de087aa32e2187708897f7 116 | subpackages: 117 | - proto 118 | - ptypes 119 | - ptypes/any 120 | - ptypes/duration 121 | - ptypes/timestamp 122 | - name: github.com/google/btree 123 | version: 925471ac9e2131377a91e1595defec898166fe49 124 | - name: github.com/google/gofuzz 125 | version: 44d81051d367757e1c7c6a5a86423ece9afcf63c 126 | - name: github.com/googleapis/gnostic 127 | version: 68f4ded48ba9414dab2ae69b3f0d69971da73aa5 128 | subpackages: 129 | - OpenAPIv2 130 | - compiler 131 | - extensions 132 | - name: github.com/gophercloud/gophercloud 133 | version: 2bf16b94fdd9b01557c4d076e567fe5cbbe5a961 134 | subpackages: 135 | - openstack 136 | - openstack/identity/v2/tenants 137 | - openstack/identity/v2/tokens 138 | - openstack/identity/v3/tokens 139 | - openstack/utils 140 | - pagination 141 | - name: github.com/gregjones/httpcache 142 | version: 787624de3eb7bd915c329cba748687a3b22666a6 143 | subpackages: 144 | - diskcache 145 | - name: github.com/hashicorp/golang-lru 146 | version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 147 | subpackages: 148 | - simplelru 149 | - name: github.com/howeyc/gopass 150 | version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 151 | - name: github.com/imdario/mergo 152 | version: 6633656539c1639d9d78127b7d47c622b5d7b6dc 153 | - name: github.com/json-iterator/go 154 | version: 36b14963da70d11297d313183d7e6388c8510e1e 155 | - name: github.com/juju/ratelimit 156 | version: 5b9ff866471762aa2ab2dced63c9fb6f53921342 157 | - name: github.com/kelseyhightower/envconfig 158 | version: f611eb38b3875cc3bd991ca91c51d06446afa14c 159 | - name: github.com/mailru/easyjson 160 | version: d5b7844b561a7bc640052f1b935f7b800330d7e0 161 | subpackages: 162 | - buffer 163 | - jlexer 164 | - jwriter 165 | - name: github.com/Microsoft/go-winio 166 | version: 78439966b38d69bf38227fbf57ac8a6fee70f69a 167 | - name: github.com/onsi/ginkgo 168 | version: fa5fabab2a1bfbd924faf4c067d07ae414e2aedf 169 | subpackages: 170 | - config 171 | - internal/codelocation 172 | - internal/containernode 173 | - internal/failer 174 | - internal/leafnodes 175 | - internal/remote 176 | - internal/spec 177 | - internal/spec_iterator 178 | - internal/specrunner 179 | - internal/suite 180 | - internal/testingtproxy 181 | - internal/writer 182 | - reporters 183 | - reporters/stenographer 184 | - reporters/stenographer/support/go-colorable 185 | - reporters/stenographer/support/go-isatty 186 | - types 187 | - name: github.com/onsi/gomega 188 | version: c1fb6682134d162f37c13f42e7157653a7de7d2b 189 | subpackages: 190 | - format 191 | - gbytes 192 | - gexec 193 | - internal/assertion 194 | - internal/asyncassertion 195 | - internal/oraclematcher 196 | - internal/testingtsupport 197 | - matchers 198 | - matchers/support/goraph/bipartitegraph 199 | - matchers/support/goraph/edge 200 | - matchers/support/goraph/node 201 | - matchers/support/goraph/util 202 | - types 203 | - name: github.com/opencontainers/go-digest 204 | version: 279bed98673dd5bef374d3b6e4b09e2af76183bf 205 | - name: github.com/opencontainers/image-spec 206 | version: e562b04403929d582d449ae5386ff79dd7961a11 207 | subpackages: 208 | - specs-go 209 | - specs-go/v1 210 | - name: github.com/pborman/uuid 211 | version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 212 | - name: github.com/peterbourgon/diskv 213 | version: 5f041e8faa004a95c88a202771f4cc3e991971e6 214 | - name: github.com/pkg/errors 215 | version: 8842a6e0cc595d1cc9d931f6c875883967280e32 216 | - name: github.com/projectcalico/go-json 217 | version: 6219dc7339ba20ee4c57df0a8baac62317d19cb1 218 | subpackages: 219 | - json 220 | - name: github.com/projectcalico/go-yaml 221 | version: 955bc3e451ef0c9df8b9113bf2e341139cdafab2 222 | - name: github.com/projectcalico/go-yaml-wrapper 223 | version: 598e54215bee41a19677faa4f0c32acd2a87eb56 224 | - name: github.com/projectcalico/libcalico-go 225 | version: efdf8fede805a0669c5233bace020ae57f2e6c5b 226 | subpackages: 227 | - lib/apiconfig 228 | - lib/apis/v1 229 | - lib/apis/v1/unversioned 230 | - lib/apis/v3 231 | - lib/backend 232 | - lib/backend/api 233 | - lib/backend/etcdv3 234 | - lib/backend/k8s 235 | - lib/backend/k8s/conversion 236 | - lib/backend/k8s/resources 237 | - lib/backend/model 238 | - lib/clientv3 239 | - lib/errors 240 | - lib/hash 241 | - lib/ipam 242 | - lib/ipip 243 | - lib/names 244 | - lib/namespace 245 | - lib/net 246 | - lib/numorstring 247 | - lib/options 248 | - lib/scope 249 | - lib/selector 250 | - lib/selector/parser 251 | - lib/selector/tokenizer 252 | - lib/set 253 | - lib/validator/v3 254 | - lib/watch 255 | - name: github.com/PuerkitoBio/purell 256 | version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 257 | - name: github.com/PuerkitoBio/urlesc 258 | version: 5bd2802263f21d8788851d5305584c82a5c75d7e 259 | - name: github.com/satori/go.uuid 260 | version: f58768cc1a7a7e77a3bd49e98cdd21419399b6a3 261 | - name: github.com/sirupsen/logrus 262 | version: c155da19408a8799da419ed3eeb0cb5db0ad5dbc 263 | - name: github.com/spf13/pflag 264 | version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 265 | - name: github.com/ugorji/go 266 | version: bdcc60b419d136a85cdf2e7cbcac34b3f1cd6e57 267 | subpackages: 268 | - codec 269 | - name: github.com/vishvananda/netlink 270 | version: 54ad9e3a4cbbe15a353df918e0801711ff5eaf17 271 | subpackages: 272 | - nl 273 | - name: github.com/vishvananda/netns 274 | version: be1fbeda19366dea804f00efff2dd73a1642fdcc 275 | - name: golang.org/x/crypto 276 | version: 1351f936d976c60a0a48d728281922cf63eafb8d 277 | subpackages: 278 | - ssh/terminal 279 | - name: golang.org/x/net 280 | version: 1c05540f6879653db88113bc4a2b70aec4bd491f 281 | subpackages: 282 | - context 283 | - context/ctxhttp 284 | - html 285 | - html/atom 286 | - html/charset 287 | - http2 288 | - http2/hpack 289 | - idna 290 | - internal/timeseries 291 | - lex/httplex 292 | - proxy 293 | - trace 294 | - name: golang.org/x/oauth2 295 | version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4 296 | subpackages: 297 | - google 298 | - internal 299 | - jws 300 | - jwt 301 | - name: golang.org/x/sys 302 | version: ebfc5b4631820b793c9010c87fd8fef0f39eb082 303 | subpackages: 304 | - unix 305 | - windows 306 | - name: golang.org/x/text 307 | version: b19bf474d317b857955b12035d2c5acb57ce8b01 308 | subpackages: 309 | - cases 310 | - encoding 311 | - encoding/charmap 312 | - encoding/htmlindex 313 | - encoding/internal 314 | - encoding/internal/identifier 315 | - encoding/japanese 316 | - encoding/korean 317 | - encoding/simplifiedchinese 318 | - encoding/traditionalchinese 319 | - encoding/unicode 320 | - internal 321 | - internal/tag 322 | - internal/utf8internal 323 | - language 324 | - runes 325 | - secure/bidirule 326 | - secure/precis 327 | - transform 328 | - unicode/bidi 329 | - unicode/norm 330 | - width 331 | - name: google.golang.org/appengine 332 | version: 4f7eeb5305a4ba1966344836ba4af9996b7b4e05 333 | subpackages: 334 | - internal 335 | - internal/app_identity 336 | - internal/base 337 | - internal/datastore 338 | - internal/log 339 | - internal/modules 340 | - internal/remote_api 341 | - internal/urlfetch 342 | - urlfetch 343 | - name: google.golang.org/genproto 344 | version: 09f6ed296fc66555a25fe4ce95173148778dfa85 345 | subpackages: 346 | - googleapis/rpc/status 347 | - name: google.golang.org/grpc 348 | version: 5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e 349 | subpackages: 350 | - balancer 351 | - codes 352 | - connectivity 353 | - credentials 354 | - grpclb/grpc_lb_v1/messages 355 | - grpclog 356 | - health 357 | - health/grpc_health_v1 358 | - internal 359 | - keepalive 360 | - metadata 361 | - naming 362 | - peer 363 | - resolver 364 | - stats 365 | - status 366 | - tap 367 | - transport 368 | - name: gopkg.in/go-playground/validator.v8 369 | version: 5f57d2222ad794d0dffb07e664ea05e2ee07d60c 370 | - name: gopkg.in/inf.v0 371 | version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 372 | - name: gopkg.in/yaml.v2 373 | version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 374 | - name: k8s.io/api 375 | version: a315a049e7a93e5455f7fefce1ba136d85054687 376 | subpackages: 377 | - admissionregistration/v1alpha1 378 | - apps/v1beta1 379 | - apps/v1beta2 380 | - authentication/v1 381 | - authentication/v1beta1 382 | - authorization/v1 383 | - authorization/v1beta1 384 | - autoscaling/v1 385 | - autoscaling/v2beta1 386 | - batch/v1 387 | - batch/v1beta1 388 | - batch/v2alpha1 389 | - certificates/v1beta1 390 | - core/v1 391 | - extensions/v1beta1 392 | - networking/v1 393 | - policy/v1beta1 394 | - rbac/v1 395 | - rbac/v1alpha1 396 | - rbac/v1beta1 397 | - scheduling/v1alpha1 398 | - settings/v1alpha1 399 | - storage/v1 400 | - storage/v1beta1 401 | - name: k8s.io/apimachinery 402 | version: 40eaf68ee1889b1da1c528b1a075ecfe94e66837 403 | subpackages: 404 | - pkg/api/equality 405 | - pkg/api/errors 406 | - pkg/api/meta 407 | - pkg/api/resource 408 | - pkg/apis/meta/internalversion 409 | - pkg/apis/meta/v1 410 | - pkg/apis/meta/v1/unstructured 411 | - pkg/apis/meta/v1alpha1 412 | - pkg/conversion 413 | - pkg/conversion/queryparams 414 | - pkg/conversion/unstructured 415 | - pkg/fields 416 | - pkg/labels 417 | - pkg/runtime 418 | - pkg/runtime/schema 419 | - pkg/runtime/serializer 420 | - pkg/runtime/serializer/json 421 | - pkg/runtime/serializer/protobuf 422 | - pkg/runtime/serializer/recognizer 423 | - pkg/runtime/serializer/streaming 424 | - pkg/runtime/serializer/versioning 425 | - pkg/selection 426 | - pkg/types 427 | - pkg/util/cache 428 | - pkg/util/clock 429 | - pkg/util/diff 430 | - pkg/util/errors 431 | - pkg/util/framer 432 | - pkg/util/intstr 433 | - pkg/util/json 434 | - pkg/util/net 435 | - pkg/util/runtime 436 | - pkg/util/sets 437 | - pkg/util/uuid 438 | - pkg/util/validation 439 | - pkg/util/validation/field 440 | - pkg/util/wait 441 | - pkg/util/yaml 442 | - pkg/version 443 | - pkg/watch 444 | - third_party/forked/golang/reflect 445 | - name: k8s.io/client-go 446 | version: 82aa063804cf055e16e8911250f888bc216e8b61 447 | subpackages: 448 | - discovery 449 | - kubernetes 450 | - kubernetes/scheme 451 | - kubernetes/typed/admissionregistration/v1alpha1 452 | - kubernetes/typed/apps/v1beta1 453 | - kubernetes/typed/apps/v1beta2 454 | - kubernetes/typed/authentication/v1 455 | - kubernetes/typed/authentication/v1beta1 456 | - kubernetes/typed/authorization/v1 457 | - kubernetes/typed/authorization/v1beta1 458 | - kubernetes/typed/autoscaling/v1 459 | - kubernetes/typed/autoscaling/v2beta1 460 | - kubernetes/typed/batch/v1 461 | - kubernetes/typed/batch/v1beta1 462 | - kubernetes/typed/batch/v2alpha1 463 | - kubernetes/typed/certificates/v1beta1 464 | - kubernetes/typed/core/v1 465 | - kubernetes/typed/extensions/v1beta1 466 | - kubernetes/typed/networking/v1 467 | - kubernetes/typed/policy/v1beta1 468 | - kubernetes/typed/rbac/v1 469 | - kubernetes/typed/rbac/v1alpha1 470 | - kubernetes/typed/rbac/v1beta1 471 | - kubernetes/typed/scheduling/v1alpha1 472 | - kubernetes/typed/settings/v1alpha1 473 | - kubernetes/typed/storage/v1 474 | - kubernetes/typed/storage/v1beta1 475 | - pkg/version 476 | - plugin/pkg/client/auth 477 | - plugin/pkg/client/auth/azure 478 | - plugin/pkg/client/auth/gcp 479 | - plugin/pkg/client/auth/oidc 480 | - plugin/pkg/client/auth/openstack 481 | - rest 482 | - rest/watch 483 | - third_party/forked/golang/template 484 | - tools/auth 485 | - tools/cache 486 | - tools/clientcmd 487 | - tools/clientcmd/api 488 | - tools/clientcmd/api/latest 489 | - tools/clientcmd/api/v1 490 | - tools/metrics 491 | - tools/pager 492 | - tools/reference 493 | - transport 494 | - util/cert 495 | - util/flowcontrol 496 | - util/homedir 497 | - util/integer 498 | - util/jsonpath 499 | - name: k8s.io/kube-openapi 500 | version: 0c329704159e3b051aafac400b15baacf2a94a04 501 | subpackages: 502 | - pkg/common 503 | testImports: [] 504 | -------------------------------------------------------------------------------- /tests/default_environment/libnetwork_test.go: -------------------------------------------------------------------------------- 1 | package default_environment 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "regexp" 9 | "time" 10 | 11 | . "github.com/onsi/ginkgo" 12 | . "github.com/onsi/gomega" 13 | . "github.com/onsi/gomega/gbytes" 14 | . "github.com/onsi/gomega/gexec" 15 | api "github.com/projectcalico/libcalico-go/lib/apis/v3" 16 | mathutils "github.com/projectcalico/libnetwork-plugin/utils/math" 17 | . "github.com/projectcalico/libnetwork-plugin/utils/test" 18 | ) 19 | 20 | var _ = Describe("Libnetwork Tests", func() { 21 | BeforeEach(func() { 22 | WipeEtcd() 23 | CreatePool("poolv4", "192.169.0.0/16") 24 | CreatePool("poolv6", "2001:db8::/32") 25 | }) 26 | 27 | // Run the plugin just once for all tests in this file. 28 | RunPlugin("") 29 | 30 | // Test the docker network commands - no need to test inspect or ls 31 | Describe("docker network create", func() { 32 | // TODO There is no coverage of the following two options. I can't see how to make them get passed to the plugin. 33 | // --label value 34 | // --aux-address 35 | Context("checking failure cases", func() { 36 | It("needs both network and IPAM drivers to be calico", func() { 37 | session := DockerSession("docker network create $RANDOM -d calico") 38 | Eventually(session).Should(Exit(1)) 39 | Eventually(session.Err).Should(Say("Error response from daemon: NetworkDriver.CreateNetwork: Non-Calico IPAM driver is used")) 40 | }) 41 | It("doesn't allow a gateway to be specified", func() { 42 | session := DockerSession("docker network create $RANDOM -d calico --ipam-driver calico-ipam --subnet=192.169.0.0/16 --gateway 192.169.0.1") 43 | Eventually(session).Should(Exit(1)) 44 | expectedError := regexp.QuoteMeta("Error response from daemon: failed to allocate gateway (192.169.0.1): IpamDriver.RequestAddress: Calico IPAM does not support specifying a gateway.") 45 | Eventually(session.Err).Should(Say(expectedError)) 46 | }) 47 | It("requires the subnet to match the calico pool", func() { 48 | // I'm trying for a /24 but calico is configured with a /16 so it will fail. 49 | session := DockerSession("docker network create $RANDOM -d calico --ipam-driver calico-ipam --subnet=192.169.0.0/24") 50 | Eventually(session).Should(Exit(1)) 51 | Eventually(session.Err).Should(Say("Error response from daemon: IpamDriver.RequestPool: The requested subnet must match the CIDR of a configured Calico IP Pool.")) 52 | }) 53 | It("rejects --internal being used", func() { 54 | session := DockerSession("docker network create $RANDOM --internal -d calico --ipam-driver calico-ipam") 55 | Eventually(session).Should(Exit(1)) 56 | Eventually(session.Err).Should(Say("Error response from daemon: NetworkDriver.CreateNetwork: Calico driver does not support the flag --internal.")) 57 | }) 58 | It("rejects --ip-range being used", func() { 59 | session := DockerSession("docker network create $RANDOM --ip-range 192.169.1.0/24 --subnet=192.169.0.0/16 -d calico --ipam-driver calico-ipam") 60 | Eventually(session).Should(Exit(1)) 61 | Eventually(session.Err).Should(Say("Error response from daemon: IpamDriver.RequestPool: Calico IPAM does not support sub pool configuration on 'docker create network'. Calico IP Pools should be configured first and IP assignment is from those pre-configured pools.")) 62 | }) 63 | It("rejects --ipam-opt being used", func() { 64 | session := DockerSession("docker network create $RANDOM --ipam-opt REJECT -d calico --ipam-driver calico-ipam") 65 | Eventually(session).Should(Exit(1)) 66 | Eventually(session.Err).Should(Say("Error response from daemon: IpamDriver.RequestPool: Arbitrary options are not supported")) 67 | }) 68 | It("rejects --opt being used", func() { 69 | session := DockerSession("docker network create $RANDOM --opt REJECT -d calico --ipam-driver calico-ipam") 70 | Eventually(session).Should(Exit(1)) 71 | Eventually(session.Err).Should(Say("NetworkDriver.CreateNetwork: Calico driver does not support the flag REJECT.")) 72 | }) 73 | It("rejects multiple --opt being used", func() { 74 | session := DockerSession("docker network create $RANDOM --opt REJECT --opt REJECT2 -d calico --ipam-driver calico-ipam") 75 | Eventually(session).Should(Exit(1)) 76 | Eventually(session.Err).Should(Say("NetworkDriver.CreateNetwork: Calico driver does not support the flags REJECT, REJECT2.")) 77 | }) 78 | }) 79 | Context("checking success cases", func() { 80 | It("creates a network", func() { 81 | session := DockerSession("docker network create success$RANDOM -d calico --ipam-driver calico-ipam") 82 | Eventually(session).Should(Exit(0)) 83 | // There are no observable side effects. We could verify that nothing changed under /calico in etcd? 84 | // I would like to verify that the correct pools were returned to Docker but it doesn't let us observe that information - https://github.com/docker/docker/issues/28567 85 | }) 86 | It("creates a network with a subnet", func() { 87 | session := DockerSession("docker network create success$RANDOM --subnet 192.169.0.0/16 -d calico --ipam-driver calico-ipam") 88 | Eventually(session).Should(Exit(0)) 89 | }) 90 | It("creates a network with IPv6", func() { 91 | session := DockerSession("docker network create --ipv6 success$RANDOM -d calico --ipam-driver calico-ipam") 92 | Eventually(session).Should(Exit(0)) 93 | }) 94 | PIt("creates a network with IPv6 from a specific subnet", func() { 95 | }) 96 | 97 | //TODO - allow multiple networks from the same pool 98 | }) 99 | }) 100 | Describe("docker network rm", func() { 101 | // No options and no side effects 102 | }) 103 | Describe("docker network connect", func() { 104 | // TODO 105 | // Usage: docker network connect [OPTIONS] NETWORK CONTAINER 106 | // 107 | //Connect a container to a network 108 | // 109 | //Options: 110 | // --alias value Add network-scoped alias for the container (default []) 111 | // --help Print usage 112 | // --ip string IP Address 113 | // --ip6 string IPv6 Address 114 | // --link value Add link to another container (default []) 115 | // --link-local-ip value Add a link-local address for the container (default []) 116 | // 117 | }) 118 | Describe("docker network disconnect", func() { 119 | // TODO - no significant options but we should observe the veth going and the endpoint removed from etcd 120 | }) 121 | 122 | //docker create/run 123 | // create - doesn't have any network interactions until the container is started 124 | // run - Run a container, check the following 125 | // - etcd contains correct info 126 | // - host namespace contains the right veth with the right info 127 | // - container - container the right routes and interface 128 | // run can have the following variations - 129 | // --mac-address 130 | // --link-local-ip 131 | // --ip and --ip6 132 | Describe("docker run", func() { 133 | var name string 134 | var pool string 135 | 136 | BeforeEach(func() { 137 | name = fmt.Sprintf("run%d", rand.Uint32()) 138 | pool = fmt.Sprintf("testp%d", rand.Uint32()) 139 | subnet := "192.170.0.0/16" 140 | CreatePool(pool, subnet) 141 | nid := DockerString(fmt.Sprintf("docker network create --driver calico --ipam-driver calico-ipam --subnet %s %s ", subnet, pool)) 142 | UpdatePool(pool, subnet, nid) 143 | }) 144 | 145 | AfterEach(func() { 146 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 147 | DockerString(fmt.Sprintf("docker network rm %s", pool)) 148 | }) 149 | 150 | It("creates a container on a network and checks all assertions", func() { 151 | // Create a container that will just sit in the background 152 | DockerString(fmt.Sprintf("docker run --net %s -tid --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 153 | 154 | // Gather information for assertions 155 | dockerEndpoint := GetDockerEndpoint(name, pool) 156 | ip := dockerEndpoint.IPAddress 157 | mac := dockerEndpoint.MacAddress 158 | endpointID := dockerEndpoint.EndpointID 159 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 160 | 161 | // Check that the endpoint is created in etcd 162 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 163 | endpointJSON := GetEtcd(key) 164 | wep := api.NewWorkloadEndpoint() 165 | json.Unmarshal(endpointJSON, &wep) 166 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 167 | 168 | // Check profile 169 | profile := api.NewProfile() 170 | json.Unmarshal(GetEtcd(fmt.Sprintf("/calico/resources/v3/projectcalico.org/profiles/%s", pool)), &profile) 171 | Expect(profile.Name).Should(Equal(pool)) 172 | Expect(len(profile.Labels)).Should(Equal(0)) 173 | //tags deprecated 174 | Expect(profile.Spec.Ingress[0].Action).Should(Equal(api.Allow)) 175 | Expect(profile.Spec.Egress[0].Action).Should(Equal(api.Allow)) 176 | 177 | // Check the interface exists on the Host - it has an autoassigned 178 | // mac and ip, so don't check anything! 179 | DockerString(fmt.Sprintf("ip addr show %s", vethName)) 180 | 181 | // Make sure the interface in the container exists and has the assigned ip and mac 182 | containerNICString := DockerString(fmt.Sprintf("docker exec -i %s ip addr", name)) 183 | Expect(containerNICString).Should(ContainSubstring(ip)) 184 | Expect(containerNICString).Should(ContainSubstring(mac)) 185 | 186 | // Make sure the container has the routes we expect 187 | routes := DockerString(fmt.Sprintf("docker exec -i %s ip route", name)) 188 | Expect(routes).Should(Equal("default via 169.254.1.1 dev cali0 \n169.254.1.1 dev cali0 scope link")) 189 | }) 190 | 191 | It("creates a container with specific MAC", func() { 192 | // Create a container that will just sit in the background 193 | chosen_mac := "00:22:33:44:55:66" 194 | DockerString(fmt.Sprintf("docker run --mac-address %s --net %s -tid --name %s %s", chosen_mac, pool, name, os.Getenv("BUSYBOX_IMAGE"))) 195 | 196 | // Gather information for assertions 197 | dockerEndpoint := GetDockerEndpoint(name, pool) 198 | ip := dockerEndpoint.IPAddress 199 | mac := dockerEndpoint.MacAddress 200 | endpointID := dockerEndpoint.EndpointID 201 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 202 | 203 | // Make sure the discovered MAC is what we asked for 204 | Expect(mac).Should(Equal(chosen_mac)) 205 | 206 | // Check that the endpoint is created in etcd 207 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 208 | endpointJSON := GetEtcd(key) 209 | wep := api.NewWorkloadEndpoint() 210 | json.Unmarshal(endpointJSON, &wep) 211 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 212 | 213 | // Check the interface exists on the Host - it has an autoassigned 214 | // mac and ip, so don't check anything! 215 | DockerString(fmt.Sprintf("ip addr show %s", vethName)) 216 | 217 | // Make sure the interface in the container exists and has the assigned ip and mac 218 | containerNICString := DockerString(fmt.Sprintf("docker exec -i %s ip addr", name)) 219 | Expect(containerNICString).Should(ContainSubstring(ip)) 220 | Expect(containerNICString).Should(ContainSubstring(mac)) 221 | 222 | // Make sure the container has the routes we expect 223 | routes := DockerString(fmt.Sprintf("docker exec -i %s ip route", name)) 224 | Expect(routes).Should(Equal("default via 169.254.1.1 dev cali0 \n169.254.1.1 dev cali0 scope link")) 225 | }) 226 | 227 | PIt("creates a container with specific link local address", func() { // https://github.com/docker/docker/issues/28606 228 | // Create a container that will just sit in the background 229 | DockerString(fmt.Sprintf("docker run --link-local-ip 169.254.0.50 --net %s -tid --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 230 | 231 | // Delete container 232 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 233 | }) 234 | 235 | // TODO Ensure that a specific IP isn't possible without a user specified subnet 236 | // TODO allocate specific IPs from specific pools - see test cases in https://github.com/projectcalico/libnetwork-plugin/pull/101/files/c8c0386a41a569fbef33fae545ad97fa061470ed#diff-3bca4eb4bf01d8f50e7babc5c90236cc 237 | // TODO auto alloc IPs from a specific pool - see https://github.com/projectcalico/libnetwork-plugin/pull/101/files/c8c0386a41a569fbef33fae545ad97fa061470ed#diff-2667baf0dbc5ac5027aa29690f306535 238 | It("creates a container with specific IP", func() { 239 | // Create a container that will just sit in the background 240 | chosen_ip := "192.170.50.51" 241 | DockerString(fmt.Sprintf("docker run --ip %s --net %s -tid --name %s %s", chosen_ip, pool, name, os.Getenv("BUSYBOX_IMAGE"))) 242 | 243 | // Gather information for assertions 244 | dockerEndpoint := GetDockerEndpoint(name, pool) 245 | ip := dockerEndpoint.IPAddress 246 | mac := dockerEndpoint.MacAddress 247 | endpointID := dockerEndpoint.EndpointID 248 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 249 | 250 | Expect(ip).Should(Equal(chosen_ip)) 251 | 252 | // Check that the endpoint is created in etcd 253 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 254 | endpointJSON := GetEtcd(key) 255 | wep := api.NewWorkloadEndpoint() 256 | json.Unmarshal(endpointJSON, &wep) 257 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 258 | 259 | // Check the interface exists on the Host - it has an autoassigned 260 | // mac and ip, so don't check anything! 261 | DockerString(fmt.Sprintf("ip addr show %s", vethName)) 262 | 263 | // Make sure the interface in the container exists and has the assigned ip and mac 264 | containerNICString := DockerString(fmt.Sprintf("docker exec -i %s ip addr", name)) 265 | Expect(containerNICString).Should(ContainSubstring(ip)) 266 | Expect(containerNICString).Should(ContainSubstring(mac)) 267 | 268 | // Make sure the container has the routes we expect 269 | routes := DockerString(fmt.Sprintf("docker exec -i %s ip route", name)) 270 | Expect(routes).Should(Equal("default via 169.254.1.1 dev cali0 \n169.254.1.1 dev cali0 scope link")) 271 | }) 272 | 273 | It("creates a container with labels, but do not expect those in endpoint", func() { 274 | // Create a container that will just sit in the background 275 | DockerString(fmt.Sprintf("docker run --net %s -tid --label not=expected --label org.projectcalico.label.foo=bar --label org.projectcalico.label.baz=quux --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 276 | 277 | // Gather information for assertions 278 | dockerEndpoint := GetDockerEndpoint(name, pool) 279 | endpointID := dockerEndpoint.EndpointID 280 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 281 | 282 | // Sleep to allow the plugin to query the started container and update the WEP 283 | // Alternative: query etcd until we hit jackpot or timeout 284 | time.Sleep(time.Second) 285 | 286 | // Check that the endpoint is created in etcd 287 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 288 | endpointJSON := GetEtcd(key) 289 | wep := api.NewWorkloadEndpoint() 290 | json.Unmarshal(endpointJSON, &wep) 291 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 292 | _, ok := wep.ObjectMeta.Labels["foo"] 293 | Expect(ok).Should(Equal(false)) 294 | _, ok = wep.ObjectMeta.Labels["baz"] 295 | Expect(ok).Should(Equal(false)) 296 | _, ok = wep.ObjectMeta.Labels["not"] 297 | Expect(ok).Should(Equal(false)) 298 | }) 299 | }) 300 | 301 | Describe("docker run ipv6", func() { 302 | var name string 303 | var pool string 304 | 305 | BeforeEach(func() { 306 | name = fmt.Sprintf("run%d", rand.Uint32()) 307 | pool = fmt.Sprintf("test6p%d", rand.Uint32()) 308 | subnet := "fdb7:472d:ff0b::/48" 309 | CreatePool(pool, subnet) 310 | nid := DockerString(fmt.Sprintf("docker network create --driver calico --ipam-driver calico-ipam --subnet %s --ipv6 %s ", subnet, pool)) 311 | UpdatePool(pool, subnet, nid) 312 | }) 313 | 314 | AfterEach(func() { 315 | DockerString(fmt.Sprintf("docker rm -f %s", name)) 316 | DockerString(fmt.Sprintf("docker network rm %s", pool)) 317 | }) 318 | 319 | It("creates a container on a network and checks all assertions", func() { 320 | // Create a container that will just sit in the background 321 | DockerString(fmt.Sprintf("docker run --net %s -tid --name %s %s", pool, name, os.Getenv("BUSYBOX_IMAGE"))) 322 | 323 | // Gather information for assertions 324 | dockerEndpoint := GetDockerEndpoint(name, pool) 325 | ipv6 := dockerEndpoint.GlobalIPv6Address 326 | mac := dockerEndpoint.MacAddress 327 | endpointID := dockerEndpoint.EndpointID 328 | vethName := "cali" + endpointID[:mathutils.MinInt(11, len(endpointID))] 329 | 330 | // Check that the endpoint is created in etcd 331 | key := fmt.Sprintf("/calico/resources/v3/projectcalico.org/workloadendpoints/libnetwork/test-libnetwork-libnetwork-%s", endpointID) 332 | endpointJSON := GetEtcd(key) 333 | wep := api.NewWorkloadEndpoint() 334 | json.Unmarshal(endpointJSON, &wep) 335 | Expect(wep.Spec.InterfaceName).Should(Equal(vethName)) 336 | 337 | // Check the interface exists on the Host - it has an autoassigned 338 | // mac and ip, so don't check anything! 339 | DockerString(fmt.Sprintf("ip addr show %s", vethName)) 340 | DockerString(fmt.Sprintf("ip -6 addr show %s", vethName)) 341 | 342 | // Make sure the interface in the container exists and has the assigned ip and mac 343 | containerNICString := DockerString(fmt.Sprintf("docker exec -i %s ip addr", name)) 344 | Expect(containerNICString).Should(ContainSubstring(ipv6)) 345 | Expect(containerNICString).Should(ContainSubstring(mac)) 346 | 347 | // Make sure the container has the routes we expect 348 | routes := DockerString(fmt.Sprintf("docker exec -i %s ip route", name)) 349 | Expect(routes).Should(Equal("default via 169.254.1.1 dev cali0 \n169.254.1.1 dev cali0 scope link")) 350 | routes6 := DockerString(fmt.Sprintf("docker exec -i %s ip -6 route", name)) 351 | Expect(routes6).Should(MatchRegexp("default via fe80::.* dev cali0 metric 1024")) 352 | 353 | }) 354 | }) 355 | //docker stop/rm - stop and rm are the same as far as the plugin is concerned 356 | // TODO - check that the endpoint is removed from etcd and that the veth is removed 357 | }) 358 | -------------------------------------------------------------------------------- /driver/network_driver.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "os" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | dockerClient "github.com/docker/docker/client" 14 | "github.com/docker/go-plugins-helpers/network" 15 | "github.com/pkg/errors" 16 | api "github.com/projectcalico/libcalico-go/lib/apis/v3" 17 | "github.com/projectcalico/libcalico-go/lib/clientv3" 18 | caliconet "github.com/projectcalico/libcalico-go/lib/net" 19 | log "github.com/sirupsen/logrus" 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | 22 | dockertypes "github.com/docker/docker/api/types" 23 | libcalicoErrors "github.com/projectcalico/libcalico-go/lib/errors" 24 | wepname "github.com/projectcalico/libcalico-go/lib/names" 25 | "github.com/projectcalico/libcalico-go/lib/options" 26 | logutils "github.com/projectcalico/libnetwork-plugin/utils/log" 27 | mathutils "github.com/projectcalico/libnetwork-plugin/utils/math" 28 | "github.com/projectcalico/libnetwork-plugin/utils/netns" 29 | osutils "github.com/projectcalico/libnetwork-plugin/utils/os" 30 | netlink "github.com/vishvananda/netlink" 31 | ) 32 | 33 | const ( 34 | DOCKER_LABEL_PREFIX = "org.projectcalico.label." 35 | LABEL_POLL_TIMEOUT_ENVKEY = "CALICO_LIBNETWORK_LABEL_POLL_TIMEOUT" 36 | CREATE_PROFILES_ENVKEY = "CALICO_LIBNETWORK_CREATE_PROFILES" 37 | LABEL_ENDPOINTS_ENVKEY = "CALICO_LIBNETWORK_LABEL_ENDPOINTS" 38 | VETH_MTU_ENVKEY = "CALICO_LIBNETWORK_VETH_MTU" 39 | ) 40 | 41 | type NetworkDriver struct { 42 | client clientv3.Interface 43 | containerName string 44 | orchestratorID string 45 | 46 | ifPrefix string 47 | 48 | DummyIPV4Nexthop string 49 | 50 | vethMTU uint16 51 | 52 | labelPollTimeout time.Duration 53 | 54 | createProfiles bool 55 | labelEndpoints bool 56 | } 57 | 58 | func NewNetworkDriver(client clientv3.Interface) network.Driver { 59 | driver := NetworkDriver{ 60 | client: client, 61 | 62 | // Orchestrator and container IDs used in our endpoint identification. These 63 | // are fixed for libnetwork. Unique endpoint identification is provided by 64 | // hostname and endpoint ID. 65 | containerName: "libnetwork", 66 | orchestratorID: "libnetwork", 67 | 68 | ifPrefix: IFPrefix, 69 | DummyIPV4Nexthop: "169.254.1.1", 70 | 71 | // default: enabled, disable by setting env key to false (case insensitive) 72 | createProfiles: !strings.EqualFold(os.Getenv(CREATE_PROFILES_ENVKEY), "false"), 73 | 74 | // default: disabled, enable by setting env key to true (case insensitive) 75 | labelEndpoints: strings.EqualFold(os.Getenv(LABEL_ENDPOINTS_ENVKEY), "true"), 76 | } 77 | 78 | // Check if MTU environment variable is given, parse into uint16 79 | // and override the default in the NetworkDriver. 80 | if mtuStr, ok := os.LookupEnv(VETH_MTU_ENVKEY); ok { 81 | mtu, err := strconv.ParseUint(mtuStr, 10, 16) 82 | if err != nil { 83 | log.Fatalf("Failed to parse %v '%v' into uint16: %v", 84 | VETH_MTU_ENVKEY, mtuStr, err) 85 | } 86 | 87 | driver.vethMTU = uint16(mtu) 88 | 89 | log.WithField("mtu", mtu).Info("Parsed veth MTU") 90 | } 91 | 92 | if !driver.createProfiles { 93 | log.Info("Feature disabled: no Calico profiles will be created per network") 94 | } 95 | if driver.labelEndpoints { 96 | log.Info("Feature enabled: Calico workloadendpoints will be labelled with Docker labels") 97 | driver.labelPollTimeout = getLabelPollTimeout() 98 | } 99 | return driver 100 | } 101 | 102 | func (d NetworkDriver) GetCapabilities() (*network.CapabilitiesResponse, error) { 103 | resp := network.CapabilitiesResponse{Scope: "global"} 104 | logutils.JSONMessage("GetCapabilities response", resp) 105 | return &resp, nil 106 | } 107 | 108 | // AllocateNetwork is used for swarm-mode support in remote plugins, which 109 | // Calico's libnetwork-plugin doesn't currently support. 110 | func (d NetworkDriver) AllocateNetwork(request *network.AllocateNetworkRequest) (*network.AllocateNetworkResponse, error) { 111 | var resp network.AllocateNetworkResponse 112 | logutils.JSONMessage("AllocateNetwork response", resp) 113 | return &resp, nil 114 | } 115 | 116 | // FreeNetwork is used for swarm-mode support in remote plugins, which 117 | // Calico's libnetwork-plugin doesn't currently support. 118 | func (d NetworkDriver) FreeNetwork(request *network.FreeNetworkRequest) error { 119 | logutils.JSONMessage("FreeNetwork request", request) 120 | return nil 121 | } 122 | 123 | func (d NetworkDriver) CreateNetwork(request *network.CreateNetworkRequest) error { 124 | logutils.JSONMessage("CreateNetwork", request) 125 | knownOpts := map[string]bool{"com.docker.network.enable_ipv6": true} 126 | // Reject all options (--internal, --enable_ipv6, etc) 127 | for k, v := range request.Options { 128 | skip := false 129 | for known, _ := range knownOpts { 130 | if k == known { 131 | skip = true 132 | break 133 | } 134 | } 135 | if skip { 136 | continue 137 | } 138 | optionSet := false 139 | flagName := k 140 | flagValue := fmt.Sprintf("%s", v) 141 | multipleFlags := false 142 | switch v := v.(type) { 143 | case bool: 144 | // if v == true then optionSet = true 145 | optionSet = v 146 | flagName = "--" + strings.TrimPrefix(k, "com.docker.network.") 147 | flagValue = "" 148 | break 149 | case map[string]interface{}: 150 | optionSet = len(v) != 0 151 | flagName = "" 152 | numFlags := 0 153 | // Sort flags for consistent error reporting 154 | flags := []string{} 155 | for flag := range v { 156 | flags = append(flags, flag) 157 | } 158 | sort.Strings(flags) 159 | 160 | for _, flag := range flags { 161 | flagName = flagName + flag + ", " 162 | numFlags++ 163 | } 164 | multipleFlags = numFlags > 1 165 | flagName = strings.TrimSuffix(flagName, ", ") 166 | flagValue = "" 167 | break 168 | default: 169 | // for unknown case let optionSet = true 170 | optionSet = true 171 | } 172 | if optionSet { 173 | if flagValue != "" { 174 | flagValue = " (" + flagValue + ")" 175 | } 176 | f := "flag" 177 | if multipleFlags { 178 | f = "flags" 179 | } 180 | err := errors.New("Calico driver does not support the " + f + " " + flagName + flagValue + ".") 181 | log.Errorln(err) 182 | return err 183 | } 184 | } 185 | 186 | ps := []string{} 187 | for _, ipData := range request.IPv4Data { 188 | // Older version of Docker have a bug where they don't provide the correct AddressSpace 189 | // so we can't check for calico IPAM using our known address space. 190 | // The Docker issue, https://github.com/projectcalico/libnetwork-plugin/issues/77, 191 | // was fixed sometime between 1.11.2 and 1.12.3. 192 | // Also the pool might not have a fixed values if --subnet was passed 193 | // So the only safe thing is to check for our special gateway value 194 | if ipData.Gateway != "0.0.0.0/0" { 195 | err := errors.New("Non-Calico IPAM driver is used. Note: Docker before 1.12.3 is unsupported") 196 | log.Errorln(err) 197 | return err 198 | } 199 | ps = append(ps, ipData.Pool) 200 | } 201 | 202 | for _, ipData := range request.IPv6Data { 203 | // Don't support older versions of Docker which have a bug where the correct AddressSpace isn't provided 204 | if ipData.AddressSpace != CalicoGlobalAddressSpace { 205 | err := errors.New("Non-Calico IPAM driver is used") 206 | log.Errorln(err) 207 | return err 208 | } 209 | ps = append(ps, ipData.Pool) 210 | } 211 | 212 | logutils.JSONMessage("CreateNetwork response", map[string]string{}) 213 | return d.populatePoolAnnotation(ps, request.NetworkID) 214 | } 215 | 216 | func (d NetworkDriver) populatePoolAnnotation(pools []string, networkID string) error { 217 | ctx := context.Background() 218 | poolClient := d.client.IPPools() 219 | ipPools, err := poolClient.List(ctx, options.ListOptions{}) 220 | if err != nil { 221 | log.Errorln(err) 222 | return err 223 | } 224 | for _, ipPool := range ipPools.Items { 225 | for _, cidr := range pools { 226 | if ipPool.Spec.CIDR == cidr { 227 | ann := ipPool.GetAnnotations() 228 | if ann == nil { 229 | ann = map[string]string{} 230 | } 231 | ann[DOCKER_LABEL_PREFIX+"network.ID"] = networkID 232 | ipPool.SetAnnotations(ann) 233 | _, err = poolClient.Update(ctx, &ipPool, options.SetOptions{}) 234 | if err != nil { 235 | log.Errorln(err) 236 | return err 237 | } 238 | } 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func (d NetworkDriver) DeleteNetwork(request *network.DeleteNetworkRequest) error { 245 | logutils.JSONMessage("DeleteNetwork", request) 246 | return nil 247 | } 248 | 249 | func (d NetworkDriver) CreateEndpoint(request *network.CreateEndpointRequest) (*network.CreateEndpointResponse, error) { 250 | logutils.JSONMessage("CreateEndpoint", request) 251 | 252 | ctx := context.Background() 253 | hostname, err := osutils.GetHostname() 254 | if err != nil { 255 | err = errors.Wrap(err, "Hostname fetching error") 256 | log.Errorln(err) 257 | return nil, err 258 | } 259 | 260 | log.Debugf("Creating endpoint %v\n", request.EndpointID) 261 | if request.Interface.Address == "" && request.Interface.AddressIPv6 == "" { 262 | err := errors.New("No address assigned for endpoint") 263 | log.Errorln(err) 264 | return nil, err 265 | } 266 | 267 | var addresses []caliconet.IPNet 268 | if request.Interface.Address != "" { 269 | // Parse the address this function was passed. Ignore the subnet - Calico always uses /32 (for IPv4) 270 | ip4, _, err := net.ParseCIDR(request.Interface.Address) 271 | log.Debugf("Parsed IP %v from (%v) \n", ip4, request.Interface.Address) 272 | 273 | if err != nil { 274 | err = errors.Wrapf(err, "Parsing %v as CIDR failed", request.Interface.Address) 275 | log.Errorln(err) 276 | return nil, err 277 | } 278 | 279 | addresses = append(addresses, caliconet.IPNet{IPNet: net.IPNet{IP: ip4, Mask: net.CIDRMask(32, 32)}}) 280 | } 281 | 282 | if request.Interface.AddressIPv6 != "" { 283 | // Parse the address this function was passed. 284 | ip6, ipnet, err := net.ParseCIDR(request.Interface.AddressIPv6) 285 | log.Debugf("Parsed IP %v from (%v) \n", ip6, request.Interface.AddressIPv6) 286 | if err != nil { 287 | err = errors.Wrapf(err, "Parsing %v as CIDR failed", request.Interface.AddressIPv6) 288 | log.Errorln(err) 289 | return nil, err 290 | } 291 | addresses = append(addresses, caliconet.IPNet{IPNet: *ipnet}) 292 | } 293 | 294 | wepName, err := d.generateEndpointName(hostname, request.EndpointID) 295 | if err != nil { 296 | log.Errorln(err) 297 | return nil, err 298 | } 299 | 300 | endpoint := api.NewWorkloadEndpoint() 301 | endpoint.Name = wepName 302 | endpoint.ObjectMeta.Namespace = d.orchestratorID 303 | endpoint.Spec.Endpoint = request.EndpointID 304 | endpoint.Spec.Node = hostname 305 | endpoint.Spec.Orchestrator = d.orchestratorID 306 | endpoint.Spec.Workload = d.containerName 307 | endpoint.Spec.InterfaceName = "cali" + request.EndpointID[:mathutils.MinInt(11, len(request.EndpointID))] 308 | var mac net.HardwareAddr 309 | if request.Interface.MacAddress != "" { 310 | if mac, err = net.ParseMAC(request.Interface.MacAddress); err != nil { 311 | err = errors.Wrap(err, "Error parsing MAC address") 312 | log.Errorln(err) 313 | return nil, err 314 | } 315 | } 316 | endpoint.Spec.MAC = mac.String() 317 | for _, addr := range addresses { 318 | endpoint.Spec.IPNetworks = append(endpoint.Spec.IPNetworks, addr.String()) 319 | } 320 | 321 | pools, err := d.client.IPPools().List(ctx, options.ListOptions{}) 322 | if err != nil { 323 | err = errors.Wrapf(err, "Network %v gather error", request.NetworkID) 324 | log.Errorln(err) 325 | return nil, err 326 | } 327 | 328 | f := false 329 | networkName := "" 330 | for _, p := range pools.Items { 331 | if nid, ok := p.Annotations[DOCKER_LABEL_PREFIX+"network.ID"]; ok && nid == request.NetworkID { 332 | f = true 333 | networkName = p.ObjectMeta.Name 334 | log.Debugf("Find ippool : %v\n", p.Name) 335 | break 336 | } 337 | } 338 | if !f { 339 | err := errors.New("The requested subnet must match the CIDR of a configured Calico IP Pool.") 340 | log.Errorln(err) 341 | return nil, err 342 | } 343 | 344 | if d.createProfiles { 345 | // Now that we know the network name, set it on the endpoint. 346 | endpoint.Spec.Profiles = append(endpoint.Spec.Profiles, networkName) 347 | 348 | // Check if exists 349 | if _, err := d.client.Profiles().Get(ctx, networkName, options.GetOptions{}); err != nil { 350 | // If a profile for the network name doesn't exist then it needs to be created. 351 | // We always attempt to create the profile and rely on the datastore to reject 352 | // the request if the profile already exists. 353 | profile := &api.Profile{ 354 | ObjectMeta: metav1.ObjectMeta{ 355 | Name: networkName, 356 | }, 357 | Spec: api.ProfileSpec{ 358 | Egress: []api.Rule{{Action: "Allow"}}, 359 | Ingress: []api.Rule{{Action: "Allow", 360 | Source: api.EntityRule{ 361 | Selector: fmt.Sprintf("has(%s)", networkName), 362 | }}}, 363 | }, 364 | } 365 | if _, err := d.client.Profiles().Create(ctx, profile, options.SetOptions{}); err != nil { 366 | if _, ok := err.(libcalicoErrors.ErrorResourceAlreadyExists); !ok { 367 | log.Errorln(err) 368 | return nil, err 369 | } 370 | } 371 | } 372 | } 373 | 374 | // Create the endpoint last to minimize side-effects if something goes wrong. 375 | endpoint, err = d.client.WorkloadEndpoints().Create(ctx, endpoint, options.SetOptions{}) 376 | if err != nil { 377 | err = errors.Wrapf(err, "Workload endpoints creation error, data: %+v", endpoint) 378 | log.Errorln(err) 379 | return nil, err 380 | } 381 | 382 | log.Debugf("Workload created, data: %+v\n", endpoint) 383 | 384 | if d.labelEndpoints { 385 | go d.populateWorkloadEndpointWithLabels(request, endpoint) 386 | } 387 | 388 | response := &network.CreateEndpointResponse{Interface: &network.EndpointInterface{}} 389 | logutils.JSONMessage("CreateEndpoint response", response) 390 | return response, nil 391 | } 392 | 393 | func (d NetworkDriver) DeleteEndpoint(request *network.DeleteEndpointRequest) error { 394 | logutils.JSONMessage("DeleteEndpoint", request) 395 | log.Debugf("Removing endpoint %v\n", request.EndpointID) 396 | 397 | hostname, err := osutils.GetHostname() 398 | if err != nil { 399 | err = errors.Wrap(err, "Hostname fetching error") 400 | log.Errorln(err) 401 | return err 402 | } 403 | 404 | wepName, err := d.generateEndpointName(hostname, request.EndpointID) 405 | if err != nil { 406 | log.Errorln(err) 407 | return err 408 | } 409 | 410 | if _, err = d.client.WorkloadEndpoints().Delete( 411 | context.Background(), 412 | d.orchestratorID, 413 | wepName, 414 | options.DeleteOptions{}); err != nil { 415 | err = errors.Wrapf(err, "Endpoint %v removal error", request.EndpointID) 416 | log.Errorln(err) 417 | return err 418 | } 419 | 420 | logutils.JSONMessage("DeleteEndpoint response JSON={}", map[string]string{}) 421 | 422 | return err 423 | } 424 | 425 | func (d NetworkDriver) EndpointInfo(request *network.InfoRequest) (*network.InfoResponse, error) { 426 | logutils.JSONMessage("EndpointInfo", request) 427 | return nil, nil 428 | } 429 | 430 | func (d NetworkDriver) Join(request *network.JoinRequest) (*network.JoinResponse, error) { 431 | logutils.JSONMessage("Join", request) 432 | 433 | ctx := context.Background() 434 | // 1) Set up a veth pair 435 | // The one end will stay in the host network namespace - named caliXXXXX 436 | // The other end is given a temporary name. It's moved into the final network namespace by libnetwork itself. 437 | var err error 438 | prefix := request.EndpointID[:mathutils.MinInt(11, len(request.EndpointID))] 439 | hostInterfaceName := "cali" + prefix 440 | tempInterfaceName := "temp" + prefix 441 | 442 | if err = netns.CreateVeth(hostInterfaceName, tempInterfaceName, d.vethMTU); err != nil { 443 | err = errors.Wrapf( 444 | err, "Veth creation error, hostInterfaceName=%v, tempInterfaceName=%v, vethMTU=%v", 445 | hostInterfaceName, tempInterfaceName, d.vethMTU) 446 | log.Errorln(err) 447 | return nil, err 448 | } 449 | 450 | // 2) update workloads 451 | hostname, err := os.Hostname() 452 | if err != nil { 453 | log.Errorln(err) 454 | return nil, err 455 | } 456 | weps := d.client.WorkloadEndpoints() 457 | wepName, err := d.generateEndpointName(hostname, request.EndpointID) 458 | if err != nil { 459 | log.Errorln(err) 460 | return nil, err 461 | } 462 | wep, err := weps.Get(ctx, d.orchestratorID, wepName, options.GetOptions{}) 463 | if err != nil { 464 | log.Errorln(err) 465 | return nil, err 466 | } 467 | tempNIC, err := netlink.LinkByName(tempInterfaceName) 468 | if err != nil { 469 | log.Errorln(err) 470 | return nil, err 471 | } 472 | wep.Spec.MAC = tempNIC.Attrs().HardwareAddr.String() 473 | _, err = weps.Update(ctx, wep, options.SetOptions{}) 474 | if err != nil { 475 | log.Errorln(err) 476 | return nil, err 477 | } 478 | 479 | resp := &network.JoinResponse{ 480 | InterfaceName: network.InterfaceName{ 481 | SrcName: tempInterfaceName, 482 | DstPrefix: IFPrefix, 483 | }, 484 | } 485 | 486 | // One of the network gateway addresses indicate that we are using 487 | // Calico IPAM driver. In this case we setup routes using the gateways 488 | // configured on the endpoint (which will be our host IPs). 489 | log.Debugln("Using Calico IPAM driver, configure gateway and static routes to the host") 490 | 491 | resp.Gateway = d.DummyIPV4Nexthop 492 | resp.StaticRoutes = append(resp.StaticRoutes, &network.StaticRoute{ 493 | Destination: d.DummyIPV4Nexthop + "/32", 494 | RouteType: 1, // 1 = CONNECTED 495 | NextHop: "", 496 | }) 497 | 498 | linkLocalAddr := netns.GetLinkLocalAddr(hostInterfaceName) 499 | if linkLocalAddr == nil { 500 | log.Warnf("No IPv6 link local address for %s", hostInterfaceName) 501 | } else { 502 | resp.GatewayIPv6 = fmt.Sprintf("%s", linkLocalAddr) 503 | nextHopIPv6 := fmt.Sprintf("%s/128", linkLocalAddr) 504 | resp.StaticRoutes = append(resp.StaticRoutes, &network.StaticRoute{ 505 | Destination: nextHopIPv6, 506 | RouteType: 1, // 1 = CONNECTED 507 | NextHop: "", 508 | }) 509 | } 510 | 511 | logutils.JSONMessage("Join response", resp) 512 | 513 | return resp, nil 514 | } 515 | 516 | func (d NetworkDriver) Leave(request *network.LeaveRequest) error { 517 | logutils.JSONMessage("Leave response", request) 518 | caliName := "cali" + request.EndpointID[:mathutils.MinInt(11, len(request.EndpointID))] 519 | err := netns.RemoveVeth(caliName) 520 | return err 521 | } 522 | 523 | func (d NetworkDriver) DiscoverNew(request *network.DiscoveryNotification) error { 524 | logutils.JSONMessage("DiscoverNew", request) 525 | log.Debugln("DiscoverNew response JSON={}") 526 | return nil 527 | } 528 | 529 | func (d NetworkDriver) DiscoverDelete(request *network.DiscoveryNotification) error { 530 | logutils.JSONMessage("DiscoverDelete", request) 531 | log.Debugln("DiscoverDelete response JSON={}") 532 | return nil 533 | } 534 | 535 | func (d NetworkDriver) ProgramExternalConnectivity(*network.ProgramExternalConnectivityRequest) error { 536 | return nil 537 | } 538 | 539 | func (d NetworkDriver) RevokeExternalConnectivity(*network.RevokeExternalConnectivityRequest) error { 540 | return nil 541 | } 542 | 543 | // Try to get the container's labels and update the WorkloadEndpoint with them 544 | // Since we do not get container info in the libnetwork API methods we need to 545 | // get them ourselves. 546 | // 547 | // This is how: 548 | // - first we try to get a list of containers attached to the custom network 549 | // - if there is a container with our endpointID, we try to inspect that container 550 | // - any labels for that container prefixed by our 'magic' prefix are added to 551 | // our WorkloadEndpoint resource 552 | // 553 | // Above may take 1 or more retries, because Docker has to update the 554 | // container list in the NetworkInspect and make the Container available 555 | // for inspecting. 556 | func (d NetworkDriver) populateWorkloadEndpointWithLabels(request *network.CreateEndpointRequest, endpoint *api.WorkloadEndpoint) { 557 | ctx := context.Background() 558 | 559 | networkID := request.NetworkID 560 | endpointID := request.EndpointID 561 | 562 | retrySleep := time.Duration(100 * time.Millisecond) 563 | 564 | start := time.Now() 565 | deadline := start.Add(d.labelPollTimeout) 566 | 567 | os.Setenv("DOCKER_API_VERSION", "1.25") 568 | dockerCli, err := dockerClient.NewEnvClient() 569 | if err != nil { 570 | err = errors.Wrap(err, "Error while attempting to instantiate docker client from env") 571 | log.Errorln(err) 572 | return 573 | } 574 | defer dockerCli.Close() 575 | 576 | RETRY_NETWORK_INSPECT: 577 | if time.Now().After(deadline) { 578 | log.Errorf("Getting labels for workloadEndpoint timed out in network inspect loop. Took %s", time.Since(start)) 579 | return 580 | } 581 | 582 | // inspect our custom network 583 | networkData, err := dockerCli.NetworkInspect(ctx, networkID, dockertypes.NetworkInspectOptions{}) 584 | if err != nil { 585 | err = errors.Wrapf(err, "Error inspecting network %s - retrying (T=%s)", networkID, time.Since(start)) 586 | log.Warningln(err) 587 | // was unable to inspect network, let's retry 588 | time.Sleep(retrySleep) 589 | goto RETRY_NETWORK_INSPECT 590 | } 591 | logutils.JSONMessage("NetworkInspect response", networkData) 592 | 593 | // try to find the container for which we created an endpoint 594 | containerID := "" 595 | for id, containerInNetwork := range networkData.Containers { 596 | if containerInNetwork.EndpointID == endpointID { 597 | // skip funky identified containers - observed with dind 1.13.0-rc3, gone in -rc5 598 | // { 599 | // "Containers": { 600 | // "ep-736ccfa7cd61ced93b67f7465ddb79633ea6d1f718a8ca7d9d19226f5d3521b0": { 601 | // "Name": "run1466946597", 602 | // "EndpointID": "736ccfa7cd61ced93b67f7465ddb79633ea6d1f718a8ca7d9d19226f5d3521b0", 603 | // ... 604 | // } 605 | // } 606 | // } 607 | if strings.HasPrefix(id, "ep-") { 608 | log.Debugf("Skipping container entry with matching endpointID, but illegal id: %s", id) 609 | } else { 610 | containerID = id 611 | log.Debugf("Container %s found in NetworkInspect result (T=%s)", containerID, time.Since(start)) 612 | break 613 | } 614 | } 615 | } 616 | 617 | if containerID == "" { 618 | // cause: Docker has not yet processed the libnetwork CreateEndpoint response. 619 | log.Warnf("Container not found in NetworkInspect result - retrying (T=%s)", time.Since(start)) 620 | // let's retry 621 | time.Sleep(retrySleep) 622 | goto RETRY_NETWORK_INSPECT 623 | } 624 | 625 | RETRY_CONTAINER_INSPECT: 626 | if time.Now().After(deadline) { 627 | log.Errorf("Getting labels for workloadEndpoint timed out in container inspect loop. Took %s", time.Since(start)) 628 | return 629 | } 630 | 631 | containerInfo, err := dockerCli.ContainerInspect(ctx, containerID) 632 | if err != nil { 633 | err = errors.Wrapf(err, "Error inspecting container %s for labels - retrying (T=%s)", containerID, time.Since(start)) 634 | log.Warningln(err) 635 | // was unable to inspect container, let's retry 636 | time.Sleep(100 * time.Millisecond) 637 | goto RETRY_CONTAINER_INSPECT 638 | } 639 | 640 | log.Debugf("Container inspected, processing labels now (T=%s)", time.Since(start)) 641 | 642 | RETRY_UPDATE_ENDPOINT: 643 | if time.Now().After(deadline) { 644 | log.Errorf("Updating endpoint timed out. Took %s", time.Since(start)) 645 | return 646 | } 647 | 648 | // make sure we have a labels map in the workloadEndpoint 649 | if endpoint.ObjectMeta.Labels == nil { 650 | endpoint.ObjectMeta.Labels = map[string]string{} 651 | } 652 | 653 | labelsFound := 0 654 | for label, labelValue := range containerInfo.Config.Labels { 655 | if !strings.HasPrefix(label, DOCKER_LABEL_PREFIX) { 656 | continue 657 | } 658 | labelsFound++ 659 | labelClean := strings.TrimPrefix(label, DOCKER_LABEL_PREFIX) 660 | endpoint.ObjectMeta.Labels[labelClean] = labelValue 661 | log.Debugf("Found label for WorkloadEndpoint: %s=%s", labelClean, labelValue) 662 | } 663 | 664 | if labelsFound == 0 { 665 | log.Debugf("No labels found for container (T=%s)", time.Since(start)) 666 | return 667 | } 668 | 669 | // lets update the workloadEndpoint 670 | _, err = d.client.WorkloadEndpoints().Update(ctx, endpoint, options.SetOptions{}) 671 | if err != nil { 672 | err = errors.Wrapf(err, "Unable to update WorkloadEndpoint with labels (T=%s)", time.Since(start)) 673 | log.Warningln(err) 674 | endpoint, err = d.client.WorkloadEndpoints().Get(ctx, endpoint.Namespace, endpoint.Name, options.GetOptions{}) 675 | if err != nil { 676 | err = errors.Wrapf(err, "Unable to get WorkloadEndpoint (T=%s)", time.Since(start)) 677 | log.Errorln(err) 678 | return 679 | } 680 | time.Sleep(100 * time.Millisecond) 681 | goto RETRY_UPDATE_ENDPOINT 682 | } 683 | 684 | log.Infof("WorkloadEndpoint %s updated with labels: %v (T=%s)", 685 | endpointID, endpoint.ObjectMeta.Labels, time.Since(start)) 686 | 687 | } 688 | 689 | // Returns the label poll timeout. Default is returned unless an environment 690 | // key is set to a valid time.Duration. 691 | func getLabelPollTimeout() time.Duration { 692 | // 5 seconds should be more than enough for this plugin to get the 693 | // container labels. More info in func populateWorkloadEndpointWithLabels 694 | defaultTimeout := time.Duration(5 * time.Second) 695 | 696 | timeoutVal := os.Getenv(LABEL_POLL_TIMEOUT_ENVKEY) 697 | if timeoutVal == "" { 698 | return defaultTimeout 699 | } 700 | 701 | labelPollTimeout, err := time.ParseDuration(timeoutVal) 702 | if err != nil { 703 | err = errors.Wrapf(err, "Label poll timeout specified via env key %s is invalid, using default %s", 704 | LABEL_POLL_TIMEOUT_ENVKEY, defaultTimeout) 705 | log.Warningln(err) 706 | return defaultTimeout 707 | } 708 | log.Infof("Using custom label poll timeout: %s", labelPollTimeout) 709 | return labelPollTimeout 710 | } 711 | 712 | func (d NetworkDriver) generateEndpointName(hostname, endpointID string) (string, error) { 713 | wepNameIdent := wepname.WorkloadEndpointIdentifiers{ 714 | Node: hostname, 715 | Orchestrator: d.orchestratorID, 716 | Endpoint: endpointID, 717 | } 718 | return wepNameIdent.CalculateWorkloadEndpointName(false) 719 | } 720 | --------------------------------------------------------------------------------