Merge pull request #42 from steveeJ/plugins-args
host-local: allow ip request via CNI_ARGS
This commit is contained in:
commit
fbd828cf60
@ -31,6 +31,11 @@ It stores the state locally on the host filesystem, therefore ensuring uniquenes
|
|||||||
* `gateway` (string, optional): IP inside of "subnet" to designate as the gateway. Defaults to ".1" IP inside of the "subnet" block.
|
* `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.
|
* `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
|
## Files
|
||||||
|
|
||||||
Allocated IP addresses are stored as files in /var/lib/cni/networks/$NETWORK_NAME.
|
Allocated IP addresses are stored as files in /var/lib/cni/networks/$NETWORK_NAME.
|
||||||
|
36
pkg/plugin/args.go
Normal file
36
pkg/plugin/args.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -78,6 +78,40 @@ func (a *IPAllocator) Get(id string) (*plugin.IPConfig, error) {
|
|||||||
gw = ip.NextIP(a.conf.Subnet.IP)
|
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) {
|
for cur := a.start; !cur.Equal(a.end); cur = ip.NextIP(cur) {
|
||||||
// don't allocate gateway IP
|
// don't allocate gateway IP
|
||||||
if gw != nil && cur.Equal(gw) {
|
if gw != nil && cur.Equal(gw) {
|
||||||
@ -96,7 +130,6 @@ func (a *IPAllocator) Get(id string) (*plugin.IPConfig, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name)
|
return nil, fmt.Errorf("no IP addresses available in network: %s", a.conf.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,11 @@ type IPAMConfig struct {
|
|||||||
Subnet ip.IPNet `json:"subnet"`
|
Subnet ip.IPNet `json:"subnet"`
|
||||||
Gateway net.IP `json:"gateway"`
|
Gateway net.IP `json:"gateway"`
|
||||||
Routes []plugin.Route `json:"routes"`
|
Routes []plugin.Route `json:"routes"`
|
||||||
|
Args *IPAMArgs `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPAMArgs struct {
|
||||||
|
IP net.IP `json:"ip",omitempty`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Net struct {
|
type Net struct {
|
||||||
@ -40,12 +45,20 @@ type Net struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewIPAMConfig creates a NetworkConfig from the given network name.
|
// 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{}
|
n := Net{}
|
||||||
if err := json.Unmarshal(bytes, &n); err != nil {
|
if err := json.Unmarshal(bytes, &n); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args != "" {
|
||||||
|
ipamArgs := IPAMArgs{}
|
||||||
|
err := plugin.LoadArgs(args, &ipamArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if n.IPAM == nil {
|
if n.IPAM == nil {
|
||||||
return nil, fmt.Errorf("%q missing 'ipam' key")
|
return nil, fmt.Errorf("%q missing 'ipam' key")
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdAdd(args *skel.CmdArgs) error {
|
func cmdAdd(args *skel.CmdArgs) error {
|
||||||
ipamConf, err := LoadIPAMConfig(args.StdinData)
|
ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -39,6 +39,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
defer store.Close()
|
defer store.Close()
|
||||||
|
|
||||||
|
ipamArgs := IPAMArgs{}
|
||||||
|
err = plugin.LoadArgs(args.Args, &ipamArgs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ipamConf.Args = &ipamArgs
|
||||||
|
|
||||||
allocator, err := NewIPAllocator(ipamConf, store)
|
allocator, err := NewIPAllocator(ipamConf, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -66,7 +73,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cmdDel(args *skel.CmdArgs) error {
|
func cmdDel(args *skel.CmdArgs) error {
|
||||||
ipamConf, err := LoadIPAMConfig(args.StdinData)
|
ipamConf, err := LoadIPAMConfig(args.StdinData, args.Args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user