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