+++ /dev/null
-// 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
-}
# 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
"rangeEnd": "3ffe:ffff:0:01ff::0020",
"routes": [
{ "dst": "3ffe:ffff:0:01ff::1/64" }
- ]
+ ],
+ "resolvConf": "/etc/resolv.conf"
}
}
```
// 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/ip"
"github.com/containernetworking/cni/pkg/types"
+ "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/plugins/ipam/host-local/backend"
)
}
// 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()
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 := ¤t.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()
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 := ¤t.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
// 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"
"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"
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{
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() {
}
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))
}
})
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())
})
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() {
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())
})
}
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() {
}
store := fakestore.NewFakeStore(ipmap, nil)
alloc, _ := NewIPAllocator(&conf, store)
- _, err = alloc.Get("ID")
+ _, _, err = alloc.Get("ID")
Expect(err).To(HaveOccurred())
})
}
store := fakestore.NewFakeStore(ipmap, nil)
alloc, _ := NewIPAllocator(&conf, store)
- _, err = alloc.Get("ID")
+ _, _, err = alloc.Get("ID")
Expect(err).To(HaveOccurred())
})
})
},
}
for _, tc := range testCases {
- _, err := tc.run()
+ _, _, err := tc.run()
Expect(err).To(MatchError("no IP addresses available in network: test"))
}
})
--- /dev/null
+// 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
+}
"net"
"os"
"path/filepath"
+ "strings"
+
+ "github.com/containernetworking/cni/plugins/ipam/host-local/backend"
)
const lastIPFile = "last_reserved_ip"
dataDir string
}
+// Store implements the Store interface
+var _ backend.Store = &Store{}
+
func New(network, dataDir string) (*Store, error) {
if dataDir == "" {
dataDir = defaultDataDir
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
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
}
import (
"net"
+
+ "github.com/containernetworking/cni/plugins/ipam/host-local/backend"
)
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}
}
+++ /dev/null
-// 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
-}
--- /dev/null
+// 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
+}
--- /dev/null
+// 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())
+}
"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"
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",
}
// 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")
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)
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 := ¤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 := 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
}
}
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)
}