A specific IP can now be requested via the environment variable CNI_ARGS, e.g.
`CNI_ARGS=ip=1.2.3.4`.
The plugin will try to reserve the specified IP.
If this is not successful the execution will fail.
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
* `routes` (string, optional): list of routes to add to the container namespace. Each route is a dictionary with "dst" and optional "gw" fields. If "gw" is omitted, value of "gateway" will be used.
+## Supported arguments
+The following [CNI_ARGS](https://github.com/appc/cni/blob/master/SPEC.md#parameters) are supported:
+
+* `ip`: request a specific IP address from the subnet. If it's not available, the plugin will exit with an error
+
## Files
Allocated IP addresses are stored as files in /var/lib/cni/networks/$NETWORK_NAME.
--- /dev/null
+package plugin
+
+import (
+ "encoding"
+ "fmt"
+ "reflect"
+ "strings"
+)
+
+func LoadArgs(args string, container interface{}) error {
+ if args == "" {
+ return nil
+ }
+
+ containerValue := reflect.ValueOf(container)
+
+ pairs := strings.Split(args, ",")
+ for _, pair := range pairs {
+ kv := strings.Split(pair, "=")
+ if len(kv) != 2 {
+ return fmt.Errorf("ARGS: invalid pair %q", pair)
+ }
+ keyString := kv[0]
+ valueString := kv[1]
+ keyField := containerValue.Elem().FieldByName(keyString)
+ if !keyField.IsValid() {
+ return fmt.Errorf("ARGS: invalid key %q", keyString)
+ }
+ u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
+ err := u.UnmarshalText([]byte(valueString))
+ if err != nil {
+ return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
+ }
+ }
+ return 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, 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)
+ if err != nil {
+ return nil, err
+ }
+
+ reserved, err := a.store.Reserve(id, requestedIP)
+ if err != nil {
+ return nil, err
+ }
+
+ if reserved {
+ return &plugin.IPConfig{
+ IP: net.IPNet{requestedIP, 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)
+ }
+
for cur := a.start; !cur.Equal(a.end); cur = ip.NextIP(cur) {
// don't allocate gateway IP
if gw != nil && cur.Equal(gw) {
}, nil
}
}
-
return nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name)
}
Subnet ip.IPNet `json:"subnet"`
Gateway net.IP `json:"gateway"`
Routes []plugin.Route `json:"routes"`
+ Args *IPAMArgs `json:"-"`
+}
+
+type IPAMArgs struct {
+ IP net.IP `json:"ip",omitempty`
}
type Net struct {
}
// NewIPAMConfig creates a NetworkConfig from the given network name.
-func LoadIPAMConfig(bytes []byte) (*IPAMConfig, error) {
+func LoadIPAMConfig(bytes []byte, args string) (*IPAMConfig, error) {
n := Net{}
if err := json.Unmarshal(bytes, &n); err != nil {
return nil, err
}
+ if args != "" {
+ ipamArgs := IPAMArgs{}
+ err := plugin.LoadArgs(args, &ipamArgs)
+ if err != nil {
+ return nil, err
+ }
+ }
+
if n.IPAM == nil {
return nil, fmt.Errorf("%q missing 'ipam' key")
}
}
func cmdAdd(args *skel.CmdArgs) error {
- ipamConf, err := LoadIPAMConfig(args.StdinData)
+ ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}
}
defer store.Close()
+ ipamArgs := IPAMArgs{}
+ err = plugin.LoadArgs(args.Args, &ipamArgs)
+ if err != nil {
+ return err
+ }
+ ipamConf.Args = &ipamArgs
+
allocator, err := NewIPAllocator(ipamConf, store)
if err != nil {
return err
}
func cmdDel(args *skel.CmdArgs) error {
- ipamConf, err := LoadIPAMConfig(args.StdinData)
+ ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
if err != nil {
return err
}