Seed code for the Plugin
[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 cmdAdd(args *skel.CmdArgs) error {
200         logrus.Infof("ovn4nfvk8s-cni: cmdAdd ")
201         conf := &types.NetConf{}
202         if err := json.Unmarshal(args.StdinData, conf); err != nil {
203                 return fmt.Errorf("failed to load netconf: %v", err)
204         }
205
206         argsMap, err := argString2Map(args.Args)
207         if err != nil {
208                 return err
209         }
210
211         namespace := argsMap["K8S_POD_NAMESPACE"]
212         podName := argsMap["K8S_POD_NAME"]
213         if namespace == "" || podName == "" {
214                 return fmt.Errorf("required CNI variable missing")
215         }
216
217         clientset, err := config.NewClientset(&config.Kubernetes)
218         if err != nil {
219                 return fmt.Errorf("Could not create clientset for kubernetes: %v", err)
220         }
221         kubecli := &kube.Kube{KClient: clientset}
222
223         // Get the IP address and MAC address from the API server.
224         var annotationBackoff = wait.Backoff{Duration: 1 * time.Second, Steps: 14, Factor: 1.5, Jitter: 0.1}
225         var annotation map[string]string
226         if err := wait.ExponentialBackoff(annotationBackoff, func() (bool, error) {
227                 annotation, err = kubecli.GetAnnotationsOnPod(namespace, podName)
228                 if err != nil {
229                         // TODO: check if err is non recoverable
230                         logrus.Warningf("Error while obtaining pod annotations - %v", err)
231                         return false, nil
232                 }
233                 if _, ok := annotation["ovnIfaceList"]; ok {
234                         return true, nil
235                 }
236                 return false, nil
237         }); err != nil {
238                 return fmt.Errorf("failed to get pod annotation - %v", err)
239         }
240         logrus.Infof("ovn4nfvk8s-cni: Annotation Found ")
241         ovnAnnotation, ok := annotation["ovnIfaceList"]
242         if !ok {
243                 return fmt.Errorf("Error while obtaining pod annotations")
244         }
245         result := addMultipleInterfaces(args, ovnAnnotation, namespace, podName)
246         return result.Print()
247 }
248
249 func cmdDel(args *skel.CmdArgs) error {
250         logrus.Infof("ovn4nfvk8s-cni: cmdDel ")
251         for i := 0; i < 10; i++ {
252                 ifaceName := args.ContainerID[:14] + strconv.Itoa(i)
253                 done, err := app.PlatformSpecificCleanup(ifaceName)
254                 if err != nil {
255                         logrus.Errorf("Teardown error: %v", err)
256                 }
257                 if done {
258                         break
259                 }
260         }
261         return nil
262 }
263
264 func main() {
265         logrus.Infof("ovn4nfvk8s-cni CNI Invoked by Multus")
266         c := cli.NewApp()
267         c.Name = "ovn4nfvk8s-cni"
268         c.Usage = "a CNI plugin to set up or tear down a additional interfaces with OVN"
269         c.Version = "0.0.2"
270         c.Flags = config.Flags
271
272         exec := kexec.New()
273         c.Action = func(ctx *cli.Context) error {
274                 if _, err := config.InitConfig(ctx, exec, nil); err != nil {
275                         return err
276                 }
277
278                 skel.PluginMain(cmdAdd, cmdDel, version.All)
279                 return nil
280         }
281
282         if err := c.Run(os.Args); err != nil {
283                 // Print the error to stdout in conformance with the CNI spec
284                 e, ok := err.(*types.Error)
285                 if !ok {
286                         e = &types.Error{Code: 100, Msg: err.Error()}
287                 }
288                 e.Print()
289         }
290 }