Pulled from github 2017-Feb-14, dealt with merge
authorMichael Cambria <michael.cambria@verizon.com>
Wed, 15 Feb 2017 13:53:24 +0000 (08:53 -0500)
committerMichael Cambria <michael.cambria@verizon.com>
Wed, 15 Feb 2017 13:53:24 +0000 (08:53 -0500)
13 files changed:
plugins/ipam/vz-local/#allocator.go# [deleted file]
plugins/ipam/vz-local/README.md
plugins/ipam/vz-local/backend/allocator/allocator.go [moved from plugins/ipam/vz-local/allocator.go with 87% similarity]
plugins/ipam/vz-local/backend/allocator/allocator_suite_test.go [moved from plugins/ipam/vz-local/host_local_suite_test.go with 88% similarity]
plugins/ipam/vz-local/backend/allocator/allocator_test.go [moved from plugins/ipam/vz-local/allocator_test.go with 90% similarity]
plugins/ipam/vz-local/backend/allocator/config.go [new file with mode: 0644]
plugins/ipam/vz-local/backend/disk/backend.go
plugins/ipam/vz-local/backend/testing/fake_store.go
plugins/ipam/vz-local/config.go [deleted file]
plugins/ipam/vz-local/dns.go [new file with mode: 0644]
plugins/ipam/vz-local/dns_test.go [new file with mode: 0644]
plugins/ipam/vz-local/host_local_test.go
plugins/ipam/vz-local/main.go

diff --git a/plugins/ipam/vz-local/#allocator.go# b/plugins/ipam/vz-local/#allocator.go#
deleted file mode 100644 (file)
index 645bf76..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-// 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 (
-       "fmt"
-       "log"
-       "net"
-
-       "github.com/containernetworking/cni/pkg/ip"
-       "github.com/containernetworking/cni/pkg/types"
-       "github.com/containernetworking/cni/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), nil, nil); err != nil {
-                       return nil, err
-               }
-               start = conf.RangeStart
-       }
-       if conf.RangeEnd != nil {
-               if err := validateRangeIP(conf.RangeEnd, (*net.IPNet)(&conf.Subnet), start, nil); 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) (*types.IPConfig, 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) {
-                       //log.Println("AdvRoute: BGP Advertises /32", requestedIP, advertise_route)
-               }
-
-               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, err
-               }
-
-               reserved, err := a.store.Reserve(id, requestedIP)
-               if err != nil {
-                       return nil, err
-               }
-
-               if reserved {
-                       return &types.IPConfig{
-                               IP:      net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask},
-                               Gateway: gw,
-                               Routes:  a.conf.Routes,
-                       }, nil
-               }
-               return 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, err
-               }
-               if reserved {
-                       return &types.IPConfig{
-                               IP:      net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask},
-                               Gateway: gw,
-                               Routes:  a.conf.Routes,
-                       }, nil
-               }
-               // break here to complete the loop
-               if cur.Equal(endIP) {
-                       break
-               }
-       }
-       return 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 {
-               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
-}
index 2efd210..128bc6d 100644 (file)
@@ -1,6 +1,7 @@
 # host-local IP address manager
 
-host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range.
+host-local IPAM allocates IPv4 and IPv6 addresses out of a specified address range. Optionally,
+it can include a DNS configuration from a `resolv.conf` file on the host.
 
 ## Usage
 
@@ -65,7 +66,8 @@ f81d4fae-7dec-11d0-a765-00a0c91e6bf6
                "rangeEnd": "3ffe:ffff:0:01ff::0020",
                "routes": [
                        { "dst": "3ffe:ffff:0:01ff::1/64" }
-               ]
+               ],
+               "resolvConf": "/etc/resolv.conf"
        }
 }
 ```
similarity index 87%
rename from plugins/ipam/vz-local/allocator.go
rename to plugins/ipam/vz-local/backend/allocator/allocator.go
index b8c0ad0..c43235e 100644 (file)
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+package allocator
 
 import (
        "fmt"
@@ -21,6 +21,7 @@ import (
 
        "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"
 )
 
@@ -129,7 +130,7 @@ func validateRangeIP(ip net.IP, ipnet *net.IPNet, start net.IP, end net.IP) erro
 }
 
 // Returns newly allocated IP along with its config
-func (a *IPAllocator) Get(id string) (*types.IPConfig, error) {
+func (a *IPAllocator) Get(id string) (*current.IPConfig, []*types.Route, error) {
        a.store.Lock()
        defer a.store.Unlock()
 
@@ -145,33 +146,35 @@ func (a *IPAllocator) Get(id string) (*types.IPConfig, error) {
 
        if requestedIP != nil {
                if gw != nil && gw.Equal(a.conf.Args.IP) {
-                       return nil, fmt.Errorf("requested IP must differ gateway IP")
+                       return nil, nil, fmt.Errorf("requested IP must differ gateway IP")
                }
 
-               //log.Println("AdvRoute: BGP Advertises /32", requestedIP, advertise_route)
                log.Println("AdvRoute: BGP Advertises /32", requestedIP)
+
                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, err
+                       return nil, nil, err
                }
 
                reserved, err := a.store.Reserve(id, requestedIP)
                if err != nil {
-                       return nil, err
+                       return nil, nil, err
                }
 
                if reserved {
-                       return &types.IPConfig{
-                               IP:      net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask},
+                       ipConfig := &current.IPConfig{
+                               Version: "4",
+                               Address: net.IPNet{IP: requestedIP, Mask: a.conf.Subnet.Mask},
                                Gateway: gw,
-                               Routes:  a.conf.Routes,
-                       }, nil
+                       }
+                       routes := convertRoutesToCurrent(a.conf.Routes)
+                       return ipConfig, routes, nil
                }
-               return nil, fmt.Errorf("requested IP address %q is not available in network: %s", requestedIP, a.conf.Name)
+               return nil, nil, fmt.Errorf("requested IP address %q is not available in network: %s", requestedIP, a.conf.Name)
        }
 
        startIP, endIP := a.getSearchRange()
@@ -183,21 +186,23 @@ func (a *IPAllocator) Get(id string) (*types.IPConfig, error) {
 
                reserved, err := a.store.Reserve(id, cur)
                if err != nil {
-                       return nil, err
+                       return nil, nil, err
                }
                if reserved {
-                       return &types.IPConfig{
-                               IP:      net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask},
+                       ipConfig := &current.IPConfig{
+                               Version: "4",
+                               Address: net.IPNet{IP: cur, Mask: a.conf.Subnet.Mask},
                                Gateway: gw,
-                               Routes:  a.conf.Routes,
-                       }, nil
+                       }
+                       routes := convertRoutesToCurrent(a.conf.Routes)
+                       return ipConfig, routes, nil
                }
                // break here to complete the loop
                if cur.Equal(endIP) {
                        break
                }
        }
-       return nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name)
+       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
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main_test
+package allocator_test
 
 import (
        . "github.com/onsi/ginkgo"
@@ -21,7 +21,7 @@ import (
        "testing"
 )
 
-func TestHostLocal(t *testing.T) {
+func TestAllocator(t *testing.T) {
        RegisterFailHandler(Fail)
-       RunSpecs(t, "HostLocal Suite")
+       RunSpecs(t, "Allocator Suite")
 }
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+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"
        . "github.com/onsi/ginkgo"
        . "github.com/onsi/gomega"
@@ -30,10 +31,10 @@ type AllocatorTestCase struct {
        lastIP       string
 }
 
-func (t AllocatorTestCase) run() (*types.IPConfig, error) {
+func (t AllocatorTestCase) run() (*current.IPConfig, []*types.Route, error) {
        subnet, err := types.ParseCIDR(t.subnet)
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
 
        conf := IPAMConfig{
@@ -44,14 +45,14 @@ func (t AllocatorTestCase) run() (*types.IPConfig, error) {
        store := fakestore.NewFakeStore(t.ipmap, net.ParseIP(t.lastIP))
        alloc, err := NewIPAllocator(&conf, store)
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
-       res, err := alloc.Get("ID")
+       res, routes, err := alloc.Get("ID")
        if err != nil {
-               return nil, err
+               return nil, nil, err
        }
 
-       return res, nil
+       return res, routes, nil
 }
 
 var _ = Describe("host-local ip allocator", func() {
@@ -128,9 +129,9 @@ var _ = Describe("host-local ip allocator", func() {
                        }
 
                        for _, tc := range testCases {
-                               res, err := tc.run()
+                               res, _, err := tc.run()
                                Expect(err).ToNot(HaveOccurred())
-                               Expect(res.IP.IP.String()).To(Equal(tc.expectResult))
+                               Expect(res.Address.IP.String()).To(Equal(tc.expectResult))
                        }
                })
 
@@ -148,14 +149,14 @@ var _ = Describe("host-local ip allocator", func() {
                        Expect(err).ToNot(HaveOccurred())
 
                        for i := 1; i < 254; i++ {
-                               res, err := alloc.Get("ID")
+                               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.IP.String()))
+                               Expect(s).To(Equal(res.Address.String()))
                        }
 
-                       _, err = alloc.Get("ID")
+                       _, _, err = alloc.Get("ID")
                        Expect(err).To(HaveOccurred())
                })
 
@@ -173,13 +174,13 @@ var _ = Describe("host-local ip allocator", func() {
                        alloc, err := NewIPAllocator(&conf, store)
                        Expect(err).ToNot(HaveOccurred())
 
-                       res, err := alloc.Get("ID")
+                       res, _, err := alloc.Get("ID")
                        Expect(err).ToNot(HaveOccurred())
-                       Expect(res.IP.String()).To(Equal("192.168.1.10/24"))
+                       Expect(res.Address.String()).To(Equal("192.168.1.10/24"))
 
-                       res, err = alloc.Get("ID")
+                       res, _, err = alloc.Get("ID")
                        Expect(err).ToNot(HaveOccurred())
-                       Expect(res.IP.String()).To(Equal("192.168.1.11/24"))
+                       Expect(res.Address.String()).To(Equal("192.168.1.11/24"))
                })
 
                It("should allocate RangeEnd but not past RangeEnd", func() {
@@ -197,13 +198,13 @@ var _ = Describe("host-local ip allocator", func() {
                        Expect(err).ToNot(HaveOccurred())
 
                        for i := 1; i < 5; i++ {
-                               res, err := alloc.Get("ID")
+                               res, _, err := alloc.Get("ID")
                                Expect(err).ToNot(HaveOccurred())
                                // i+1 because the gateway address is skipped
-                               Expect(res.IP.String()).To(Equal(fmt.Sprintf("192.168.1.%d/24", i+1)))
+                               Expect(res.Address.String()).To(Equal(fmt.Sprintf("192.168.1.%d/24", i+1)))
                        }
 
-                       _, err = alloc.Get("ID")
+                       _, _, err = alloc.Get("ID")
                        Expect(err).To(HaveOccurred())
                })
 
@@ -221,9 +222,9 @@ var _ = Describe("host-local ip allocator", func() {
                                }
                                store := fakestore.NewFakeStore(ipmap, nil)
                                alloc, _ := NewIPAllocator(&conf, store)
-                               res, err := alloc.Get("ID")
+                               res, _, err := alloc.Get("ID")
                                Expect(err).ToNot(HaveOccurred())
-                               Expect(res.IP.IP.String()).To(Equal(requestedIP.String()))
+                               Expect(res.Address.IP.String()).To(Equal(requestedIP.String()))
                        })
 
                        It("must return an error when the requested IP is after RangeEnd", func() {
@@ -239,7 +240,7 @@ var _ = Describe("host-local ip allocator", func() {
                                }
                                store := fakestore.NewFakeStore(ipmap, nil)
                                alloc, _ := NewIPAllocator(&conf, store)
-                               _, err = alloc.Get("ID")
+                               _, _, err = alloc.Get("ID")
                                Expect(err).To(HaveOccurred())
                        })
 
@@ -256,7 +257,7 @@ var _ = Describe("host-local ip allocator", func() {
                                }
                                store := fakestore.NewFakeStore(ipmap, nil)
                                alloc, _ := NewIPAllocator(&conf, store)
-                               _, err = alloc.Get("ID")
+                               _, _, err = alloc.Get("ID")
                                Expect(err).To(HaveOccurred())
                        })
                })
@@ -331,7 +332,7 @@ var _ = Describe("host-local ip allocator", func() {
                                },
                        }
                        for _, tc := range testCases {
-                               _, err := tc.run()
+                               _, _, err := tc.run()
                                Expect(err).To(MatchError("no IP addresses available in network: test"))
                        }
                })
diff --git a/plugins/ipam/vz-local/backend/allocator/config.go b/plugins/ipam/vz-local/backend/allocator/config.go
new file mode 100644 (file)
index 0000000..d261d0f
--- /dev/null
@@ -0,0 +1,84 @@
+// 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 (
+       "encoding/json"
+       "fmt"
+       "net"
+
+       "github.com/containernetworking/cni/pkg/types"
+)
+
+// IPAMConfig represents the IP related network configuration.
+type IPAMConfig struct {
+       Name       string
+       Type       string        `json:"type"`
+       RangeStart net.IP        `json:"rangeStart"`
+       RangeEnd   net.IP        `json:"rangeEnd"`
+       Subnet     types.IPNet   `json:"subnet"`
+       Gateway    net.IP        `json:"gateway"`
+       Routes     []types.Route `json:"routes"`
+       DataDir    string        `json:"dataDir"`
+       ResolvConf string        `json:"resolvConf"`
+       Args       *IPAMArgs     `json:"-"`
+}
+
+type IPAMArgs struct {
+       types.CommonArgs
+       IP net.IP `json:"ip,omitempty"`
+}
+
+type Net struct {
+       Name       string      `json:"name"`
+       CNIVersion string      `json:"cniVersion"`
+       IPAM       *IPAMConfig `json:"ipam"`
+}
+
+// NewIPAMConfig creates a NetworkConfig from the given network name.
+func LoadIPAMConfig(bytes []byte, args string) (*IPAMConfig, string, error) {
+       n := Net{}
+       if err := json.Unmarshal(bytes, &n); err != nil {
+               return nil, "", err
+       }
+
+       if args != "" {
+               n.IPAM.Args = &IPAMArgs{}
+               err := types.LoadArgs(args, n.IPAM.Args)
+               if err != nil {
+                       return nil, "", err
+               }
+       }
+
+       if n.IPAM == nil {
+               return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
+       }
+
+       // Copy net name into IPAM so not to drag Net struct around
+       n.IPAM.Name = n.Name
+
+       return n.IPAM, n.CNIVersion, nil
+}
+
+func convertRoutesToCurrent(routes []types.Route) []*types.Route {
+       var currentRoutes []*types.Route
+       for _, r := range routes {
+               currentRoutes = append(currentRoutes, &types.Route{
+                       Dst: r.Dst,
+                       GW:  r.GW,
+               })
+       }
+       return currentRoutes
+}
index 14dd40d..9c11699 100644 (file)
@@ -20,6 +20,9 @@ import (
        "net"
        "os"
        "path/filepath"
+       "strings"
+
+       "github.com/containernetworking/cni/plugins/ipam/host-local/backend"
 )
 
 const lastIPFile = "last_reserved_ip"
@@ -31,6 +34,9 @@ type Store struct {
        dataDir string
 }
 
+// Store implements the Store interface
+var _ backend.Store = &Store{}
+
 func New(network, dataDir string) (*Store, error) {
        if dataDir == "" {
                dataDir = defaultDataDir
@@ -56,7 +62,7 @@ func (s *Store) Reserve(id string, ip net.IP) (bool, error) {
        if err != nil {
                return false, err
        }
-       if _, err := f.WriteString(id); err != nil {
+       if _, err := f.WriteString(strings.TrimSpace(id)); err != nil {
                f.Close()
                os.Remove(f.Name())
                return false, err
@@ -99,7 +105,7 @@ func (s *Store) ReleaseByID(id string) error {
                if err != nil {
                        return nil
                }
-               if string(data) == id {
+               if strings.TrimSpace(string(data)) == strings.TrimSpace(id) {
                        if err := os.Remove(path); err != nil {
                                return nil
                        }
index f7750ca..0a2b5ca 100644 (file)
@@ -16,6 +16,8 @@ package testing
 
 import (
        "net"
+
+       "github.com/containernetworking/cni/plugins/ipam/host-local/backend"
 )
 
 type FakeStore struct {
@@ -23,6 +25,9 @@ type FakeStore struct {
        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}
 }
diff --git a/plugins/ipam/vz-local/config.go b/plugins/ipam/vz-local/config.go
deleted file mode 100644 (file)
index 355b340..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-// 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 (
-       "encoding/json"
-       "fmt"
-       "log"
-       "net"
-
-       "os"
-
-       "github.com/containernetworking/cni/pkg/types"
-)
-
-// IPAMConfig represents the IP related network configuration.
-type IPAMConfig struct {
-       Name       string
-       Type       string        `json:"type"`
-       RangeStart net.IP        `json:"rangeStart"`
-       RangeEnd   net.IP        `json:"rangeEnd"`
-       Subnet     types.IPNet   `json:"subnet"`
-       Gateway    net.IP        `json:"gateway"`
-       Routes     []types.Route `json:"routes"`
-       DataDir    string        `json:"dataDir"`
-       Args       *IPAMArgs     `json:"-"`
-}
-
-type IPAMArgs struct {
-       types.CommonArgs
-       IP net.IP `json:"ip,omitempty"`
-       UPLINK types.UnmarshallableString `json:"uplink,omitempty"`
-}
-
-/*
- * Add structs needed to parse labels  
- */
-type Args struct {
-       Mesos Mesos `json:"org.apache.mesos,omitempty"`
-}
-
-type Mesos struct {
-       NetworkInfo NetworkInfo `json:"network_info"`
-}
-
-type NetworkInfo struct {
-       Name   string `json:"name"`
-       Labels struct {
-               Labels []struct {
-                       Key   string `json:"key"`
-                       Value string `json:"value"`
-               } `json:"labels,omitempty"`
-       } `json:"labels,omitempty"`
-}
-
-// NetConf describes a network.
-type NetConf struct {
-       CNIVersion string `json:"cniVersion,omitempty"`
-
-       Name string `json:"name,omitempty"`
-       Type string `json:"type,omitempty"`
-       IPAM *IPAMConfig `json:"ipam"`
-       DNS types.DNS `json:"dns"`
-       ARGS *Args `json:"args,omitempty"`
-}
-
-// NewIPAMConfig creates a NetworkConfig from the given network name.
-func LoadIPAMConfig(bytes []byte, args string) (*IPAMConfig, error) {
-       //n := Net{}
-       n := NetConf{}
-       if err := json.Unmarshal(bytes, &n); err != nil {
-               return nil, err
-       }
-
-       if args != "" {
-               n.IPAM.Args = &IPAMArgs{}
-               err := types.LoadArgs(args, n.IPAM.Args)
-               if err != nil {
-                       return nil, err
-               }
-       }
-
-       if n.IPAM == nil {
-               return nil, fmt.Errorf("IPAM config missing 'ipam' key")
-       }
-
-       // Copy net name into IPAM so not to drag Net struct around
-       n.IPAM.Name = n.Name
-
-       /*
-        * Example of getting an environment variable supplied to the IPAM plugin
-        */
-       mccval := os.Getenv("MCCVAL")
-       println("mccval is: ", mccval)
-
-       /*
-        * Get values for supplied labels
-        * Ensure that IPAM args (e.g. CNI_ARGS) isn't confused with args passed to CNI itself 
-        */
-       labels := map[string]string{}
-
-       if args != "" {
-         if n.ARGS != nil {
-
-               for k, label := range n.ARGS.Mesos.NetworkInfo.Labels.Labels {
-                   labels[label.Key] = label.Value
-                   println("Map k (for)", k)
-                   println("Map k (for)", k, label.Key, label.Value)
-               }
-
-               println("CNI Args: Net Name: ", n.ARGS.Mesos.NetworkInfo.Name)
-         }
-       }
-
-       for key, value := range labels {
-           println("Key:", key, "Value:", value)
-       }
-
-       println("CNI IPAM Name: ", n.IPAM.Name)
-
-       if n.IPAM.Args != nil { 
-               var args_ip net.IP
-               if n.IPAM.Args.IP != nil {
-                  args_ip = n.IPAM.Args.IP
-                  log.Println("IPAM args: n.IPAM.IP is:", args_ip)
-               }
-
-               var uplink types.UnmarshallableString
-               if n.IPAM.Args.UPLINK != "" {
-                  uplink = n.IPAM.Args.UPLINK
-                  log.Println("IPAM args: n.IPAM.UPLINK is:", uplink)
-               }
-       }
-
-       staticIP, found := labels["StaticIP"]
-       if found {
-          println("StaticIP is: ", staticIP)
-       }
-
-       bull, found := labels["bull"]
-       if !found {
-          println("Hard to believe, but bull not found")
-       } else {
-          println("Found: ", bull)
-       }
-
-       return n.IPAM, nil
-}
diff --git a/plugins/ipam/vz-local/dns.go b/plugins/ipam/vz-local/dns.go
new file mode 100644 (file)
index 0000000..1b3975a
--- /dev/null
@@ -0,0 +1,64 @@
+// 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
+
+import (
+       "bufio"
+       "os"
+       "strings"
+
+       "github.com/containernetworking/cni/pkg/types"
+)
+
+// parseResolvConf parses an existing resolv.conf in to a DNS struct
+func parseResolvConf(filename string) (*types.DNS, error) {
+       fp, err := os.Open(filename)
+       if err != nil {
+               return nil, err
+       }
+
+       dns := types.DNS{}
+       scanner := bufio.NewScanner(fp)
+       for scanner.Scan() {
+               line := scanner.Text()
+               line = strings.TrimSpace(line)
+
+               // Skip comments, empty lines
+               if len(line) == 0 || line[0] == '#' || line[0] == ';' {
+                       continue
+               }
+
+               fields := strings.Fields(line)
+               if len(fields) < 2 {
+                       continue
+               }
+               switch fields[0] {
+               case "nameserver":
+                       dns.Nameservers = append(dns.Nameservers, fields[1])
+               case "domain":
+                       dns.Domain = fields[1]
+               case "search":
+                       dns.Search = append(dns.Search, fields[1:]...)
+               case "options":
+                       dns.Options = append(dns.Options, fields[1:]...)
+               }
+       }
+
+       if err := scanner.Err(); err != nil {
+               return nil, err
+       }
+
+       return &dns, nil
+}
diff --git a/plugins/ipam/vz-local/dns_test.go b/plugins/ipam/vz-local/dns_test.go
new file mode 100644 (file)
index 0000000..4f3a05f
--- /dev/null
@@ -0,0 +1,80 @@
+// 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
+
+import (
+       "io/ioutil"
+       "os"
+
+       "github.com/containernetworking/cni/pkg/types"
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+)
+
+var _ = Describe("parsing resolv.conf", func() {
+       It("parses a simple resolv.conf file", func() {
+               contents := `
+               nameserver 192.0.2.0
+               nameserver 192.0.2.1
+               `
+               dns, err := parse(contents)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0", "192.0.2.1"}}))
+       })
+       It("ignores comments", func() {
+               dns, err := parse(`
+nameserver 192.0.2.0
+;nameserver 192.0.2.1
+`)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(*dns).Should(Equal(types.DNS{Nameservers: []string{"192.0.2.0"}}))
+       })
+       It("parses all fields", func() {
+               dns, err := parse(`
+nameserver 192.0.2.0
+nameserver 192.0.2.2
+domain example.com
+;nameserver comment
+#nameserver comment
+search example.net example.org
+search example.gov
+options one two three
+options four
+`)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(*dns).Should(Equal(types.DNS{
+                       Nameservers: []string{"192.0.2.0", "192.0.2.2"},
+                       Domain:      "example.com",
+                       Search:      []string{"example.net", "example.org", "example.gov"},
+                       Options:     []string{"one", "two", "three", "four"},
+               }))
+       })
+})
+
+func parse(contents string) (*types.DNS, error) {
+       f, err := ioutil.TempFile("", "host_local_resolv")
+       defer f.Close()
+       defer os.Remove(f.Name())
+
+       if err != nil {
+               return nil, err
+       }
+
+       if _, err := f.WriteString(contents); err != nil {
+               return nil, err
+       }
+
+       return parseResolvConf(f.Name())
+}
index 41dba71..52b1c79 100644 (file)
@@ -20,10 +20,13 @@ import (
        "net"
        "os"
        "path/filepath"
+       "strings"
 
        "github.com/containernetworking/cni/pkg/skel"
        "github.com/containernetworking/cni/pkg/testutils"
        "github.com/containernetworking/cni/pkg/types"
+       "github.com/containernetworking/cni/pkg/types/020"
+       "github.com/containernetworking/cni/pkg/types/current"
 
        . "github.com/onsi/ginkgo"
        . "github.com/onsi/gomega"
@@ -38,8 +41,76 @@ var _ = Describe("host-local Operations", func() {
                Expect(err).NotTo(HaveOccurred())
                defer os.RemoveAll(tmpDir)
 
+               err = ioutil.WriteFile(filepath.Join(tmpDir, "resolv.conf"), []byte("nameserver 192.0.2.3"), 0644)
+               Expect(err).NotTo(HaveOccurred())
+
                conf := fmt.Sprintf(`{
-    "cniVersion": "0.2.0",
+    "cniVersion": "0.3.0",
+    "name": "mynet",
+    "type": "ipvlan",
+    "master": "foo0",
+    "ipam": {
+        "type": "host-local",
+        "subnet": "10.1.2.0/24",
+        "dataDir": "%s",
+               "resolvConf": "%s/resolv.conf"
+    }
+}`, tmpDir, tmpDir)
+
+               args := &skel.CmdArgs{
+                       ContainerID: "dummy",
+                       Netns:       nspath,
+                       IfName:      ifname,
+                       StdinData:   []byte(conf),
+               }
+
+               // Allocate the IP
+               r, raw, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
+                       return cmdAdd(args)
+               })
+               Expect(err).NotTo(HaveOccurred())
+               Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
+
+               result, err := current.GetResult(r)
+               Expect(err).NotTo(HaveOccurred())
+
+               expectedAddress, err := types.ParseCIDR("10.1.2.2/24")
+               Expect(err).NotTo(HaveOccurred())
+               Expect(len(result.IPs)).To(Equal(1))
+               expectedAddress.IP = expectedAddress.IP.To16()
+               Expect(result.IPs[0].Address).To(Equal(*expectedAddress))
+               Expect(result.IPs[0].Gateway).To(Equal(net.ParseIP("10.1.2.1")))
+
+               ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
+               contents, err := ioutil.ReadFile(ipFilePath)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(string(contents)).To(Equal("dummy"))
+
+               lastFilePath := filepath.Join(tmpDir, "mynet", "last_reserved_ip")
+               contents, err = ioutil.ReadFile(lastFilePath)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(string(contents)).To(Equal("10.1.2.2"))
+
+               // Release the IP
+               err = testutils.CmdDelWithResult(nspath, ifname, func() error {
+                       return cmdDel(args)
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               _, err = os.Stat(ipFilePath)
+               Expect(err).To(HaveOccurred())
+       })
+
+       It("allocates and releases an address with ADD/DEL and 0.1.0 config", func() {
+               const ifname string = "eth0"
+               const nspath string = "/some/where"
+
+               tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
+               Expect(err).NotTo(HaveOccurred())
+               defer os.RemoveAll(tmpDir)
+
+               conf := fmt.Sprintf(`{
+    "cniVersion": "0.1.0",
     "name": "mynet",
     "type": "ipvlan",
     "master": "foo0",
@@ -58,16 +129,19 @@ var _ = Describe("host-local Operations", func() {
                }
 
                // Allocate the IP
-               result, err := testutils.CmdAddWithResult(nspath, ifname, func() error {
+               r, raw, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
                        return cmdAdd(args)
                })
                Expect(err).NotTo(HaveOccurred())
+               Expect(strings.Index(string(raw), "\"ip4\":")).Should(BeNumerically(">", 0))
+
+               result, err := types020.GetResult(r)
+               Expect(err).NotTo(HaveOccurred())
 
                expectedAddress, err := types.ParseCIDR("10.1.2.2/24")
                Expect(err).NotTo(HaveOccurred())
                expectedAddress.IP = expectedAddress.IP.To16()
                Expect(result.IP4.IP).To(Equal(*expectedAddress))
-
                Expect(result.IP4.Gateway).To(Equal(net.ParseIP("10.1.2.1")))
 
                ipFilePath := filepath.Join(tmpDir, "mynet", "10.1.2.2")
@@ -80,6 +154,59 @@ var _ = Describe("host-local Operations", func() {
                Expect(err).NotTo(HaveOccurred())
                Expect(string(contents)).To(Equal("10.1.2.2"))
 
+               Expect(result.DNS).To(Equal(types.DNS{Nameservers: []string{"192.0.2.3"}}))
+
+               // Release the IP
+               err = testutils.CmdDelWithResult(nspath, ifname, func() error {
+                       return cmdDel(args)
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               _, err = os.Stat(ipFilePath)
+               Expect(err).To(HaveOccurred())
+       })
+
+       It("ignores whitespace in disk files", func() {
+               const ifname string = "eth0"
+               const nspath string = "/some/where"
+
+               tmpDir, err := ioutil.TempDir("", "host_local_artifacts")
+               Expect(err).NotTo(HaveOccurred())
+               defer os.RemoveAll(tmpDir)
+
+               conf := fmt.Sprintf(`{
+    "cniVersion": "0.2.0",
+    "name": "mynet",
+    "type": "ipvlan",
+    "master": "foo0",
+    "ipam": {
+        "type": "host-local",
+        "subnet": "10.1.2.0/24",
+        "dataDir": "%s"
+    }
+}`, tmpDir)
+
+               args := &skel.CmdArgs{
+                       ContainerID: "   dummy\n ",
+                       Netns:       nspath,
+                       IfName:      ifname,
+                       StdinData:   []byte(conf),
+               }
+
+               // Allocate the IP
+               r, _, err := testutils.CmdAddWithResult(nspath, ifname, []byte(conf), func() error {
+                       return cmdAdd(args)
+               })
+               Expect(err).NotTo(HaveOccurred())
+
+               result, err := current.GetResult(r)
+               Expect(err).NotTo(HaveOccurred())
+
+               ipFilePath := filepath.Join(tmpDir, "mynet", result.IPs[0].Address.IP.String())
+               contents, err := ioutil.ReadFile(ipFilePath)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(string(contents)).To(Equal("dummy"))
+
                // Release the IP
                err = testutils.CmdDelWithResult(nspath, ifname, func() error {
                        return cmdDel(args)
index 19a3180..2f77912 100644 (file)
 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/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.Legacy)
+       skel.PluginMain(cmdAdd, cmdDel, version.All)
 }
 
 func cmdAdd(args *skel.CmdArgs) error {
-       ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
+       ipamConf, confVersion, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
        if err != nil {
                return err
        }
 
+       result := &current.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 := NewIPAllocator(ipamConf, store)
+       allocator, err := allocator.NewIPAllocator(ipamConf, store)
        if err != nil {
                return err
        }
 
-       ipConf, err := allocator.Get(args.ContainerID)
+       ipConf, routes, err := allocator.Get(args.ContainerID)
        if err != nil {
                return err
        }
+       result.IPs = []*current.IPConfig{ipConf}
+       result.Routes = routes
 
-       r := &types.Result{
-               IP4: ipConf,
-       }
-       return r.Print()
+       return types.PrintResult(result, confVersion)
 }
 
 func cmdDel(args *skel.CmdArgs) error {
-       ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
+       ipamConf, _, err := allocator.LoadIPAMConfig(args.StdinData, args.Args)
        if err != nil {
                return err
        }
@@ -66,10 +77,10 @@ func cmdDel(args *skel.CmdArgs) error {
        }
        defer store.Close()
 
-       allocator, err := NewIPAllocator(ipamConf, store)
+       ipAllocator, err := allocator.NewIPAllocator(ipamConf, store)
        if err != nil {
                return err
        }
 
-       return allocator.Release(args.ContainerID)
+       return ipAllocator.Release(args.ContainerID)
 }