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