Use controller runtime and operator sdk
[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                 logrus.Debugf("addMultipleInterfaces: ipAddress %v %v", ipAddress, interfaceName)
146                 interfacesArray, err = app.ConfigureInterface(args, namespace, podName, macAddress, ipAddress, gatewayIP, interfaceName, defaultGateway, config.Default.MTU)
147                 if err != nil {
148                         logrus.Errorf("Failed to configure interface in pod: %v", err)
149                         return nil
150                 }
151                 addr, addrNet, err := net.ParseCIDR(ipAddress)
152                 if err != nil {
153                         logrus.Errorf("failed to parse IP address %q: %v", ipAddress, err)
154                         return nil
155                 }
156                 ipVersion := "6"
157                 if addr.To4() != nil {
158                         ipVersion = "4"
159                 }
160                 var routes types.Route
161                 if defaultGateway == "true" {
162                         defaultAddr, defaultAddrNet, _ := net.ParseCIDR("0.0.0.0/0")
163                         routes = types.Route{Dst: net.IPNet{IP: defaultAddr, Mask: defaultAddrNet.Mask}, GW: net.ParseIP(gatewayIP)}
164
165                         result = &current.Result{
166                                 Interfaces: interfacesArray,
167                                 IPs: []*current.IPConfig{
168                                         {
169                                                 Version:   ipVersion,
170                                                 Interface: current.Int(1),
171                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
172                                                 Gateway:   net.ParseIP(gatewayIP),
173                                         },
174                                 },
175                                 Routes: []*types.Route{&routes},
176                         }
177                 } else {
178                         result = &current.Result{
179                                 Interfaces: interfacesArray,
180                                 IPs: []*current.IPConfig{
181                                         {
182                                                 Version:   ipVersion,
183                                                 Interface: current.Int(1),
184                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
185                                                 Gateway:   net.ParseIP(gatewayIP),
186                                         },
187                                 },
188                         }
189
190                 }
191                 // Build the result structure to pass back to the runtime
192                 dstResult, err = mergeWithResult(types.Result(result), dstResult)
193                 if err != nil {
194                         logrus.Errorf("Failed to merge results: %v", err)
195                         return nil
196                 }
197         }
198         logrus.Infof("addMultipleInterfaces:  %s", prettyPrint(dstResult))
199         return dstResult
200 }
201
202 func addRoutes(args *skel.CmdArgs, ovnAnnotation string, dstResult types.Result) types.Result {
203         logrus.Infof("ovn4nfvk8s-cni: addRoutes ")
204
205         var ovnAnnotatedMap []map[string]string
206         ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
207         if err != nil {
208                 logrus.Errorf("addLogicalPort : Error Parsing Ovn Route List %v", err)
209                 return nil
210         }
211
212         var result types.Result
213         var routes []*types.Route
214         for _, ovnNet := range ovnAnnotatedMap {
215                 dst := ovnNet["dst"]
216                 gw := ovnNet["gw"]
217                 dev := ovnNet["dev"]
218                 if dst == "" || gw == "" || dev == "" {
219                         logrus.Errorf("failed in pod annotation key extract")
220                         return nil
221                 }
222                 err = app.ConfigureRoute(args, dst, gw, dev)
223                 if err != nil {
224                         logrus.Errorf("Failed to configure interface in pod: %v", err)
225                         return nil
226                 }
227                 dstAddr, dstAddrNet, _ := net.ParseCIDR(dst)
228                 routes = append(routes, &types.Route{
229                         Dst: net.IPNet{IP: dstAddr, Mask: dstAddrNet.Mask},
230                         GW:  net.ParseIP(gw),
231                 })
232         }
233
234         result = &current.Result{
235                 Routes: routes,
236         }
237         // Build the result structure to pass back to the runtime
238         dstResult, err = mergeWithResult(result, dstResult)
239         if err != nil {
240                 logrus.Errorf("Failed to merge results: %v", err)
241                 return nil
242         }
243         logrus.Infof("addRoutes:  %s", prettyPrint(dstResult))
244         return dstResult
245
246 }
247
248 func cmdAdd(args *skel.CmdArgs) error {
249         logrus.Infof("ovn4nfvk8s-cni: cmdAdd ")
250         conf := &types.NetConf{}
251         if err := json.Unmarshal(args.StdinData, conf); err != nil {
252                 return fmt.Errorf("failed to load netconf: %v", err)
253         }
254
255         argsMap, err := argString2Map(args.Args)
256         if err != nil {
257                 return err
258         }
259
260         namespace := argsMap["K8S_POD_NAMESPACE"]
261         podName := argsMap["K8S_POD_NAME"]
262         if namespace == "" || podName == "" {
263                 return fmt.Errorf("required CNI variable missing")
264         }
265
266         clientset, err := config.NewClientset(&config.Kubernetes)
267         if err != nil {
268                 return fmt.Errorf("Could not create clientset for kubernetes: %v", err)
269         }
270         kubecli := &kube.Kube{KClient: clientset}
271
272         // Get the IP address and MAC address from the API server.
273         var annotationBackoff = wait.Backoff{Duration: 1 * time.Second, Steps: 14, Factor: 1.5, Jitter: 0.1}
274         var annotation map[string]string
275         if err := wait.ExponentialBackoff(annotationBackoff, func() (bool, error) {
276                 annotation, err = kubecli.GetAnnotationsOnPod(namespace, podName)
277                 if err != nil {
278                         // TODO: check if err is non recoverable
279                         logrus.Warningf("Error while obtaining pod annotations - %v", err)
280                         return false, nil
281                 }
282                 if _, ok := annotation[ovn4nfvAnnotationTag]; ok {
283                         return true, nil
284                 }
285                 return false, nil
286         }); err != nil {
287                 return fmt.Errorf("failed to get pod annotation - %v", err)
288         }
289         logrus.Infof("ovn4nfvk8s-cni: Annotation Found ")
290         ovnAnnotation, ok := annotation[ovn4nfvAnnotationTag]
291         if !ok {
292                 return fmt.Errorf("Error while obtaining pod annotations")
293         }
294         result := addMultipleInterfaces(args, ovnAnnotation, namespace, podName)
295         // Add Routes to the pod if annotation found for routes
296         ovnRouteAnnotation, ok := annotation["ovnNetworkRoutes"]
297         if ok {
298                 logrus.Infof("ovn4nfvk8s-cni: ovnNetworkRoutes Annotation Found %+v", ovnRouteAnnotation)
299                 result = addRoutes(args, ovnRouteAnnotation, result)
300         }
301
302         return result.Print()
303 }
304
305 func cmdDel(args *skel.CmdArgs) error {
306         logrus.Infof("ovn4nfvk8s-cni: cmdDel ")
307         for i := 0; i < 10; i++ {
308                 ifaceName := args.ContainerID[:14] + strconv.Itoa(i)
309                 done, err := app.PlatformSpecificCleanup(ifaceName)
310                 if err != nil {
311                         logrus.Errorf("Teardown error: %v", err)
312                 }
313                 if done {
314                         break
315                 }
316         }
317         return nil
318 }
319
320 func main() {
321         logrus.Infof("ovn4nfvk8s-cni CNI Invoked by Multus")
322         c := cli.NewApp()
323         c.Name = "ovn4nfvk8s-cni"
324         c.Usage = "a CNI plugin to set up or tear down a additional interfaces with OVN"
325         c.Version = "0.0.2"
326         c.Flags = config.Flags
327
328         c.Action = func(ctx *cli.Context) error {
329                 if _, err := config.InitConfig(ctx); err != nil {
330                         return err
331                 }
332
333                 skel.PluginMain(cmdAdd, nil, cmdDel, version.All, "")
334                 return nil
335         }
336
337         if err := c.Run(os.Args); err != nil {
338                 // Print the error to stdout in conformance with the CNI spec
339                 e, ok := err.(*types.Error)
340                 if !ok {
341                         e = &types.Error{Code: 100, Msg: err.Error()}
342                 }
343                 e.Print()
344         }
345 }