-
Notifications
You must be signed in to change notification settings - Fork 228
/
ipam.go
217 lines (189 loc) · 8.34 KB
/
ipam.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
package main
import (
"context"
"encoding/json"
"io"
"net"
"github.com/Azure/azure-container-networking/azure-ipam/internal/buildinfo"
"github.com/Azure/azure-container-networking/azure-ipam/ipconfig"
"github.com/Azure/azure-container-networking/cns"
cnscli "github.com/Azure/azure-container-networking/cns/client"
cniSkel "github.com/containernetworking/cni/pkg/skel"
cniTypes "github.com/containernetworking/cni/pkg/types"
types100 "github.com/containernetworking/cni/pkg/types/100"
"github.com/pkg/errors"
"go.uber.org/zap"
)
// IPAMPlugin is the struct for the delegated azure-ipam plugin
// https://www.cni.dev/docs/spec/#section-4-plugin-delegation
type IPAMPlugin struct {
Name string
Version string
Options map[string]interface{}
logger *zap.Logger
cnsClient cnsClient
out io.Writer // indicate the output channel for the plugin
}
type cnsClient interface {
RequestIPAddress(context.Context, cns.IPConfigRequest) (*cns.IPConfigResponse, error)
RequestIPs(context.Context, cns.IPConfigsRequest) (*cns.IPConfigsResponse, error)
ReleaseIPs(context.Context, cns.IPConfigsRequest) error
ReleaseIPAddress(context.Context, cns.IPConfigRequest) error
}
// NewPlugin constructs a new IPAM plugin instance with given logger and CNS client
func NewPlugin(logger *zap.Logger, c cnsClient, out io.Writer) (*IPAMPlugin, error) {
plugin := &IPAMPlugin{
Name: pluginName,
Version: buildinfo.Version,
logger: logger,
out: out,
cnsClient: c,
}
return plugin, nil
}
//
// CNI implementation
// https://github.com/containernetworking/cni/blob/master/SPEC.md
//
// CmdAdd handles CNI add commands.
func (p *IPAMPlugin) CmdAdd(args *cniSkel.CmdArgs) error {
p.logger.Info("ADD called", zap.Any("args", args))
// Parsing network conf
nwCfg, err := parseNetConf(args.StdinData)
if err != nil {
p.logger.Error("Failed to parse CNI network config from stdin", zap.Error(err), zap.Any("argStdinData", args.StdinData))
return cniTypes.NewError(cniTypes.ErrDecodingFailure, err.Error(), "failed to parse CNI network config from stdin")
}
p.logger.Debug("Parsed network config", zap.Any("netconf", nwCfg))
// Create ip config request from args
req, err := ipconfig.CreateIPConfigsReq(args)
if err != nil {
p.logger.Error("Failed to create CNS IP configs request", zap.Error(err))
return cniTypes.NewError(ErrCreateIPConfigsRequest, err.Error(), "failed to create CNS IP configs request")
}
p.logger.Debug("Created CNS IP config request", zap.Any("request", req))
p.logger.Debug("Making request to CNS")
// if this fails, the caller plugin should execute again with cmdDel before returning error.
// https://www.cni.dev/docs/spec/#delegated-plugin-execution-procedure
resp, err := p.cnsClient.RequestIPs(context.TODO(), req)
if err != nil {
if cnscli.IsUnsupportedAPI(err) {
p.logger.Error("Failed to request IPs using RequestIPs from CNS, going to try RequestIPAddress", zap.Error(err), zap.Any("request", req))
ipconfigReq, err := ipconfig.CreateIPConfigReq(args)
if err != nil {
p.logger.Error("Failed to create CNS IP config request", zap.Error(err))
return cniTypes.NewError(ErrCreateIPConfigRequest, err.Error(), "failed to create CNS IP config request")
}
p.logger.Debug("Created CNS IP config request", zap.Any("request", ipconfigReq))
p.logger.Debug("Making request to CNS")
res, err := p.cnsClient.RequestIPAddress(context.TODO(), ipconfigReq)
// if the old API fails as well then we just return the error
if err != nil {
p.logger.Error("Failed to request IP address from CNS using RequestIPAddress", zap.Error(err), zap.Any("request", ipconfigReq))
return cniTypes.NewError(ErrRequestIPConfigFromCNS, err.Error(), "failed to request IP address from CNS using RequestIPAddress")
}
// takes values from the IPConfigResponse struct and puts them in a IPConfigsResponse struct
resp = &cns.IPConfigsResponse{
Response: res.Response,
PodIPInfo: []cns.PodIpInfo{
res.PodIpInfo,
},
}
} else {
p.logger.Error("Failed to request IP address from CNS", zap.Error(err), zap.Any("request", req))
return cniTypes.NewError(ErrRequestIPConfigFromCNS, err.Error(), "failed to request IP address from CNS")
}
}
p.logger.Debug("Received CNS IP config response", zap.Any("response", resp))
// Get Pod IP and gateway IP from ip config response
podIPNet, err := ipconfig.ProcessIPConfigsResp(resp)
if err != nil {
p.logger.Error("Failed to interpret CNS IPConfigResponse", zap.Error(err), zap.Any("response", resp))
return cniTypes.NewError(ErrProcessIPConfigResponse, err.Error(), "failed to interpret CNS IPConfigResponse")
}
cniResult := &types100.Result{}
cniResult.IPs = make([]*types100.IPConfig, len(*podIPNet))
for i, ipNet := range *podIPNet {
p.logger.Debug("Parsed pod IP", zap.String("podIPNet", ipNet.String()))
ipConfig := &types100.IPConfig{}
if ipNet.Addr().Is4() {
ipConfig.Address = net.IPNet{
IP: net.ParseIP(ipNet.Addr().String()),
Mask: net.CIDRMask(ipNet.Bits(), 32), // nolint
}
} else {
ipConfig.Address = net.IPNet{
IP: net.ParseIP(ipNet.Addr().String()),
Mask: net.CIDRMask(ipNet.Bits(), 128), // nolint
}
}
cniResult.IPs[i] = ipConfig
}
// Get versioned result
versionedCniResult, err := cniResult.GetAsVersion(nwCfg.CNIVersion)
if err != nil {
p.logger.Error("Failed to interpret CNI result with netconf CNI version", zap.Error(err), zap.Any("cniVersion", nwCfg.CNIVersion))
return cniTypes.NewError(cniTypes.ErrIncompatibleCNIVersion, err.Error(), "failed to interpret CNI result with netconf CNI version")
}
p.logger.Info("ADD success", zap.Any("result", versionedCniResult))
// Write result to output channel
err = versionedCniResult.PrintTo(p.out)
if err != nil {
p.logger.Error("Failed to print CNI result to output channel", zap.Error(err), zap.Any("result", versionedCniResult))
return cniTypes.NewError(cniTypes.ErrIOFailure, err.Error(), "failed to print CNI result to output channel")
}
return nil
}
// CmdDel handles CNI delete commands.
func (p *IPAMPlugin) CmdDel(args *cniSkel.CmdArgs) error {
p.logger.Info("DEL called", zap.Any("args", args))
// Create ip config request from args
req, err := ipconfig.CreateIPConfigsReq(args)
if err != nil {
p.logger.Error("Failed to create CNS IP configs request", zap.Error(err))
return cniTypes.NewError(cniTypes.ErrTryAgainLater, err.Error(), "failed to create CNS IP configs request")
}
p.logger.Debug("Created CNS IP config request", zap.Any("request", req))
p.logger.Debug("Making request to CNS")
// cnsClient enforces it own timeout
if err := p.cnsClient.ReleaseIPs(context.TODO(), req); err != nil {
// if we fail a request with a 404 error try using the old API
if cnscli.IsUnsupportedAPI(err) {
p.logger.Error("Failed to release IPs using ReleaseIPs from CNS, going to try ReleaseIPAddress", zap.Error(err), zap.Any("request", req))
ipconfigReq, err := ipconfig.CreateIPConfigReq(args)
if err != nil {
p.logger.Error("Failed to create CNS IP config request", zap.Error(err))
return cniTypes.NewError(ErrCreateIPConfigRequest, err.Error(), "failed to create CNS IP config request")
}
p.logger.Debug("Created CNS IP config request", zap.Any("request", ipconfigReq))
p.logger.Debug("Making request to CNS")
err = p.cnsClient.ReleaseIPAddress(context.TODO(), ipconfigReq)
if err != nil {
p.logger.Error("Failed to release IP address to CNS using ReleaseIPAddress", zap.Error(err), zap.Any("request", ipconfigReq))
return cniTypes.NewError(ErrRequestIPConfigFromCNS, err.Error(), "failed to release IP address from CNS using ReleaseIPAddress")
}
} else {
p.logger.Error("Failed to release IP addresses from CNS", zap.Error(err), zap.Any("request", req))
return cniTypes.NewError(cniTypes.ErrTryAgainLater, err.Error(), "failed to release IP addresses from CNS")
}
}
p.logger.Info("DEL success")
return nil
}
// CmdCheck handles CNI check command - not implemented
func (p *IPAMPlugin) CmdCheck(args *cniSkel.CmdArgs) error {
p.logger.Info("CHECK called")
return nil
}
// Parse network config from given byte array
func parseNetConf(b []byte) (*cniTypes.NetConf, error) {
netConf := &cniTypes.NetConf{}
err := json.Unmarshal(b, netConf)
if err != nil {
return nil, errors.Wrapf(err, "failed to unmarshal net conf")
}
if netConf.CNIVersion == "" {
netConf.CNIVersion = "0.2.0" // default CNI version
}
return netConf, nil
}