From: Dan Williams Date: Mon, 15 May 2017 04:06:45 +0000 (-0500) Subject: plugins: add plugins from containernetworking/cni X-Git-Url: https://git.halfball.org/?a=commitdiff_plain;h=d2792f264e13977659c565cdf06f24b3d5b50933;p=plugins.git plugins: add plugins from containernetworking/cni Plugins prepared from the containernetworking/cni repo as follows: 1) git reset --hard 1a9288c3c09cea4e580fdb1a636f1c5e185a391f 2) git remove everything not in plugins/ 3) git remove plugins/test 4) git merge into containernetworking/plugins repo 5) adjust import paths for containernetworking/cni -> containernetworking/plugins --- d2792f264e13977659c565cdf06f24b3d5b50933 diff --cc build index 4e503f5,0000000..b1b82a6 mode 100755,000000..100755 --- a/build +++ b/build @@@ -1,26 -1,0 +1,26 @@@ +#!/usr/bin/env bash +set -e + +ORG_PATH="github.com/containernetworking" +export REPO_PATH="${ORG_PATH}/plugins" + +if [ ! -h gopath/src/${REPO_PATH} ]; then + mkdir -p gopath/src/${ORG_PATH} + ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255 +fi + +export GO15VENDOREXPERIMENT=1 +export GOPATH=${PWD}/gopath + +mkdir -p "${PWD}/bin" + +echo "Building plugins" - PLUGINS="plugins/sample plugins/main/vlan" ++PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample" +for d in $PLUGINS; do + if [ -d "$d" ]; then + plugin="$(basename "$d")" + echo " $plugin" + # use go install so we don't duplicate work + go build -o "${PWD}/bin/$plugin" -pkgdir "$GOPATH/pkg" "$@" "$REPO_PATH/$d" + fi +done diff --cc plugins/ipam/host-local/backend/allocator/allocator.go index 0000000,45190cc..87b4fa1 mode 000000,100644..100644 --- a/plugins/ipam/host-local/backend/allocator/allocator.go +++ b/plugins/ipam/host-local/backend/allocator/allocator.go @@@ -1,0 -1,277 +1,277 @@@ + // Copyright 2015 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package allocator + + import ( + "fmt" + "log" + "net" + "os" + + "github.com/containernetworking/cni/pkg/ip" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" - "github.com/containernetworking/cni/plugins/ipam/host-local/backend" ++ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend" + ) + + type IPAllocator struct { + // start is inclusive and may be allocated + start net.IP + // end is inclusive and may be allocated + end net.IP + conf *IPAMConfig + store backend.Store + } + + func NewIPAllocator(conf *IPAMConfig, store backend.Store) (*IPAllocator, error) { + // Can't create an allocator for a network with no addresses, eg + // a /32 or /31 + ones, masklen := conf.Subnet.Mask.Size() + if ones > masklen-2 { + return nil, fmt.Errorf("Network %v too small to allocate from", conf.Subnet) + } + + var ( + start net.IP + end net.IP + err error + ) + start, end, err = networkRange((*net.IPNet)(&conf.Subnet)) + if err != nil { + return nil, err + } + + // skip the .0 address + start = ip.NextIP(start) + + if conf.RangeStart != nil { + if err := validateRangeIP(conf.RangeStart, (*net.IPNet)(&conf.Subnet), start, end); err != nil { + return nil, err + } + start = conf.RangeStart + } + if conf.RangeEnd != nil { + if err := validateRangeIP(conf.RangeEnd, (*net.IPNet)(&conf.Subnet), start, end); err != nil { + return nil, err + } + end = conf.RangeEnd + } + return &IPAllocator{start, end, conf, store}, nil + } + + func canonicalizeIP(ip net.IP) (net.IP, error) { + if ip.To4() != nil { + return ip.To4(), nil + } else if ip.To16() != nil { + return ip.To16(), nil + } + return nil, fmt.Errorf("IP %s not v4 nor v6", ip) + } + + // Ensures @ip is within @ipnet, and (if given) inclusive of @start and @end + func validateRangeIP(ip net.IP, ipnet *net.IPNet, start net.IP, end net.IP) error { + var err error + + // Make sure we can compare IPv4 addresses directly + ip, err = canonicalizeIP(ip) + if err != nil { + return err + } + + if !ipnet.Contains(ip) { + return fmt.Errorf("%s not in network: %s", ip, ipnet) + } + + if start != nil { + start, err = canonicalizeIP(start) + if err != nil { + return err + } + if len(ip) != len(start) { + return fmt.Errorf("%s %d not same size IP address as start %s %d", ip, len(ip), start, len(start)) + } + for i := 0; i < len(ip); i++ { + if ip[i] > start[i] { + break + } else if ip[i] < start[i] { + return fmt.Errorf("%s outside of network %s with start %s", ip, ipnet, start) + } + } + } + + if end != nil { + end, err = canonicalizeIP(end) + if err != nil { + return err + } + if len(ip) != len(end) { + return fmt.Errorf("%s %d not same size IP address as end %s %d", ip, len(ip), end, len(end)) + } + for i := 0; i < len(ip); i++ { + if ip[i] < end[i] { + break + } else if ip[i] > end[i] { + return fmt.Errorf("%s outside of network %s with end %s", ip, ipnet, end) + } + } + } + return nil + } + + // Returns newly allocated IP along with its config + func (a *IPAllocator) Get(id string) (*current.IPConfig, []*types.Route, error) { + a.store.Lock() + defer a.store.Unlock() + + gw := a.conf.Gateway + if gw == nil { + gw = ip.NextIP(a.conf.Subnet.IP) + } + + var requestedIP net.IP + if a.conf.Args != nil { + requestedIP = a.conf.Args.IP + } + + if requestedIP != nil { + if gw != nil && gw.Equal(a.conf.Args.IP) { + return nil, nil, fmt.Errorf("requested IP must differ gateway IP") + } + + subnet := net.IPNet{ + IP: a.conf.Subnet.IP, + Mask: a.conf.Subnet.Mask, + } + err := validateRangeIP(requestedIP, &subnet, a.start, a.end) + if err != nil { + return nil, nil, err + } + + reserved, err := a.store.Reserve(id, requestedIP) + if err != nil { + return nil, nil, err + } + + if reserved { + ipConfig := ¤t.IPConfig{ + Version: "4", + Address: net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask}, + Gateway: gw, + } + routes := convertRoutesToCurrent(a.conf.Routes) + return ipConfig, routes, nil + } + return nil, nil, fmt.Errorf("requested IP address %q is not available in network: %s", requestedIP, a.conf.Name) + } + + startIP, endIP := a.getSearchRange() + for cur := startIP; ; cur = a.nextIP(cur) { + // don't allocate gateway IP + if gw != nil && cur.Equal(gw) { + continue + } + + reserved, err := a.store.Reserve(id, cur) + if err != nil { + return nil, nil, err + } + if reserved { + ipConfig := ¤t.IPConfig{ + Version: "4", + Address: net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask}, + Gateway: gw, + } + routes := convertRoutesToCurrent(a.conf.Routes) + return ipConfig, routes, nil + } + // break here to complete the loop + if cur.Equal(endIP) { + break + } + } + return nil, nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name) + } + + // Releases all IPs allocated for the container with given ID + func (a *IPAllocator) Release(id string) error { + a.store.Lock() + defer a.store.Unlock() + + return a.store.ReleaseByID(id) + } + + // Return the start and end IP addresses of a given subnet, excluding + // the broadcast address (eg, 192.168.1.255) + func networkRange(ipnet *net.IPNet) (net.IP, net.IP, error) { + if ipnet.IP == nil { + return nil, nil, fmt.Errorf("missing field %q in IPAM configuration", "subnet") + } + ip, err := canonicalizeIP(ipnet.IP) + if err != nil { + return nil, nil, fmt.Errorf("IP not v4 nor v6") + } + + if len(ip) != len(ipnet.Mask) { + return nil, nil, fmt.Errorf("IPNet IP and Mask version mismatch") + } + + var end net.IP + for i := 0; i < len(ip); i++ { + end = append(end, ip[i]|^ipnet.Mask[i]) + } + + // Exclude the broadcast address for IPv4 + if ip.To4() != nil { + end[3]-- + } + + return ipnet.IP, end, nil + } + + // nextIP returns the next ip of curIP within ipallocator's subnet + func (a *IPAllocator) nextIP(curIP net.IP) net.IP { + if curIP.Equal(a.end) { + return a.start + } + return ip.NextIP(curIP) + } + + // getSearchRange returns the start and end ip based on the last reserved ip + func (a *IPAllocator) getSearchRange() (net.IP, net.IP) { + var startIP net.IP + var endIP net.IP + startFromLastReservedIP := false + lastReservedIP, err := a.store.LastReservedIP() + if err != nil && !os.IsNotExist(err) { + log.Printf("Error retriving last reserved ip: %v", err) + } else if lastReservedIP != nil { + subnet := net.IPNet{ + IP: a.conf.Subnet.IP, + Mask: a.conf.Subnet.Mask, + } + err := validateRangeIP(lastReservedIP, &subnet, a.start, a.end) + if err == nil { + startFromLastReservedIP = true + } + } + if startFromLastReservedIP { + startIP = a.nextIP(lastReservedIP) + endIP = lastReservedIP + } else { + startIP = a.start + endIP = a.end + } + return startIP, endIP + } diff --cc plugins/ipam/host-local/backend/allocator/allocator_test.go index 0000000,894f502..54ead09 mode 000000,100644..100644 --- a/plugins/ipam/host-local/backend/allocator/allocator_test.go +++ b/plugins/ipam/host-local/backend/allocator/allocator_test.go @@@ -1,0 -1,378 +1,378 @@@ + // Copyright 2016 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package allocator + + import ( + "fmt" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" - fakestore "github.com/containernetworking/cni/plugins/ipam/host-local/backend/testing" ++ fakestore "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/testing" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "net" + ) + + type AllocatorTestCase struct { + subnet string + ipmap map[string]string + expectResult string + lastIP string + } + + func (t AllocatorTestCase) run() (*current.IPConfig, []*types.Route, error) { + subnet, err := types.ParseCIDR(t.subnet) + if err != nil { + return nil, nil, err + } + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + } + store := fakestore.NewFakeStore(t.ipmap, net.ParseIP(t.lastIP)) + alloc, err := NewIPAllocator(&conf, store) + if err != nil { + return nil, nil, err + } + res, routes, err := alloc.Get("ID") + if err != nil { + return nil, nil, err + } + + return res, routes, nil + } + + var _ = Describe("host-local ip allocator", func() { + Context("when has free ip", func() { + It("should allocate ips in round robin", func() { + testCases := []AllocatorTestCase{ + // fresh start + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{}, + expectResult: "10.0.0.2", + lastIP: "", + }, + { + subnet: "10.0.0.0/30", + ipmap: map[string]string{}, + expectResult: "10.0.0.2", + lastIP: "", + }, + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.2": "id", + }, + expectResult: "10.0.0.3", + lastIP: "", + }, + // next ip of last reserved ip + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{}, + expectResult: "10.0.0.6", + lastIP: "10.0.0.5", + }, + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.4": "id", + "10.0.0.5": "id", + }, + expectResult: "10.0.0.6", + lastIP: "10.0.0.3", + }, + // round robin to the beginning + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.6": "id", + }, + expectResult: "10.0.0.2", + lastIP: "10.0.0.5", + }, + // lastIP is out of range + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.2": "id", + }, + expectResult: "10.0.0.3", + lastIP: "10.0.0.128", + }, + // wrap around and reserve lastIP + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.2": "id", + "10.0.0.4": "id", + "10.0.0.5": "id", + "10.0.0.6": "id", + }, + expectResult: "10.0.0.3", + lastIP: "10.0.0.3", + }, + } + + for _, tc := range testCases { + res, _, err := tc.run() + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address.IP.String()).To(Equal(tc.expectResult)) + } + }) + + It("should not allocate the broadcast address", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + alloc, err := NewIPAllocator(&conf, store) + Expect(err).ToNot(HaveOccurred()) + + for i := 1; i < 254; i++ { + res, _, err := alloc.Get("ID") + Expect(err).ToNot(HaveOccurred()) + // i+1 because the gateway address is skipped + s := fmt.Sprintf("192.168.1.%d/24", i+1) + Expect(s).To(Equal(res.Address.String())) + } + + _, _, err = alloc.Get("ID") + Expect(err).To(HaveOccurred()) + }) + + It("should allocate RangeStart first", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + RangeStart: net.ParseIP("192.168.1.10"), + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + alloc, err := NewIPAllocator(&conf, store) + Expect(err).ToNot(HaveOccurred()) + + res, _, err := alloc.Get("ID") + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address.String()).To(Equal("192.168.1.10/24")) + + res, _, err = alloc.Get("ID") + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address.String()).To(Equal("192.168.1.11/24")) + }) + + It("should allocate RangeEnd but not past RangeEnd", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + RangeEnd: net.ParseIP("192.168.1.5"), + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + alloc, err := NewIPAllocator(&conf, store) + Expect(err).ToNot(HaveOccurred()) + + for i := 1; i < 5; i++ { + res, _, err := alloc.Get("ID") + Expect(err).ToNot(HaveOccurred()) + // i+1 because the gateway address is skipped + Expect(res.Address.String()).To(Equal(fmt.Sprintf("192.168.1.%d/24", i+1))) + } + + _, _, err = alloc.Get("ID") + Expect(err).To(HaveOccurred()) + }) + + Context("when requesting a specific IP", func() { + It("must allocate the requested IP", func() { + subnet, err := types.ParseCIDR("10.0.0.0/29") + Expect(err).ToNot(HaveOccurred()) + requestedIP := net.ParseIP("10.0.0.2") + ipmap := map[string]string{} + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + Args: &IPAMArgs{IP: requestedIP}, + } + store := fakestore.NewFakeStore(ipmap, nil) + alloc, _ := NewIPAllocator(&conf, store) + res, _, err := alloc.Get("ID") + Expect(err).ToNot(HaveOccurred()) + Expect(res.Address.IP.String()).To(Equal(requestedIP.String())) + }) + + It("must return an error when the requested IP is after RangeEnd", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + ipmap := map[string]string{} + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + Args: &IPAMArgs{IP: net.ParseIP("192.168.1.50")}, + RangeEnd: net.ParseIP("192.168.1.20"), + } + store := fakestore.NewFakeStore(ipmap, nil) + alloc, _ := NewIPAllocator(&conf, store) + _, _, err = alloc.Get("ID") + Expect(err).To(HaveOccurred()) + }) + + It("must return an error when the requested IP is before RangeStart", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + ipmap := map[string]string{} + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + Args: &IPAMArgs{IP: net.ParseIP("192.168.1.3")}, + RangeStart: net.ParseIP("192.168.1.10"), + } + store := fakestore.NewFakeStore(ipmap, nil) + alloc, _ := NewIPAllocator(&conf, store) + _, _, err = alloc.Get("ID") + Expect(err).To(HaveOccurred()) + }) + }) + + It("RangeStart must be in the given subnet", func() { + testcases := []struct { + name string + ipnet string + start string + }{ + {"outside-subnet", "192.168.1.0/24", "10.0.0.1"}, + {"zero-ip", "10.1.0.0/16", "10.1.0.0"}, + } + + for _, tc := range testcases { + subnet, err := types.ParseCIDR(tc.ipnet) + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: tc.name, + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + RangeStart: net.ParseIP(tc.start), + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + _, err = NewIPAllocator(&conf, store) + Expect(err).To(HaveOccurred()) + } + }) + + It("RangeEnd must be in the given subnet", func() { + testcases := []struct { + name string + ipnet string + end string + }{ + {"outside-subnet", "192.168.1.0/24", "10.0.0.1"}, + {"broadcast-ip", "10.1.0.0/16", "10.1.255.255"}, + } + + for _, tc := range testcases { + subnet, err := types.ParseCIDR(tc.ipnet) + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: tc.name, + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + RangeEnd: net.ParseIP(tc.end), + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + _, err = NewIPAllocator(&conf, store) + Expect(err).To(HaveOccurred()) + } + }) + + It("RangeEnd must be after RangeStart in the given subnet", func() { + subnet, err := types.ParseCIDR("192.168.1.0/24") + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + RangeStart: net.ParseIP("192.168.1.10"), + RangeEnd: net.ParseIP("192.168.1.3"), + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + _, err = NewIPAllocator(&conf, store) + Expect(err).To(HaveOccurred()) + }) + }) + + Context("when out of ips", func() { + It("returns a meaningful error", func() { + testCases := []AllocatorTestCase{ + { + subnet: "10.0.0.0/30", + ipmap: map[string]string{ + "10.0.0.2": "id", + "10.0.0.3": "id", + }, + }, + { + subnet: "10.0.0.0/29", + ipmap: map[string]string{ + "10.0.0.2": "id", + "10.0.0.3": "id", + "10.0.0.4": "id", + "10.0.0.5": "id", + "10.0.0.6": "id", + "10.0.0.7": "id", + }, + }, + } + for _, tc := range testCases { + _, _, err := tc.run() + Expect(err).To(MatchError("no IP addresses available in network: test")) + } + }) + }) + + Context("when given an invalid subnet", func() { + It("returns a meaningful error", func() { + subnet, err := types.ParseCIDR("192.168.1.0/31") + Expect(err).ToNot(HaveOccurred()) + + conf := IPAMConfig{ + Name: "test", + Type: "host-local", + Subnet: types.IPNet{IP: subnet.IP, Mask: subnet.Mask}, + } + store := fakestore.NewFakeStore(map[string]string{}, net.ParseIP("")) + _, err = NewIPAllocator(&conf, store) + Expect(err).To(HaveOccurred()) + }) + }) + }) diff --cc plugins/ipam/host-local/backend/disk/backend.go index 0000000,183b76e..483103a mode 000000,100644..100644 --- a/plugins/ipam/host-local/backend/disk/backend.go +++ b/plugins/ipam/host-local/backend/disk/backend.go @@@ -1,0 -1,115 +1,115 @@@ + // Copyright 2015 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package disk + + import ( + "io/ioutil" + "net" + "os" + "path/filepath" + "strings" + - "github.com/containernetworking/cni/plugins/ipam/host-local/backend" ++ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend" + ) + + const lastIPFile = "last_reserved_ip" + + var defaultDataDir = "/var/lib/cni/networks" + + type Store struct { + FileLock + dataDir string + } + + // Store implements the Store interface + var _ backend.Store = &Store{} + + func New(network, dataDir string) (*Store, error) { + if dataDir == "" { + dataDir = defaultDataDir + } + dir := filepath.Join(dataDir, network) + if err := os.MkdirAll(dir, 0644); err != nil { + return nil, err + } + + lk, err := NewFileLock(dir) + if err != nil { + return nil, err + } + return &Store{*lk, dir}, nil + } + + func (s *Store) Reserve(id string, ip net.IP) (bool, error) { + fname := filepath.Join(s.dataDir, ip.String()) + f, err := os.OpenFile(fname, os.O_RDWR|os.O_EXCL|os.O_CREATE, 0644) + if os.IsExist(err) { + return false, nil + } + if err != nil { + return false, err + } + if _, err := f.WriteString(strings.TrimSpace(id)); err != nil { + f.Close() + os.Remove(f.Name()) + return false, err + } + if err := f.Close(); err != nil { + os.Remove(f.Name()) + return false, err + } + // store the reserved ip in lastIPFile + ipfile := filepath.Join(s.dataDir, lastIPFile) + err = ioutil.WriteFile(ipfile, []byte(ip.String()), 0644) + if err != nil { + return false, err + } + return true, nil + } + + // LastReservedIP returns the last reserved IP if exists + func (s *Store) LastReservedIP() (net.IP, error) { + ipfile := filepath.Join(s.dataDir, lastIPFile) + data, err := ioutil.ReadFile(ipfile) + if err != nil { + return nil, err + } + return net.ParseIP(string(data)), nil + } + + func (s *Store) Release(ip net.IP) error { + return os.Remove(filepath.Join(s.dataDir, ip.String())) + } + + // N.B. This function eats errors to be tolerant and + // release as much as possible + func (s *Store) ReleaseByID(id string) error { + err := filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return nil + } + data, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + if strings.TrimSpace(string(data)) == strings.TrimSpace(id) { + if err := os.Remove(path); err != nil { + return nil + } + } + return nil + }) + return err + } diff --cc plugins/ipam/host-local/backend/testing/fake_store.go index 0000000,0a2b5ca..c35a130 mode 000000,100644..100644 --- a/plugins/ipam/host-local/backend/testing/fake_store.go +++ b/plugins/ipam/host-local/backend/testing/fake_store.go @@@ -1,0 -1,77 +1,77 @@@ + // Copyright 2015 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package testing + + import ( + "net" + - "github.com/containernetworking/cni/plugins/ipam/host-local/backend" ++ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend" + ) + + type FakeStore struct { + ipMap map[string]string + lastReservedIP net.IP + } + + // FakeStore implements the Store interface + var _ backend.Store = &FakeStore{} + + func NewFakeStore(ipmap map[string]string, lastIP net.IP) *FakeStore { + return &FakeStore{ipmap, lastIP} + } + + func (s *FakeStore) Lock() error { + return nil + } + + func (s *FakeStore) Unlock() error { + return nil + } + + func (s *FakeStore) Close() error { + return nil + } + + func (s *FakeStore) Reserve(id string, ip net.IP) (bool, error) { + key := ip.String() + if _, ok := s.ipMap[key]; !ok { + s.ipMap[key] = id + s.lastReservedIP = ip + return true, nil + } + return false, nil + } + + func (s *FakeStore) LastReservedIP() (net.IP, error) { + return s.lastReservedIP, nil + } + + func (s *FakeStore) Release(ip net.IP) error { + delete(s.ipMap, ip.String()) + return nil + } + + func (s *FakeStore) ReleaseByID(id string) error { + toDelete := []string{} + for k, v := range s.ipMap { + if v == id { + toDelete = append(toDelete, k) + } + } + for _, ip := range toDelete { + delete(s.ipMap, ip) + } + return nil + } diff --cc plugins/ipam/host-local/main.go index 0000000,2f77912..4db9a07 mode 000000,100644..100644 --- a/plugins/ipam/host-local/main.go +++ b/plugins/ipam/host-local/main.go @@@ -1,0 -1,86 +1,86 @@@ + // Copyright 2015 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package main + + import ( - "github.com/containernetworking/cni/plugins/ipam/host-local/backend/allocator" - "github.com/containernetworking/cni/plugins/ipam/host-local/backend/disk" ++ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator" ++ "github.com/containernetworking/plugins/plugins/ipam/host-local/backend/disk" + + "github.com/containernetworking/cni/pkg/skel" + "github.com/containernetworking/cni/pkg/types" + "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/cni/pkg/version" + ) + + func main() { + skel.PluginMain(cmdAdd, cmdDel, version.All) + } + + func cmdAdd(args *skel.CmdArgs) error { + ipamConf, confVersion, err := allocator.LoadIPAMConfig(args.StdinData, args.Args) + if err != nil { + return err + } + + result := ¤t.Result{} + + if ipamConf.ResolvConf != "" { + dns, err := parseResolvConf(ipamConf.ResolvConf) + if err != nil { + return err + } + result.DNS = *dns + } + + store, err := disk.New(ipamConf.Name, ipamConf.DataDir) + if err != nil { + return err + } + defer store.Close() + + allocator, err := allocator.NewIPAllocator(ipamConf, store) + if err != nil { + return err + } + + ipConf, routes, err := allocator.Get(args.ContainerID) + if err != nil { + return err + } + result.IPs = []*current.IPConfig{ipConf} + result.Routes = routes + + return types.PrintResult(result, confVersion) + } + + func cmdDel(args *skel.CmdArgs) error { + ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args) + if err != nil { + return err + } + + store, err := disk.New(ipamConf.Name, ipamConf.DataDir) + if err != nil { + return err + } + defer store.Close() + + ipAllocator, err := allocator.NewIPAllocator(ipamConf, store) + if err != nil { + return err + } + + return ipAllocator.Release(args.ContainerID) + } diff --cc plugins/main/loopback/loopback_suite_test.go index 0000000,e355747..212bf60 mode 000000,100644..100644 --- a/plugins/main/loopback/loopback_suite_test.go +++ b/plugins/main/loopback/loopback_suite_test.go @@@ -1,0 -1,41 +1,41 @@@ + // Copyright 2016 CNI authors + // + // Licensed under the Apache License, Version 2.0 (the "License"); + // you may not use this file except in compliance with the License. + // You may obtain a copy of the License at + // + // http://www.apache.org/licenses/LICENSE-2.0 + // + // Unless required by applicable law or agreed to in writing, software + // distributed under the License is distributed on an "AS IS" BASIS, + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + // See the License for the specific language governing permissions and + // limitations under the License. + + package main_test + + import ( + "github.com/onsi/gomega/gexec" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" + ) + + var pathToLoPlugin string + + func TestLoopback(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Loopback Suite") + } + + var _ = BeforeSuite(func() { + var err error - pathToLoPlugin, err = gexec.Build("github.com/containernetworking/cni/plugins/main/loopback") ++ pathToLoPlugin, err = gexec.Build("github.com/containernetworking/plugins/plugins/main/loopback") + Expect(err).NotTo(HaveOccurred()) + }) + + var _ = AfterSuite(func() { + gexec.CleanupBuildArtifacts() + }) diff --cc test index 6c1ec4b,0000000..67e6780 mode 100755,000000..100755 --- a/test +++ b/test @@@ -1,60 -1,0 +1,43 @@@ +#!/usr/bin/env bash +# +# Run CNI plugin tests. +# - # This needs sudo, as we'll be creating net interfaces. It also needs a valid - # CNI_PATH, with at least the ptp and host-local plugins. ++# This needs sudo, as we'll be creating net interfaces. +# - # You'll probably run it like this: - # CNI_PATH=../cni/bin ./test +set -e + +source ./build + - if [ -z "$CNI_PATH" ]; then - echo "Need a valid CNI_PATH" - exit 1 - fi - - # Check that the plugins we need are available - if ! PATH=${CNI_PATH} type -P ptp host-local >& /dev/null; then - echo '$CNI_PATH must include ptp and host-local' - exit 1 - fi - - #add our build path to CNI_PATH - CNI_PATH=$(pwd)/bin:${CNI_PATH} - +echo "Running tests" + - TESTABLE="plugins/sample plugins/main/vlan" ++TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/ipam/host-local/backend/allocator plugins/main/loopback plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/meta/flannel plugins/main/vlan plugins/sample" + +# user has not provided PKG override +if [ -z "$PKG" ]; then + TEST=$TESTABLE + FMT=$TESTABLE + +# user has provided PKG override +else + # strip out slashes and dots from PKG=./foo/ + TEST=${PKG//\//} + TEST=${TEST//./} + + # only run gofmt on packages provided by user + FMT="$TEST" +fi + +# split TEST into an array and prepend REPO_PATH to each local package +split=(${TEST// / }) +TEST=${split[@]/#/${REPO_PATH}/} + - sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${CNI_PATH}:${PATH} go test ${TEST}" ++sudo -E bash -c "umask 0; PATH=${GOROOT}/bin:$(pwd)/bin:${PATH} go test ${TEST}" + +echo "Checking gofmt..." +fmtRes=$(gofmt -l $FMT) +if [ -n "${fmtRes}" ]; then + echo -e "gofmt checking failed:\n${fmtRes}" + exit 255 +fi + +