14 "github.com/sirupsen/logrus"
15 "github.com/urfave/cli"
17 "github.com/containernetworking/cni/pkg/skel"
18 "github.com/containernetworking/cni/pkg/types"
19 "github.com/containernetworking/cni/pkg/types/current"
20 "github.com/containernetworking/cni/pkg/version"
21 "k8s.io/apimachinery/pkg/util/wait"
23 kexec "k8s.io/utils/exec"
24 "ovn4nfv-k8s-plugin/internal/pkg/kube"
26 "ovn4nfv-k8s-plugin/cmd/ovn4nfvk8s-cni/app"
27 "ovn4nfv-k8s-plugin/internal/pkg/config"
30 func argString2Map(args string) (map[string]string, error) {
31 argsMap := make(map[string]string)
33 pairs := strings.Split(args, ";")
34 for _, pair := range pairs {
35 kv := strings.Split(pair, "=")
37 return nil, fmt.Errorf("ARGS: invalid pair %q", pair)
41 argsMap[keyString] = valueString
47 func parseOvnNetworkObject(ovnnetwork string) ([]map[string]string, error) {
48 var ovnNet []map[string]string
51 return nil, fmt.Errorf("parseOvnNetworkObject:error")
54 if err := json.Unmarshal([]byte(ovnnetwork), &ovnNet); err != nil {
55 return nil, fmt.Errorf("parseOvnNetworkObject: failed to load ovn network err: %v | ovn network: %v", err, ovnnetwork)
61 func mergeWithResult(srcObj, dstObj types.Result) (types.Result, error) {
66 src, err := current.NewResultFromResult(srcObj)
68 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
70 dst, err := current.NewResultFromResult(dstObj)
72 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
75 ifacesLength := len(dst.Interfaces)
77 for _, iface := range src.Interfaces {
78 dst.Interfaces = append(dst.Interfaces, iface)
80 for _, ip := range src.IPs {
81 if ip.Interface != nil && *(ip.Interface) != -1 {
82 ip.Interface = current.Int(*(ip.Interface) + ifacesLength)
84 dst.IPs = append(dst.IPs, ip)
86 for _, route := range src.Routes {
87 dst.Routes = append(dst.Routes, route)
90 for _, ns := range src.DNS.Nameservers {
91 dst.DNS.Nameservers = append(dst.DNS.Nameservers, ns)
93 for _, s := range src.DNS.Search {
94 dst.DNS.Search = append(dst.DNS.Search, s)
96 for _, opt := range src.DNS.Options {
97 dst.DNS.Options = append(dst.DNS.Options, opt)
99 // TODO: what about DNS.domain?
103 func prettyPrint(i interface{}) string {
104 s, _ := json.MarshalIndent(i, "", "\t")
108 func addMultipleInterfaces(args *skel.CmdArgs, ovnAnnotation, namespace, podName string) types.Result {
109 logrus.Infof("ovn4nfvk8s-cni: addMultipleInterfaces ")
111 var ovnAnnotatedMap []map[string]string
112 ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
114 logrus.Errorf("addLogicalPort : Error Parsing Ovn Network List %v", ovnAnnotatedMap)
117 if namespace == "" || podName == "" {
118 logrus.Errorf("required CNI variable missing")
121 var interfacesArray []*current.Interface
123 var result *current.Result
124 var dstResult types.Result
125 for _, ovnNet := range ovnAnnotatedMap {
126 ipAddress := ovnNet["ip_address"]
127 macAddress := ovnNet["mac_address"]
128 gatewayIP := ovnNet["gateway_ip"]
129 defaultGateway := ovnNet["defaultGateway"]
131 if ipAddress == "" || macAddress == "" || gatewayIP == "" {
132 logrus.Errorf("failed in pod annotation key extract")
137 interfaceName := ovnNet["interface"]
138 if interfaceName == "" {
139 logrus.Errorf("addMultipleInterfaces: interface can't be null")
142 logrus.Debugf("addMultipleInterfaces: ipAddress %v %v", ipAddress, interfaceName)
143 interfacesArray, err = app.ConfigureInterface(args, namespace, podName, macAddress, ipAddress, gatewayIP, interfaceName, defaultGateway, config.Default.MTU)
145 logrus.Errorf("Failed to configure interface in pod: %v", err)
148 addr, addrNet, err := net.ParseCIDR(ipAddress)
150 logrus.Errorf("failed to parse IP address %q: %v", ipAddress, err)
154 if addr.To4() != nil {
157 var routes types.Route
158 if defaultGateway == "true" {
159 defaultAddr, defaultAddrNet, _ := net.ParseCIDR("0.0.0.0/0")
160 routes = types.Route{Dst: net.IPNet{IP: defaultAddr, Mask: defaultAddrNet.Mask}, GW: net.ParseIP(gatewayIP)}
162 result = ¤t.Result{
163 Interfaces: interfacesArray,
164 IPs: []*current.IPConfig{
167 Interface: current.Int(1),
168 Address: net.IPNet{IP: addr, Mask: addrNet.Mask},
169 Gateway: net.ParseIP(gatewayIP),
172 Routes: []*types.Route{&routes},
175 result = ¤t.Result{
176 Interfaces: interfacesArray,
177 IPs: []*current.IPConfig{
180 Interface: current.Int(1),
181 Address: net.IPNet{IP: addr, Mask: addrNet.Mask},
182 Gateway: net.ParseIP(gatewayIP),
188 // Build the result structure to pass back to the runtime
189 dstResult, err = mergeWithResult(types.Result(result), dstResult)
191 logrus.Errorf("Failed to merge results: %v", err)
195 logrus.Infof("addMultipleInterfaces: %s", prettyPrint(dstResult))
199 func addRoutes(args *skel.CmdArgs, ovnAnnotation string, dstResult types.Result) types.Result {
200 logrus.Infof("ovn4nfvk8s-cni: addRoutes ")
202 var ovnAnnotatedMap []map[string]string
203 ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
205 logrus.Errorf("addLogicalPort : Error Parsing Ovn Route List %v", err)
209 var result types.Result
210 var routes []*types.Route
211 for _, ovnNet := range ovnAnnotatedMap {
215 if dst == "" || gw == "" || dev == "" {
216 logrus.Errorf("failed in pod annotation key extract")
219 err = app.ConfigureRoute(args, dst, gw, dev)
221 logrus.Errorf("Failed to configure interface in pod: %v", err)
224 dstAddr, dstAddrNet, _ := net.ParseCIDR(dst)
225 routes = append(routes, &types.Route{
226 Dst: net.IPNet{IP: dstAddr, Mask: dstAddrNet.Mask},
231 result = ¤t.Result{
234 // Build the result structure to pass back to the runtime
235 dstResult, err = mergeWithResult(result, dstResult)
237 logrus.Errorf("Failed to merge results: %v", err)
240 logrus.Infof("addRoutes: %s", prettyPrint(dstResult))
246 func cmdAdd(args *skel.CmdArgs) error {
247 logrus.Infof("ovn4nfvk8s-cni: cmdAdd ")
248 conf := &types.NetConf{}
249 if err := json.Unmarshal(args.StdinData, conf); err != nil {
250 return fmt.Errorf("failed to load netconf: %v", err)
253 argsMap, err := argString2Map(args.Args)
258 namespace := argsMap["K8S_POD_NAMESPACE"]
259 podName := argsMap["K8S_POD_NAME"]
260 if namespace == "" || podName == "" {
261 return fmt.Errorf("required CNI variable missing")
264 clientset, err := config.NewClientset(&config.Kubernetes)
266 return fmt.Errorf("Could not create clientset for kubernetes: %v", err)
268 kubecli := &kube.Kube{KClient: clientset}
270 // Get the IP address and MAC address from the API server.
271 var annotationBackoff = wait.Backoff{Duration: 1 * time.Second, Steps: 14, Factor: 1.5, Jitter: 0.1}
272 var annotation map[string]string
273 if err := wait.ExponentialBackoff(annotationBackoff, func() (bool, error) {
274 annotation, err = kubecli.GetAnnotationsOnPod(namespace, podName)
276 // TODO: check if err is non recoverable
277 logrus.Warningf("Error while obtaining pod annotations - %v", err)
280 if _, ok := annotation["ovnIfaceList"]; ok {
285 return fmt.Errorf("failed to get pod annotation - %v", err)
287 logrus.Infof("ovn4nfvk8s-cni: Annotation Found ")
288 ovnAnnotation, ok := annotation["ovnIfaceList"]
290 return fmt.Errorf("Error while obtaining pod annotations")
292 result := addMultipleInterfaces(args, ovnAnnotation, namespace, podName)
293 // Add Routes to the pod if annotation found for routes
294 ovnRouteAnnotation, ok := annotation["ovnNetworkRoutes"]
296 logrus.Infof("ovn4nfvk8s-cni: ovnNetworkRoutes Annotation Found %+v", ovnRouteAnnotation)
297 result = addRoutes(args, ovnRouteAnnotation, result)
300 return result.Print()
303 func cmdDel(args *skel.CmdArgs) error {
304 logrus.Infof("ovn4nfvk8s-cni: cmdDel ")
305 for i := 0; i < 10; i++ {
306 ifaceName := args.ContainerID[:14] + strconv.Itoa(i)
307 done, err := app.PlatformSpecificCleanup(ifaceName)
309 logrus.Errorf("Teardown error: %v", err)
319 logrus.Infof("ovn4nfvk8s-cni CNI Invoked by Multus")
321 c.Name = "ovn4nfvk8s-cni"
322 c.Usage = "a CNI plugin to set up or tear down a additional interfaces with OVN"
324 c.Flags = config.Flags
327 c.Action = func(ctx *cli.Context) error {
328 if _, err := config.InitConfig(ctx, exec, nil); err != nil {
332 skel.PluginMain(cmdAdd, cmdDel, version.All)
336 if err := c.Run(os.Args); err != nil {
337 // Print the error to stdout in conformance with the CNI spec
338 e, ok := err.(*types.Error)
340 e = &types.Error{Code: 100, Msg: err.Error()}