Add CRD Controller for Network
[ovn4nfv-k8s-plugin.git] / internal / pkg / ovn / ovn.go
1 package ovn
2
3 import (
4         "fmt"
5         "github.com/mitchellh/mapstructure"
6         kapi "k8s.io/api/core/v1"
7         kexec "k8s.io/utils/exec"
8         "math/rand"
9         "net"
10         k8sv1alpha1 "ovn4nfv-k8s-plugin/pkg/apis/k8s/v1alpha1"
11         "strings"
12         "time"
13 )
14
15 type Controller struct {
16         gatewayCache map[string]string
17 }
18
19 const (
20         ovn4nfvRouterName = "ovn4nfv-master"
21         // Ovn4nfvAnnotationTag tag on already processed Pods
22         Ovn4nfvAnnotationTag = "k8s.plugin.opnfv.org/ovnInterfaces"
23 )
24
25 type netInterface struct {
26         Name           string
27         Interface      string
28         NetType        string
29         DefaultGateway string
30         IPAddress      string
31         MacAddress     string
32 }
33
34 var ovnCtl *Controller
35
36 // NewOvnController creates a new OVN controller for creating logical networks
37 func NewOvnController(exec kexec.Interface) (*Controller, error) {
38
39         if exec == nil {
40                 exec = kexec.New()
41         }
42         if err := SetExec(exec); err != nil {
43                 log.Error(err, "Failed to initialize exec helper")
44                 return nil, err
45         }
46         if err := SetupOvnUtils(); err != nil {
47                 log.Error(err, "Failed to initialize OVN State")
48                 return nil, err
49         }
50         ovnCtl = &Controller{
51                 gatewayCache: make(map[string]string),
52         }
53         return ovnCtl, nil
54 }
55
56 // GetOvnController returns OVN controller for creating logical networks
57 func GetOvnController() (*Controller, error) {
58         if ovnCtl != nil {
59                 return ovnCtl, nil
60         }
61         return nil, fmt.Errorf("OVN Controller not initialized")
62 }
63
64 // AddLogicalPorts adds ports to the Pod
65 func (oc *Controller) AddLogicalPorts(pod *kapi.Pod, ovnNetObjs []map[string]interface{}) (key, value string) {
66
67         if ovnNetObjs == nil {
68                 return
69         }
70
71         if pod.Spec.HostNetwork {
72                 return
73         }
74
75         if _, ok := pod.Annotations[Ovn4nfvAnnotationTag]; ok {
76                 log.V(1).Info("AddLogicalPorts : Pod annotation found")
77                 return
78         }
79
80         var ovnString, outStr string
81         ovnString = "["
82         var ns netInterface
83         for _, net := range ovnNetObjs {
84
85                 err := mapstructure.Decode(net, &ns)
86                 if err != nil {
87                         log.Error(err, "mapstruct error", "network", net)
88                         return
89                 }
90
91                 if !oc.FindLogicalSwitch(ns.Name) {
92                         log.Info("Logical Switch not found")
93                         return
94                 }
95                 if ns.Interface == "" {
96                         log.Info("Interface name must be provided")
97                         return
98                 }
99                 if ns.DefaultGateway == "" {
100                         ns.DefaultGateway = "false"
101                 }
102                 if ns.NetType == "" || ns.NetType != "provider" {
103                         ns.NetType = "virtual"
104                 }
105                 if ns.NetType == "provider" {
106                         if ns.IPAddress == "" {
107                                 log.Info("ipAddress must be provided for netType Provider")
108                                 return
109                         }
110                         if ns.DefaultGateway == "true" {
111                                 log.Info("defaultGateway not supported for provider network - Use ovnNetworkRoutes to add routes")
112                                 return
113                         }
114
115                 }
116                 outStr = oc.addLogicalPortWithSwitch(pod, ns.Name, ns.IPAddress, ns.MacAddress, ns.Interface, ns.NetType)
117                 if outStr == "" {
118                         return
119                 }
120                 last := len(outStr) - 1
121                 tmpString := outStr[:last]
122                 tmpString += "," + "\\\"defaultGateway\\\":" + "\\\"" + ns.DefaultGateway + "\\\""
123                 tmpString += "," + "\\\"interface\\\":" + "\\\"" + ns.Interface + "\\\"}"
124                 ovnString += tmpString
125                 ovnString += ","
126         }
127         last := len(ovnString) - 1
128         ovnString = ovnString[:last]
129         ovnString += "]"
130         key = Ovn4nfvAnnotationTag
131         value = ovnString
132         return key, value
133 }
134
135 // DeleteLogicalPorts deletes the OVN ports for the pod
136 func (oc *Controller) DeleteLogicalPorts(name, namespace string) {
137
138         logicalPort := fmt.Sprintf("%s_%s", namespace, name)
139
140         // get the list of logical ports from OVN
141         stdout, stderr, err := RunOVNNbctl("--data=bare", "--no-heading",
142                 "--columns=name", "find", "logical_switch_port", "external_ids:pod=true")
143         if err != nil {
144                 log.Error(err, "Error in obtaining list of logical ports ", "stdout", stdout, "stderr", stderr)
145                 return
146         }
147         existingLogicalPorts := strings.Fields(stdout)
148         for _, existingPort := range existingLogicalPorts {
149                 if strings.Contains(existingPort, logicalPort) {
150                         // found, delete this logical port
151                         log.V(1).Info("Deleting", "Port", existingPort)
152                         stdout, stderr, err := RunOVNNbctl("--if-exists", "lsp-del",
153                                 existingPort)
154                         if err != nil {
155                                 log.Error(err, "Error in deleting pod's logical port ", "stdout", stdout, "stderr", stderr)
156                         }
157                 }
158         }
159         return
160 }
161
162 // CreateNetwork in OVN controller
163 func (oc *Controller) CreateNetwork(cr *k8sv1alpha1.Network) error {
164         var stdout, stderr string
165
166         // Currently only these fields are supported
167         name := cr.Name
168         subnet := cr.Spec.Ipv4Subnets[0].Subnet
169         gatewayIP := cr.Spec.Ipv4Subnets[0].Gateway
170         excludeIps := cr.Spec.Ipv4Subnets[0].ExcludeIps
171
172         output, stderr, err := RunOVNNbctl("--data=bare", "--no-heading",
173                 "--columns=name", "find", "logical_switch", "name="+name)
174         if err != nil {
175                 log.Error(err, "Error in reading logical switch", "stderr", stderr)
176                 return nil
177         }
178
179         if strings.Compare(name, output) == 0 {
180                 log.V(1).Info("Logical Switch already exists, delete first to update/recreate", "name", name)
181                 return nil
182         }
183
184         _, cidr, err := net.ParseCIDR(subnet)
185         if err != nil {
186                 log.Error(err, "ovnNetwork '%s' invalid subnet CIDR", "name", name)
187                 return err
188
189         }
190         firstIP := NextIP(cidr.IP)
191         n, _ := cidr.Mask.Size()
192
193         var gatewayIPMask string
194         var gwIP net.IP
195         if gatewayIP != "" {
196                 gwIP, _, err = net.ParseCIDR(gatewayIP)
197                 if err != nil {
198                         // Check if this is a valid IP address
199                         gwIP = net.ParseIP(gatewayIP)
200                 }
201         }
202         // If no valid Gateway use the first IP address for GatewayIP
203         if gwIP == nil {
204                 gatewayIPMask = fmt.Sprintf("%s/%d", firstIP.String(), n)
205         } else {
206                 gatewayIPMask = fmt.Sprintf("%s/%d", gwIP.String(), n)
207         }
208
209         // Create a logical switch and set its subnet.
210         if excludeIps != "" {
211                 stdout, stderr, err = RunOVNNbctl("--wait=hv", "--", "--may-exist", "ls-add", name, "--", "set", "logical_switch", name, "other-config:subnet="+subnet, "external-ids:gateway_ip="+gatewayIPMask, "other-config:exclude_ips="+excludeIps)
212         } else {
213                 stdout, stderr, err = RunOVNNbctl("--wait=hv", "--", "--may-exist", "ls-add", name, "--", "set", "logical_switch", name, "other-config:subnet="+subnet, "external-ids:gateway_ip="+gatewayIPMask)
214         }
215         if err != nil {
216                 log.Error(err, "Failed to create a logical switch", "name", name, "stdout", stdout, "stderr", stderr)
217                 return err
218         }
219
220         routerMac, stderr, err := RunOVNNbctl("--if-exist", "get", "logical_router_port", "rtos-"+name, "mac")
221         if err != nil {
222                 log.Error(err, "Failed to get logical router port", "stderr", stderr)
223                 return err
224         }
225         if routerMac == "" {
226                 prefix := "00:00:00"
227                 newRand := rand.New(rand.NewSource(time.Now().UnixNano()))
228                 routerMac = fmt.Sprintf("%s:%02x:%02x:%02x", prefix, newRand.Intn(255), newRand.Intn(255), newRand.Intn(255))
229         }
230
231         _, stderr, err = RunOVNNbctl("--wait=hv", "--may-exist", "lrp-add", ovn4nfvRouterName, "rtos-"+name, routerMac, gatewayIPMask)
232         if err != nil {
233                 log.Error(err, "Failed to add logical port to router", "stderr", stderr)
234                 return err
235         }
236
237         // Connect the switch to the router.
238         stdout, stderr, err = RunOVNNbctl("--wait=hv", "--", "--may-exist", "lsp-add", name, "stor-"+name, "--", "set", "logical_switch_port", "stor-"+name, "type=router", "options:router-port=rtos-"+name, "addresses="+"\""+routerMac+"\"")
239         if err != nil {
240                 log.Error(err, "Failed to add logical port to switch", "stderr", stderr, "stdout", stdout)
241                 return err
242         }
243
244         return nil
245 }
246
247 // DeleteNetwork in OVN controller
248 func (oc *Controller) DeleteNetwork(cr *k8sv1alpha1.Network) error {
249
250         name := cr.Name
251         stdout, stderr, err := RunOVNNbctl("--if-exist", "--wait=hv", "lrp-del", "rtos-"+name)
252         if err != nil {
253                 log.Error(err, "Failed to delete router port", "name", name, "stdout", stdout, "stderr", stderr)
254                 return err
255         }
256         stdout, stderr, err = RunOVNNbctl("--if-exist", "--wait=hv", "ls-del", name)
257         if err != nil {
258                 log.Error(err, "Failed to delete switch", "name", name, "stdout", stdout, "stderr", stderr)
259                 return err
260         }
261         return nil
262 }
263
264 // FindLogicalSwitch returns true if switch exists
265 func (oc *Controller) FindLogicalSwitch(name string) bool {
266         // get logical switch from OVN
267         output, stderr, err := RunOVNNbctl("--data=bare", "--no-heading",
268                 "--columns=name", "find", "logical_switch", "name="+name)
269         if err != nil {
270                 log.Error(err, "Error in obtaining list of logical switch", "stderr", stderr)
271                 return false
272         }
273         if strings.Compare(name, output) == 0 {
274                 return true
275         }
276         return false
277 }
278
279 func (oc *Controller) getGatewayFromSwitch(logicalSwitch string) (string, string, error) {
280         var gatewayIPMaskStr, stderr string
281         var ok bool
282         var err error
283         log.V(1).Info("getGatewayFromSwitch", "logicalSwitch", logicalSwitch)
284         if gatewayIPMaskStr, ok = oc.gatewayCache[logicalSwitch]; !ok {
285                 gatewayIPMaskStr, stderr, err = RunOVNNbctl("--if-exists",
286                         "get", "logical_switch", logicalSwitch,
287                         "external_ids:gateway_ip")
288                 if err != nil {
289                         log.Error(err, "Failed to get gateway IP", "stderr", stderr, "gatewayIPMaskStr", gatewayIPMaskStr)
290                         return "", "", err
291                 }
292                 if gatewayIPMaskStr == "" {
293                         return "", "", fmt.Errorf("Empty gateway IP in logical switch %s",
294                                 logicalSwitch)
295                 }
296                 oc.gatewayCache[logicalSwitch] = gatewayIPMaskStr
297         }
298         gatewayIPMask := strings.Split(gatewayIPMaskStr, "/")
299         if len(gatewayIPMask) != 2 {
300                 return "", "", fmt.Errorf("Failed to get IP and Mask from gateway CIDR:  %s",
301                         gatewayIPMaskStr)
302         }
303         gatewayIP := gatewayIPMask[0]
304         mask := gatewayIPMask[1]
305         return gatewayIP, mask, nil
306 }
307
308 func (oc *Controller) addLogicalPortWithSwitch(pod *kapi.Pod, logicalSwitch, ipAddress, macAddress, interfaceName, netType string) (annotation string) {
309         var out, stderr string
310         var err error
311         var isStaticIP bool
312         if pod.Spec.HostNetwork {
313                 return
314         }
315
316         var portName string
317         if interfaceName != "" {
318                 portName = fmt.Sprintf("%s_%s_%s", pod.Namespace, pod.Name, interfaceName)
319         } else {
320                 return
321         }
322
323         log.V(1).Info("Creating logical port for on switch", "portName", portName, "logicalSwitch", logicalSwitch)
324
325         if ipAddress != "" && macAddress != "" {
326                 isStaticIP = true
327         }
328         if ipAddress != "" && macAddress == "" {
329                 macAddress = generateMac()
330                 isStaticIP = true
331         }
332
333         if isStaticIP {
334                 out, stderr, err = RunOVNNbctl("--may-exist", "lsp-add",
335                         logicalSwitch, portName, "--", "lsp-set-addresses", portName,
336                         fmt.Sprintf("%s %s", macAddress, ipAddress), "--", "--if-exists",
337                         "clear", "logical_switch_port", portName, "dynamic_addresses", "--", "set",
338                         "logical_switch_port", portName,
339                         "external-ids:namespace="+pod.Namespace,
340                         "external-ids:logical_switch="+logicalSwitch,
341                         "external-ids:pod=true")
342                 if err != nil {
343                         log.Error(err, "Failed to add logical port to switch", "out", out, "stderr", stderr)
344                         return
345                 }
346         } else {
347                 out, stderr, err = RunOVNNbctl("--wait=sb", "--",
348                         "--may-exist", "lsp-add", logicalSwitch, portName,
349                         "--", "lsp-set-addresses",
350                         portName, "dynamic", "--", "set",
351                         "logical_switch_port", portName,
352                         "external-ids:namespace="+pod.Namespace,
353                         "external-ids:logical_switch="+logicalSwitch,
354                         "external-ids:pod=true")
355                 if err != nil {
356                         log.Error(err, "Error while creating logical port %s ", "portName", portName, "stdout", out, "stderr", stderr)
357                         return
358                 }
359         }
360
361         count := 30
362         for count > 0 {
363                 if isStaticIP {
364                         out, stderr, err = RunOVNNbctl("get",
365                                 "logical_switch_port", portName, "addresses")
366                 } else {
367                         out, stderr, err = RunOVNNbctl("get",
368                                 "logical_switch_port", portName, "dynamic_addresses")
369                 }
370                 if err == nil && out != "[]" {
371                         break
372                 }
373                 if err != nil {
374                         log.Error(err, "Error while obtaining addresses for", "portName", portName)
375                         return
376                 }
377                 time.Sleep(time.Second)
378                 count--
379         }
380         if count == 0 {
381                 log.Error(err, "Error while obtaining addresses for", "portName", portName, "stdout", out, "stderr", stderr)
382                 return
383         }
384
385         // static addresses have format ["0a:00:00:00:00:01 192.168.1.3"], while
386         // dynamic addresses have format "0a:00:00:00:00:01 192.168.1.3".
387         outStr := strings.TrimLeft(out, `[`)
388         outStr = strings.TrimRight(outStr, `]`)
389         outStr = strings.Trim(outStr, `"`)
390         addresses := strings.Split(outStr, " ")
391         if len(addresses) != 2 {
392                 log.Info("Error while obtaining addresses for", "portName", portName)
393                 return
394         }
395
396         if netType == "virtual" {
397                 gatewayIP, mask, err := oc.getGatewayFromSwitch(logicalSwitch)
398                 if err != nil {
399                         log.Error(err, "Error obtaining gateway address for switch", "logicalSwitch", logicalSwitch)
400                         return
401                 }
402                 annotation = fmt.Sprintf(`{\"ip_address\":\"%s/%s\", \"mac_address\":\"%s\", \"gateway_ip\": \"%s\"}`, addresses[1], mask, addresses[0], gatewayIP)
403         } else {
404                 annotation = fmt.Sprintf(`{\"ip_address\":\"%s\", \"mac_address\":\"%s\", \"gateway_ip\": \"%s\"}`, addresses[1], addresses[0], "")
405         }
406
407         return annotation
408 }