Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

add support for ips in ipam config #3754

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ipam/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ func (c *claim) Try(alloc *Allocator) bool {
// but we also cannot prove otherwise, so we let it reclaim the address:
alloc.debugln("Re-Claimed", c.cidr, "for ID", c.ident, "having existing ID as", existingIdent)
c.sendResult(nil)
case c.ident == "weave:expose":
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bboreham Is weave:expose even a valid identifier for this function? AFAICT this is supposed to be a container ID, so it seems like there is a bug where weave:expose calls this function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The weave expose command allocates an address for the local machine on the Weave network.
Rather than have a whole different path for that, we pretend we are allocating an address for a container with ID weave:expose.

alloc.debugln("Ignoring weave:expose")
c.sendResult(nil)
Comment on lines +131 to +133
Copy link
Author

@nonsense nonsense Jan 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bboreham I am not entirely sure what's going on here. Without this case that ignores weave:expose, this is what I see in the weave logs:

DEBU: 2021/01/25 17:56:56.533494 [http] PUT /ip/99cb8ae4fe35aa7992bc1ae439fcd07bc464b9ccc846ec6140d3860665adedaf/10.32.1.30/8
INFO: 2021/01/25 17:56:56.533603 Assuming quorum size of 1
DEBU: 2021/01/25 17:56:56.533612 [allocator 32:7d:23:67:8a:ff] Paxos proposing
DEBU: 2021/01/25 17:56:56.533644 [allocator 32:7d:23:67:8a:ff]: Paxos consensus: [32:7d:23:67:8a:ff]
DEBU: 2021/01/25 17:56:56.536546 [allocator 32:7d:23:67:8a:ff]: Claimed 10.32.1.30/8 for 99cb8ae4fe35aa7992bc1ae439fcd07bc464b9ccc846ec6140d3860665adedaf
DEBU: 2021/01/25 17:56:56.537963 [ring 32:7d:23:67:8a:ff]: ReportFree token=10.32.0.0 peer=32:7d:23:67:8a:ff version=1
DEBU: 2021/01/25 17:56:56.538721 [http] PUT /ip/weave:expose/10.32.1.30/8
WARN: 2021/01/25 17:56:56.540951 [allocator]: Unable to claim: address 10.32.1.30/8 is already owned by 99cb8ae4fe35aa7992bc1ae439fcd07bc464b9ccc846ec6140d3860665adedaf ; i am weave:expose

I added the i am weave:expose part, which corresponds to c.ident. I am not sure why func (c* claim) Try is called with weave:expose for ident, after an IP has been allocated to the container.


Looks like PUT /ip/weave:expose/10.32.1.30/8 is entering a check for the claim on the IP, and then complains that a container owns the IP already.

default:
// Addr already owned by container on this machine
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.cidr.String(), existingIdent))
Expand Down
81 changes: 66 additions & 15 deletions plugin/ipam/cni.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ipamplugin

import (
"encoding/json"
"errors"
"fmt"
"net"

Expand Down Expand Up @@ -35,31 +36,80 @@ func (i *Ipam) Allocate(args *skel.CmdArgs) (types.Result, error) {
if containerID == "" {
return nil, fmt.Errorf("Weave CNI Allocate: blank container name")
}
var ipnet *net.IPNet

if conf.Subnet == "" {
ipnet, err = i.weave.AllocateIP(containerID, false)
var ipconfigs []*current.IPConfig

if len(conf.IPs) > 0 {
Copy link
Author

@nonsense nonsense May 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bboreham I am a bit confused with how to support >1 IPs passed to the CNI via a NetworkConfigList, since the signature of AddNetworkList is:

func (c *CNIConfig) AddNetworkList(ctx context.Context, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {

and RuntimeConf includes:

type RuntimeConf struct {
	ContainerID string
	NetNS       string
	IfName      string
...

Can a given interface (i.e. eth1) have multiple IP addresses? I am assuming we set IfName to eth1 for example.

I hope that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, an interface can have many IP addresses. These days one IPv4 and one IPv6 is quite common (though not in Weave Net).

What you did is OK, though the Weave Net CNI plugin will currently only use the first address:

weave/plugin/net/cni.go

Lines 85 to 86 in b04217d

// Only expecting one address
ip := result.IPs[0]

// configuration includes desired IPs

var ips []net.IP
for _, ip := range conf.IPs {
ip4 := net.ParseIP(ip).To4()
ip16 := net.ParseIP(ip).To16()

if ip4 == nil && ip16 == nil {
return nil, errors.New("provided value is not an IP")
}

if ip4 == nil && ip16 != nil {
return nil, errors.New("allocation of ipv6 addresses is not implemented")
nonsense marked this conversation as resolved.
Show resolved Hide resolved
}

ips = append(ips, ip4)
}

for j := range ips {
ipnet := &net.IPNet{
IP: ips[j],
Mask: ips[j].DefaultMask(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong; the IPs are coming back as 10.32.1.42/8 rather than 10.32.1.42/12 which would match the default subnet used by Weave Net.

}

err := i.weave.ClaimIP(containerID, ipnet, false)
if err != nil {
return nil, err
}

ipconfigs = append(ipconfigs, &current.IPConfig{
Version: "4",
Address: *ipnet,
Gateway: conf.Gateway,
})
}
} else if conf.Subnet == "" {
// configuration doesn't include Subnet or IPs, so ask the allocator for an IP
ipnet, err := i.weave.AllocateIP(containerID, false)
if err != nil {
return nil, err
}

ipconfigs = append(ipconfigs, &current.IPConfig{
Version: "4",
Address: *ipnet,
Gateway: conf.Gateway,
})
} else {
var subnet *net.IPNet
subnet, err = types.ParseCIDR(conf.Subnet)
// configuration includes desired Subnet

subnet, err := types.ParseCIDR(conf.Subnet)
if err != nil {
return nil, fmt.Errorf("subnet given in config, but not parseable: %s", err)
}
ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet, false)
}
ipnet, err := i.weave.AllocateIPInSubnet(containerID, subnet, false)
if err != nil {
return nil, err
}

if err != nil {
return nil, err
}
result := &current.Result{
IPs: []*current.IPConfig{{
ipconfigs = append(ipconfigs, &current.IPConfig{
Version: "4",
Address: *ipnet,
Gateway: conf.Gateway,
}},
Routes: conf.Routes,
})
}
return result, nil

return &current.Result{
IPs: ipconfigs,
Routes: conf.Routes,
}, nil
}

func (i *Ipam) CmdDel(args *skel.CmdArgs) error {
Expand All @@ -74,6 +124,7 @@ type ipamConf struct {
Subnet string `json:"subnet,omitempty"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []*types.Route `json:"routes"`
IPs []string `json:"ips,omitempty"`
}

type netConf struct {
Expand Down
107 changes: 107 additions & 0 deletions test/880_cni_plugin_ip_address_assignment_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#! /bin/bash

. "$(dirname "$0")/config.sh"

start_suite "Test CNI plugin with static IP allocation"

cni_connect() {
pid=$(container_pid $1 $2)
id=$(docker_on $1 inspect -f '{{.Id}}' $2)
run_on $1 sudo CNI_COMMAND=ADD CNI_CONTAINERID=$id CNI_IFNAME=eth0 \
CNI_NETNS=/proc/$pid/ns/net CNI_PATH=/opt/cni/bin /opt/cni/bin/weave-net
}

run_on $HOST1 sudo mkdir -p /opt/cni/bin
# setup-cni is a subset of 'weave setup', without doing any 'docker pull's
weave_on $HOST1 setup-cni --log-level=debug
weave_on $HOST1 launch --log-level=debug

C0=$(docker_on $HOST1 run --net=none --name=c0 --privileged -dt $SMALL_IMAGE /bin/sh)
C1=$(docker_on $HOST1 run --net=none --name=c1 --privileged -dt $SMALL_IMAGE /bin/sh)
C2=$(docker_on $HOST1 run --net=none --name=c2 --privileged -dt $SMALL_IMAGE /bin/sh)

# Enable unsolicited ARPs so that ping after the address reuse does not time out
exec_on $HOST1 c1 sysctl -w net.ipv4.conf.all.arp_accept=1

cni_connect $HOST1 c0 <<EOF
{
"name": "weave",
"type": "weave-net",
"ipam": {
"type": "weave-ipam",
"ips": [
"10.32.1.30"
]
},
"hairpinMode": true
}
EOF

cni_connect $HOST1 c1 <<EOF
{
"name": "weave",
"type": "weave-net",
"ipam": {
"type": "weave-ipam",
"ips": [
"10.32.1.40"
]
},
"hairpinMode": true
}
EOF

cni_connect $HOST1 c2 <<EOF
{
"name": "weave",
"type": "weave-net",
"ipam": {
"type": "weave-ipam",
"ips": [
"10.32.1.42"
]
},
"hairpinMode": true
}
EOF


C0IP=$(container_ip $HOST1 c0)
C1IP=$(container_ip $HOST1 c1)
C2IP=$(container_ip $HOST1 c2)

assert_raises "[ "10.32.1.30" == $C0IP ]"
assert_raises "[ "10.32.1.40" == $C1IP ]"
assert_raises "[ "10.32.1.42" == $C2IP ]"

BRIP=$(container_ip $HOST1 weave:expose)
# Check the bridge IP is different from the container IPs
assert_raises "[ $BRIP != $C0IP ]"
assert_raises "[ $BRIP != $C1IP ]"
assert_raises "[ $BRIP != $C2IP ]"

# Containers should be able to reach one another
assert_raises "exec_on $HOST1 c0 $PING $C1IP"
assert_raises "exec_on $HOST1 c1 $PING $C2IP"
assert_raises "exec_on $HOST1 c2 $PING $C1IP"

# Containers should not have a default route to the world
assert_raises "exec_on $HOST1 c0 sh -c '! $PING 8.8.8.8'"
assert_raises "exec_on $HOST1 c1 sh -c '! $PING 8.8.8.8'"
assert_raises "exec_on $HOST1 c2 sh -c '! $PING 8.8.8.8'"

# Ensure existing containers can reclaim their IP addresses after CNI has been used -- see #2548
stop_weave_on $HOST1

# Ensure no warning is printed to the standard error:
ACTUAL_OUTPUT=$(CHECKPOINT_DISABLE="$CHECKPOINT_DISABLE" DOCKER_HOST=tcp://$HOST1:$DOCKER_PORT $WEAVE launch 2>&1)
EXPECTED_OUTPUT=$($SSH $HOST1 docker inspect --format="{{.Id}}" weave)

assert_raises "[ $EXPECTED_OUTPUT == $ACTUAL_OUTPUT ]"

assert "$SSH $HOST1 \"curl -s -X GET 127.0.0.1:6784/ip/$C1 | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'\"" "$C1IP"
assert "$SSH $HOST1 \"curl -s -X GET 127.0.0.1:6784/ip/$C2 | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'\"" "$C2IP"
assert "$SSH $HOST1 \"curl -s -X GET 127.0.0.1:6784/ip/$C3 | grep -o -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'\"" "$C3IP"


end_suite