Skip to content

Commit bae7072

Browse files
committed
Added initial CNI support
1 parent 8a9e482 commit bae7072

File tree

4 files changed

+685
-0
lines changed

4 files changed

+685
-0
lines changed

cni/cni.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright Microsoft Corp.
2+
// All rights reserved.
3+
4+
package cni
5+
6+
import (
7+
"encoding/json"
8+
9+
cniSkel "github.com/containernetworking/cni/pkg/skel"
10+
cniTypes "github.com/containernetworking/cni/pkg/types"
11+
)
12+
13+
// Plugin is the interface implemented by CNI plugins.
14+
type Plugin interface {
15+
Add(args *cniSkel.CmdArgs) error
16+
Delete(args *cniSkel.CmdArgs) error
17+
18+
AddImpl(args *cniSkel.CmdArgs, nwCfg *NetworkConfig) (*cniTypes.Result, error)
19+
DeleteImpl(args *cniSkel.CmdArgs, nwCfg *NetworkConfig) (*cniTypes.Result, error)
20+
}
21+
22+
// NetworkConfig represents the Azure CNI plugin's network configuration.
23+
type NetworkConfig struct {
24+
CniVersion string `json:"cniVersion"`
25+
Name string `json:"name"`
26+
Type string `json:"type"`
27+
Bridge string `json:"bridge"`
28+
IfName string `json:"ifName"`
29+
Ipam struct {
30+
Type string `json:"type"`
31+
AddrSpace string `json:"addressSpace"`
32+
Subnet string `json:"subnet"`
33+
Address string `json:"ipAddress"`
34+
Result string `json:"result"`
35+
}
36+
}
37+
38+
// ParseNetworkConfig unmarshals network configuration from bytes.
39+
func ParseNetworkConfig(b []byte) (*NetworkConfig, error) {
40+
nwCfg := NetworkConfig{}
41+
42+
err := json.Unmarshal(b, &nwCfg)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
return &nwCfg, nil
48+
}
49+
50+
// Serialize marshals a network configuration to bytes.
51+
func (nwcfg *NetworkConfig) Serialize() []byte {
52+
bytes, _ := json.Marshal(nwcfg)
53+
return bytes
54+
}

cni/ipam/ipam.go

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// Copyright Microsoft Corp.
2+
// All rights reserved.
3+
4+
package ipam
5+
6+
import (
7+
"net"
8+
9+
"github.com/Azure/azure-container-networking/cni"
10+
"github.com/Azure/azure-container-networking/common"
11+
"github.com/Azure/azure-container-networking/ipam"
12+
"github.com/Azure/azure-container-networking/log"
13+
14+
cniSkel "github.com/containernetworking/cni/pkg/skel"
15+
cniTypes "github.com/containernetworking/cni/pkg/types"
16+
)
17+
18+
const (
19+
// Plugin name.
20+
name = "ipam"
21+
22+
// The default address space ID used when an explicit ID is not specified.
23+
defaultAddressSpaceId = "LocalDefaultAddressSpace"
24+
)
25+
26+
// IpamPlugin represents a CNI IPAM plugin.
27+
type ipamPlugin struct {
28+
*common.Plugin
29+
am ipam.AddressManager
30+
}
31+
32+
// NewPlugin creates a new ipamPlugin object.
33+
func NewPlugin(config *common.PluginConfig) (*ipamPlugin, error) {
34+
// Setup base plugin.
35+
plugin, err := common.NewPlugin(name, config.Version)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
// Setup address manager.
41+
am, err := ipam.NewAddressManager()
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
config.IpamApi = am
47+
48+
return &ipamPlugin{
49+
Plugin: plugin,
50+
am: am,
51+
}, nil
52+
}
53+
54+
// Starts the plugin.
55+
func (plugin *ipamPlugin) Start(config *common.PluginConfig) error {
56+
// Initialize base plugin.
57+
err := plugin.Initialize(config)
58+
if err != nil {
59+
log.Printf("[ipam] Failed to initialize base plugin, err:%v.", err)
60+
return err
61+
}
62+
63+
// Initialize address manager.
64+
environment := plugin.GetOption(common.OptEnvironmentKey)
65+
err = plugin.am.Initialize(config, environment)
66+
if err != nil {
67+
log.Printf("[ipam] Failed to initialize address manager, err:%v.", err)
68+
return err
69+
}
70+
71+
log.Printf("[ipam] Plugin started.")
72+
73+
return nil
74+
}
75+
76+
// Stops the plugin.
77+
func (plugin *ipamPlugin) Stop() {
78+
plugin.am.Uninitialize()
79+
plugin.Uninitialize()
80+
log.Printf("[ipam] Plugin stopped.")
81+
}
82+
83+
//
84+
// CNI implementation
85+
// https://github.com/containernetworking/cni/blob/master/SPEC.md
86+
//
87+
88+
// Add handles CNI add commands.
89+
func (plugin *ipamPlugin) Add(args *cniSkel.CmdArgs) error {
90+
log.Printf("[ipam] Processing ADD command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
91+
args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)
92+
93+
// Parse network configuration from stdin.
94+
nwCfg, err := cni.ParseNetworkConfig(args.StdinData)
95+
if err != nil {
96+
log.Printf("[ipam] Failed to parse network configuration: %v.", err)
97+
return nil
98+
}
99+
100+
log.Printf("[ipam] Read network configuration %+v.", nwCfg)
101+
102+
// Assume default address space if not specified.
103+
if nwCfg.Ipam.AddrSpace == "" {
104+
nwCfg.Ipam.AddrSpace = defaultAddressSpaceId
105+
}
106+
107+
// Check if an address pool is specified.
108+
if nwCfg.Ipam.Subnet == "" {
109+
// Allocate an address pool.
110+
poolId, subnet, err := plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", nil, false)
111+
if err != nil {
112+
log.Printf("[ipam] Failed to allocate pool, err:%v.", err)
113+
return nil
114+
}
115+
116+
nwCfg.Ipam.Subnet = subnet
117+
log.Printf("[ipam] Allocated address poolId %v with subnet %v.", poolId, subnet)
118+
}
119+
120+
// Allocate an address for the endpoint.
121+
address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, nil)
122+
if err != nil {
123+
log.Printf("[ipam] Failed to allocate address, err:%v.", err)
124+
return nil
125+
}
126+
127+
log.Printf("[ipam] Allocated address %v.", address)
128+
129+
// Output the result.
130+
ip, cidr, err := net.ParseCIDR(address)
131+
cidr.IP = ip
132+
if err != nil {
133+
log.Printf("[ipam] Failed to parse address, err:%v.", err)
134+
return nil
135+
}
136+
137+
result := &cniTypes.Result{
138+
IP4: &cniTypes.IPConfig{IP: *cidr},
139+
}
140+
141+
// Output response.
142+
if nwCfg.Ipam.Result == "" {
143+
result.Print()
144+
} else {
145+
args.Args = result.String()
146+
}
147+
148+
log.Printf("[ipam] ADD succeeded with output %+v.", result)
149+
150+
return err
151+
}
152+
153+
// Delete handles CNI delete commands.
154+
func (plugin *ipamPlugin) Delete(args *cniSkel.CmdArgs) error {
155+
log.Printf("[ipam] Processing DEL command with args {ContainerID:%v Netns:%v IfName:%v Args:%v Path:%v}.",
156+
args.ContainerID, args.Netns, args.IfName, args.Args, args.Path)
157+
158+
// Parse network configuration from stdin.
159+
nwCfg, err := cni.ParseNetworkConfig(args.StdinData)
160+
if err != nil {
161+
log.Printf("[ipam] Failed to parse network configuration: %v.", err)
162+
return nil
163+
}
164+
165+
log.Printf("[ipam] Read network configuration %+v.", nwCfg)
166+
167+
// Process command.
168+
result, err := plugin.DeleteImpl(args, nwCfg)
169+
if err != nil {
170+
log.Printf("[ipam] Failed to process command: %v.", err)
171+
return nil
172+
}
173+
174+
// Output response.
175+
if result != nil {
176+
result.Print()
177+
}
178+
179+
log.Printf("[ipam] DEL succeeded with output %+v.", result)
180+
181+
return err
182+
}
183+
184+
// AddImpl handles CNI add commands.
185+
func (plugin *ipamPlugin) AddImpl(args *cniSkel.CmdArgs, nwCfg *cni.NetworkConfig) (*cniTypes.Result, error) {
186+
// Assume default address space if not specified.
187+
if nwCfg.Ipam.AddrSpace == "" {
188+
nwCfg.Ipam.AddrSpace = defaultAddressSpaceId
189+
}
190+
191+
// Check if an address pool is specified.
192+
if nwCfg.Ipam.Subnet == "" {
193+
// Allocate an address pool.
194+
poolId, subnet, err := plugin.am.RequestPool(nwCfg.Ipam.AddrSpace, "", "", nil, false)
195+
if err != nil {
196+
log.Printf("[ipam] Failed to allocate pool, err:%v.", err)
197+
return nil, err
198+
}
199+
200+
nwCfg.Ipam.Subnet = subnet
201+
log.Printf("[ipam] Allocated address poolId %v with subnet %v.", poolId, subnet)
202+
}
203+
204+
// Allocate an address for the endpoint.
205+
address, err := plugin.am.RequestAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address, nil)
206+
if err != nil {
207+
log.Printf("[ipam] Failed to allocate address, err:%v.", err)
208+
return nil, err
209+
}
210+
211+
log.Printf("[ipam] Allocated address %v.", address)
212+
213+
// Output the result.
214+
ip, cidr, err := net.ParseCIDR(address)
215+
cidr.IP = ip
216+
if err != nil {
217+
log.Printf("[ipam] Failed to parse address, err:%v.", err)
218+
return nil, err
219+
}
220+
221+
result := &cniTypes.Result{
222+
IP4: &cniTypes.IPConfig{IP: *cidr},
223+
}
224+
225+
return result, nil
226+
}
227+
228+
// DeleteImpl handles CNI delete commands.
229+
func (plugin *ipamPlugin) DeleteImpl(args *cniSkel.CmdArgs, nwCfg *cni.NetworkConfig) (*cniTypes.Result, error) {
230+
// Assume default address space if not specified.
231+
if nwCfg.Ipam.AddrSpace == "" {
232+
nwCfg.Ipam.AddrSpace = defaultAddressSpaceId
233+
}
234+
235+
// If an address is specified, release that address. Otherwise, release the pool.
236+
if nwCfg.Ipam.Address != "" {
237+
// Release the address.
238+
err := plugin.am.ReleaseAddress(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet, nwCfg.Ipam.Address)
239+
if err != nil {
240+
log.Printf("[cni] Failed to release address, err:%v.", err)
241+
return nil, err
242+
}
243+
} else {
244+
// Release the pool.
245+
err := plugin.am.ReleasePool(nwCfg.Ipam.AddrSpace, nwCfg.Ipam.Subnet)
246+
if err != nil {
247+
log.Printf("[cni] Failed to release pool, err:%v.", err)
248+
return nil, err
249+
}
250+
}
251+
252+
return nil, nil
253+
}

0 commit comments

Comments
 (0)