Add Routes to Pod
[ovn4nfv-k8s-plugin.git] / cmd / ovn4nfvk8s-cni / ovn4nfvk8s-cni.go
1 // +build linux
2
3 package main
4
5 import (
6         "encoding/json"
7         "fmt"
8         "net"
9         "os"
10         "strconv"
11         "strings"
12         "time"
13
14         "github.com/sirupsen/logrus"
15         "github.com/urfave/cli"
16
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"
22
23         kexec "k8s.io/utils/exec"
24         "ovn4nfv-k8s-plugin/internal/pkg/kube"
25
26         "ovn4nfv-k8s-plugin/cmd/ovn4nfvk8s-cni/app"
27         "ovn4nfv-k8s-plugin/internal/pkg/config"
28 )
29
30 func argString2Map(args string) (map[string]string, error) {
31         argsMap := make(map[string]string)
32
33         pairs := strings.Split(args, ";")
34         for _, pair := range pairs {
35                 kv := strings.Split(pair, "=")
36                 if len(kv) != 2 {
37                         return nil, fmt.Errorf("ARGS: invalid pair %q", pair)
38                 }
39                 keyString := kv[0]
40                 valueString := kv[1]
41                 argsMap[keyString] = valueString
42         }
43
44         return argsMap, nil
45 }
46
47 func parseOvnNetworkObject(ovnnetwork string) ([]map[string]string, error) {
48         var ovnNet []map[string]string
49
50         if ovnnetwork == "" {
51                 return nil, fmt.Errorf("parseOvnNetworkObject:error")
52         }
53
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)
56         }
57
58         return ovnNet, nil
59 }
60
61 func mergeWithResult(srcObj, dstObj types.Result) (types.Result, error) {
62
63         if dstObj == nil {
64                 return srcObj, nil
65         }
66         src, err := current.NewResultFromResult(srcObj)
67         if err != nil {
68                 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
69         }
70         dst, err := current.NewResultFromResult(dstObj)
71         if err != nil {
72                 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
73         }
74
75         ifacesLength := len(dst.Interfaces)
76
77         for _, iface := range src.Interfaces {
78                 dst.Interfaces = append(dst.Interfaces, iface)
79         }
80         for _, ip := range src.IPs {
81                 if ip.Interface != nil && *(ip.Interface) != -1 {
82                         ip.Interface = current.Int(*(ip.Interface) + ifacesLength)
83                 }
84                 dst.IPs = append(dst.IPs, ip)
85         }
86         for _, route := range src.Routes {
87                 dst.Routes = append(dst.Routes, route)
88         }
89
90         for _, ns := range src.DNS.Nameservers {
91                 dst.DNS.Nameservers = append(dst.DNS.Nameservers, ns)
92         }
93         for _, s := range src.DNS.Search {
94                 dst.DNS.Search = append(dst.DNS.Search, s)
95         }
96         for _, opt := range src.DNS.Options {
97                 dst.DNS.Options = append(dst.DNS.Options, opt)
98         }
99         // TODO: what about DNS.domain?
100         return dst, nil
101 }
102
103 func prettyPrint(i interface{}) string {
104         s, _ := json.MarshalIndent(i, "", "\t")
105         return string(s)
106 }
107
108 func addMultipleInterfaces(args *skel.CmdArgs, ovnAnnotation, namespace, podName string) types.Result {
109         logrus.Infof("ovn4nfvk8s-cni: addMultipleInterfaces ")
110
111         var ovnAnnotatedMap []map[string]string
112         ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
113         if err != nil {
114                 logrus.Errorf("addLogicalPort : Error Parsing Ovn Network List %v", ovnAnnotatedMap)
115                 return nil
116         }
117         if namespace == "" || podName == "" {
118                 logrus.Errorf("required CNI variable missing")
119                 return nil
120         }
121         var interfacesArray []*current.Interface
122         var index int
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"]
130
131                 if ipAddress == "" || macAddress == "" || gatewayIP == "" {
132                         logrus.Errorf("failed in pod annotation key extract")
133                         return nil
134                 }
135
136                 index++
137                 interfaceName := ovnNet["interface"]
138                 if interfaceName == "" {
139                         logrus.Errorf("addMultipleInterfaces: interface can't be null")
140                         return nil
141                 }
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)
144                 if err != nil {
145                         logrus.Errorf("Failed to configure interface in pod: %v", err)
146                         return nil
147                 }
148                 addr, addrNet, err := net.ParseCIDR(ipAddress)
149                 if err != nil {
150                         logrus.Errorf("failed to parse IP address %q: %v", ipAddress, err)
151                         return nil
152                 }
153                 ipVersion := "6"
154                 if addr.To4() != nil {
155                         ipVersion = "4"
156                 }
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)}
161
162                         result = &current.Result{
163                                 Interfaces: interfacesArray,
164                                 IPs: []*current.IPConfig{
165                                         {
166                                                 Version:   ipVersion,
167                                                 Interface: current.Int(1),
168                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
169                                                 Gateway:   net.ParseIP(gatewayIP),
170                                         },
171                                 },
172                                 Routes: []*types.Route{&routes},
173                         }
174                 } else {
175                         result = &current.Result{
176                                 Interfaces: interfacesArray,
177                                 IPs: []*current.IPConfig{
178                                         {
179                                                 Version:   ipVersion,
180                                                 Interface: current.Int(1),
181                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
182                                                 Gateway:   net.ParseIP(gatewayIP),
183                                         },
184                                 },
185                         }
186
187                 }
188                 // Build the result structure to pass back to the runtime
189                 dstResult, err = mergeWithResult(types.Result(result), dstResult)
190                 if err != nil {
191                         logrus.Errorf("Failed to merge results: %v", err)
192                         return nil
193                 }
194         }
195         logrus.Infof("addMultipleInterfaces:  %s", prettyPrint(dstResult))
196         return dstResult
197 }
198
199 func addRoutes(args *skel.CmdArgs, ovnAnnotation string, dstResult types.Result) types.Result {
200         logrus.Infof("ovn4nfvk8s-cni: addRoutes ")
201
202         var ovnAnnotatedMap []map[string]string
203         ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
204         if err != nil {
205                 logrus.Errorf("addLogicalPort : Error Parsing Ovn Route List %v", err)
206                 return nil
207         }
208
209         var result types.Result
210         var routes []*types.Route
211         for _, ovnNet := range ovnAnnotatedMap {
212                 dst := ovnNet["dst"]
213                 gw := ovnNet["gw"]
214                 dev := ovnNet["dev"]
215                 if dst == "" || gw == "" || dev == "" {
216                         logrus.Errorf("failed in pod annotation key extract")
217                         return nil
218                 }
219                 err = app.ConfigureRoute(args, dst, gw, dev)
220                 if err != nil {
221                         logrus.Errorf("Failed to configure interface in pod: %v", err)
222                         return nil
223                 }
224                 dstAddr, dstAddrNet, _ := net.ParseCIDR(dst)
225                 routes = append(routes, &types.Route{
226                         Dst: net.IPNet{IP: dstAddr, Mask: dstAddrNet.Mask},
227                         GW:  net.ParseIP(gw),
228                 })
229         }
230
231         result = &current.Result{
232                 Routes: routes,
233         }
234         // Build the result structure to pass back to the runtime
235         dstResult, err = mergeWithResult(result, dstResult)
236         if err != nil {
237                 logrus.Errorf("Failed to merge results: %v", err)
238                 return nil
239         }
240         logrus.Infof("addRoutes:  %s", prettyPrint(dstResult))
241         return dstResult
242
243 }
244
245
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)
251         }
252
253         argsMap, err := argString2Map(args.Args)
254         if err != nil {
255                 return err
256         }
257
258         namespace := argsMap["K8S_POD_NAMESPACE"]
259         podName := argsMap["K8S_POD_NAME"]
260         if namespace == "" || podName == "" {
261                 return fmt.Errorf("required CNI variable missing")
262         }
263
264         clientset, err := config.NewClientset(&config.Kubernetes)
265         if err != nil {
266                 return fmt.Errorf("Could not create clientset for kubernetes: %v", err)
267         }
268         kubecli := &kube.Kube{KClient: clientset}
269
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)
275                 if err != nil {
276                         // TODO: check if err is non recoverable
277                         logrus.Warningf("Error while obtaining pod annotations - %v", err)
278                         return false, nil
279                 }
280                 if _, ok := annotation["ovnIfaceList"]; ok {
281                         return true, nil
282                 }
283                 return false, nil
284         }); err != nil {
285                 return fmt.Errorf("failed to get pod annotation - %v", err)
286         }
287         logrus.Infof("ovn4nfvk8s-cni: Annotation Found ")
288         ovnAnnotation, ok := annotation["ovnIfaceList"]
289         if !ok {
290                 return fmt.Errorf("Error while obtaining pod annotations")
291         }
292         result := addMultipleInterfaces(args, ovnAnnotation, namespace, podName)
293         // Add Routes to the pod if annotation found for routes
294         ovnRouteAnnotation, ok := annotation["ovnNetworkRoutes"]
295         if ok {
296                 logrus.Infof("ovn4nfvk8s-cni: ovnNetworkRoutes Annotation Found %+v", ovnRouteAnnotation)
297                 result = addRoutes(args, ovnRouteAnnotation, result)
298         }
299
300         return result.Print()
301 }
302
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)
308                 if err != nil {
309                         logrus.Errorf("Teardown error: %v", err)
310                 }
311                 if done {
312                         break
313                 }
314         }
315         return nil
316 }
317
318 func main() {
319         logrus.Infof("ovn4nfvk8s-cni CNI Invoked by Multus")
320         c := cli.NewApp()
321         c.Name = "ovn4nfvk8s-cni"
322         c.Usage = "a CNI plugin to set up or tear down a additional interfaces with OVN"
323         c.Version = "0.0.2"
324         c.Flags = config.Flags
325
326         exec := kexec.New()
327         c.Action = func(ctx *cli.Context) error {
328                 if _, err := config.InitConfig(ctx, exec, nil); err != nil {
329                         return err
330                 }
331
332                 skel.PluginMain(cmdAdd, cmdDel, version.All)
333                 return nil
334         }
335
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)
339                 if !ok {
340                         e = &types.Error{Code: 100, Msg: err.Error()}
341                 }
342                 e.Print()
343         }
344 }