Adding cnishim and cniserver
[ovn4nfv-k8s-plugin.git] / internal / pkg / cniserver / cni.go
1 package cniserver
2
3 import (
4         "encoding/json"
5         "k8s.io/apimachinery/pkg/util/wait"
6         "fmt"
7         "net"
8         "strconv"
9         "net/http"
10         "time"
11         "k8s.io/klog"
12
13         "k8s.io/client-go/kubernetes"
14         "github.com/containernetworking/cni/pkg/types"
15         "github.com/containernetworking/cni/pkg/types/current"
16         "ovn4nfv-k8s-plugin/internal/pkg/kube"
17         "k8s.io/apimachinery/pkg/api/errors"
18         "ovn4nfv-k8s-plugin/internal/pkg/config"
19         "ovn4nfv-k8s-plugin/cmd/ovn4nfvk8s-cni/app"
20 )
21
22 const (
23         ovn4nfvAnnotationTag = "k8s.plugin.opnfv.org/ovnInterfaces"
24 )
25
26 func parseOvnNetworkObject(ovnnetwork string) ([]map[string]string, error) {
27         var ovnNet []map[string]string
28
29         if ovnnetwork == "" {
30                 return nil, fmt.Errorf("parseOvnNetworkObject:error")
31         }
32
33         if err := json.Unmarshal([]byte(ovnnetwork), &ovnNet); err != nil {
34                 return nil, fmt.Errorf("parseOvnNetworkObject: failed to load ovn network err: %v | ovn network: %v", err, ovnnetwork)
35         }
36
37         return ovnNet, nil
38 }
39
40 func mergeWithResult(srcObj, dstObj types.Result) (types.Result, error) {
41
42         if dstObj == nil {
43                 return srcObj, nil
44         }
45         src, err := current.NewResultFromResult(srcObj)
46         if err != nil {
47                 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
48         }
49         dst, err := current.NewResultFromResult(dstObj)
50         if err != nil {
51                 return nil, fmt.Errorf("Couldn't convert old result to current version: %v", err)
52         }
53
54         ifacesLength := len(dst.Interfaces)
55
56         for _, iface := range src.Interfaces {
57                 dst.Interfaces = append(dst.Interfaces, iface)
58         }
59         for _, ip := range src.IPs {
60                 if ip.Interface != nil && *(ip.Interface) != -1 {
61                         ip.Interface = current.Int(*(ip.Interface) + ifacesLength)
62                 }
63                 dst.IPs = append(dst.IPs, ip)
64         }
65         for _, route := range src.Routes {
66                 dst.Routes = append(dst.Routes, route)
67         }
68
69         for _, ns := range src.DNS.Nameservers {
70                 dst.DNS.Nameservers = append(dst.DNS.Nameservers, ns)
71         }
72         for _, s := range src.DNS.Search {
73                 dst.DNS.Search = append(dst.DNS.Search, s)
74         }
75         for _, opt := range src.DNS.Options {
76                 dst.DNS.Options = append(dst.DNS.Options, opt)
77         }
78         // TODO: what about DNS.domain?
79         return dst, nil
80 }
81
82 func prettyPrint(i interface{}) string {
83         s, _ := json.MarshalIndent(i, "", "\t")
84         return string(s)
85 }
86
87 func isNotFoundError(err error) bool {
88         statusErr, ok := err.(*errors.StatusError)
89         return ok && statusErr.Status().Code == http.StatusNotFound
90 }
91
92 func (cr *CNIServerRequest) addMultipleInterfaces(ovnAnnotation, namespace, podName string) types.Result {
93         klog.Infof("ovn4nfvk8s-cni: addMultipleInterfaces ")
94         var ovnAnnotatedMap []map[string]string
95         ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
96         if err != nil {
97                 klog.Errorf("addLogicalPort : Error Parsing Ovn Network List %v %v", ovnAnnotatedMap, err)
98                 return nil
99         }
100         if namespace == "" || podName == "" {
101                 klog.Errorf("required CNI variable missing")
102                 return nil
103         }
104         var interfacesArray []*current.Interface
105         var index int
106         var result *current.Result
107         var dstResult types.Result
108         for _, ovnNet := range ovnAnnotatedMap {
109                 ipAddress := ovnNet["ip_address"]
110                 macAddress := ovnNet["mac_address"]
111                 gatewayIP := ovnNet["gateway_ip"]
112                 defaultGateway := ovnNet["defaultGateway"]
113
114                 if ipAddress == "" || macAddress == "" {
115                         klog.Errorf("failed in pod annotation key extract")
116                         return nil
117                 }
118
119                 index++
120                 interfaceName := ovnNet["interface"]
121                 if interfaceName == "" {
122                         klog.Errorf("addMultipleInterfaces: interface can't be null")
123                         return nil
124                 }
125                 klog.Infof("addMultipleInterfaces: ipAddress %v %v", ipAddress, interfaceName)
126                 interfacesArray, err = app.ConfigureInterface(cr.Netns, cr.SandboxID, cr.IfName, namespace, podName, macAddress, ipAddress, gatewayIP, interfaceName, defaultGateway, index, config.Default.MTU)
127                 if err != nil {
128                         klog.Errorf("Failed to configure interface in pod: %v", err)
129                         return nil
130                 }
131                 addr, addrNet, err := net.ParseCIDR(ipAddress)
132                 if err != nil {
133                         klog.Errorf("failed to parse IP address %q: %v", ipAddress, err)
134                         return nil
135                 }
136                 ipVersion := "6"
137                 if addr.To4() != nil {
138                         ipVersion = "4"
139                 }
140                 var routes types.Route
141                 if defaultGateway == "true" {
142                         defaultAddr, defaultAddrNet, _ := net.ParseCIDR("0.0.0.0/0")
143                         routes = types.Route{Dst: net.IPNet{IP: defaultAddr, Mask: defaultAddrNet.Mask}, GW: net.ParseIP(gatewayIP)}
144
145                         result = &current.Result{
146                                 Interfaces: interfacesArray,
147                                 IPs: []*current.IPConfig{
148                                         {
149                                                 Version:   ipVersion,
150                                                 Interface: current.Int(1),
151                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
152                                                 Gateway:   net.ParseIP(gatewayIP),
153                                         },
154                                 },
155                                 Routes: []*types.Route{&routes},
156                         }
157                 } else {
158                         result = &current.Result{
159                                 Interfaces: interfacesArray,
160                                 IPs: []*current.IPConfig{
161                                         {
162                                                 Version:   ipVersion,
163                                                 Interface: current.Int(1),
164                                                 Address:   net.IPNet{IP: addr, Mask: addrNet.Mask},
165                                                 Gateway:   net.ParseIP(gatewayIP),
166                                         },
167                                 },
168                         }
169
170                 }
171                 // Build the result structure to pass back to the runtime
172                 dstResult, err = mergeWithResult(types.Result(result), dstResult)
173                 if err != nil {
174                         klog.Errorf("Failed to merge results: %v", err)
175                         return nil
176                 }
177         }
178         klog.Infof("addMultipleInterfaces: results %s", prettyPrint(dstResult))
179         return dstResult
180 }
181
182 func (cr *CNIServerRequest) addRoutes(ovnAnnotation string, dstResult types.Result) types.Result {
183         klog.Infof("ovn4nfvk8s-cni: addRoutes ")
184         var ovnAnnotatedMap []map[string]string
185         ovnAnnotatedMap, err := parseOvnNetworkObject(ovnAnnotation)
186         if err != nil {
187                 klog.Errorf("addLogicalPort : Error Parsing Ovn Route List %v", err)
188                 return nil
189         }
190
191         var result types.Result
192         var routes []*types.Route
193         for _, ovnNet := range ovnAnnotatedMap {
194                 dst := ovnNet["dst"]
195                 gw := ovnNet["gw"]
196                 dev := ovnNet["dev"]
197                 if dst == "" || gw == "" || dev == "" {
198                         klog.Errorf("failed in pod annotation key extract")
199                         return nil
200                 }
201                 err = app.ConfigureRoute(cr.Netns, dst, gw, dev)
202                 if err != nil {
203                         klog.Errorf("Failed to configure interface in pod: %v", err)
204                         return nil
205                 }
206                 dstAddr, dstAddrNet, _ := net.ParseCIDR(dst)
207                 routes = append(routes, &types.Route{
208                         Dst: net.IPNet{IP: dstAddr, Mask: dstAddrNet.Mask},
209                         GW:  net.ParseIP(gw),
210                 })
211         }
212
213         result = &current.Result{
214                 Routes: routes,
215         }
216         // Build the result structure to pass back to the runtime
217         dstResult, err = mergeWithResult(result, dstResult)
218         if err != nil {
219                 klog.Errorf("Failed to merge results: %v", err)
220                 return nil
221         }
222         klog.Infof("addRoutes: results %s", prettyPrint(dstResult))
223         return dstResult
224 }
225
226 func (cr *CNIServerRequest) cmdAdd(kclient kubernetes.Interface) ([]byte, error) {
227         klog.Infof("ovn4nfvk8s-cni: cmdAdd")
228         namespace := cr.PodNamespace
229         podname := cr.PodName
230         if namespace == "" || podname == "" {
231                 return nil, fmt.Errorf("required CNI variable missing")
232         }
233         klog.Infof("ovn4nfvk8s-cni: cmdAdd for pod podname:%s and namespace:%s", podname, namespace)
234         kubecli := &kube.Kube{KClient: kclient}
235         // Get the IP address and MAC address from the API server.
236         var annotationBackoff = wait.Backoff{Duration: 1 * time.Second, Steps: 14, Factor: 1.5, Jitter: 0.1}
237         var annotation map[string]string
238         var err error
239         if err = wait.ExponentialBackoff(annotationBackoff, func() (bool, error) {
240                 annotation, err = kubecli.GetAnnotationsOnPod(namespace, podname)
241                 if err != nil {
242                         if isNotFoundError(err) {
243                                 return false, fmt.Errorf("Error - pod not found - %v", err)
244                         }
245                         klog.Infof("ovn4nfvk8s-cni: cmdAdd Warning - Error while obtaining pod annotations - %v", err)
246                         return false,nil
247                 }
248                 if _, ok := annotation[ovn4nfvAnnotationTag]; ok {
249                         return true, nil
250                 }
251                 return false, nil
252         }); err != nil {
253                 return nil, fmt.Errorf("failed to get pod annotation - %v", err)
254         }
255
256         klog.Infof("ovn4nfvk8s-cni: cmdAdd Annotation Found ")
257         ovnAnnotation, ok := annotation[ovn4nfvAnnotationTag]
258         if !ok {
259                 return nil, fmt.Errorf("Error while obtaining pod annotations")
260         }
261         result := cr.addMultipleInterfaces(ovnAnnotation, namespace, podname)
262         //Add Routes to the pod if annotation found for routes
263         ovnRouteAnnotation, ok := annotation["ovnNetworkRoutes"]
264         if ok {
265                 klog.Infof("ovn4nfvk8s-cni: ovnNetworkRoutes Annotation Found %+v", ovnRouteAnnotation)
266                 result = cr.addRoutes(ovnRouteAnnotation, result)
267         }
268
269         if result == nil {
270                 klog.Errorf("result struct the ovn4nfv-k8s-plugin cniserver")
271                 return nil, fmt.Errorf("result is nil from cni server response")
272         }
273
274         responseBytes, err := json.Marshal(result)
275         if err != nil {
276                 return nil, fmt.Errorf("failed to marshal pod request response: %v", err)
277         }
278
279         return responseBytes, nil
280 }
281
282 func (cr *CNIServerRequest) cmdDel() ([]byte, error) {
283         klog.Infof("cmdDel ")
284         for i := 0; i < 10; i++ {
285                 ifaceName := cr.SandboxID[:14] + strconv.Itoa(i)
286                 done, err := app.PlatformSpecificCleanup(ifaceName)
287                 if err != nil {
288                         klog.Errorf("Teardown error: %v", err)
289                 }
290                 if done {
291                         break
292                 }
293         }
294         return []byte{}, nil
295 }