Route based chaining
[ovn4nfv-k8s-plugin.git] / internal / pkg / ovn / common.go
index b504440..0053e56 100644 (file)
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package ovn
 
 import (
        "encoding/json"
        "fmt"
-       "github.com/sirupsen/logrus"
+       "github.com/vishvananda/netlink"
        "math/big"
        "math/rand"
        "net"
+       logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
+       "strings"
        "time"
 )
 
-func SetupDistributedRouter(name string) error {
+var log = logf.Log.WithName("ovn")
 
-       // Make sure br-int is created.
-       stdout, stderr, err := RunOVSVsctlUnix("--", "--may-exist", "add-br", "br-int")
+// CreateVlan creates VLAN with vlanID
+func CreateVlan(vlanID, interfaceName, logicalInterfaceName string) error {
+       if interfaceName == "" || vlanID == "" || logicalInterfaceName == "" {
+               return fmt.Errorf("CreateVlan invalid parameters: %v %v %v", interfaceName, vlanID, logicalInterfaceName)
+       }
+       _, err := netlink.LinkByName(logicalInterfaceName)
+       if err == nil {
+               return err
+       }
+       stdout, stderr, err := RunIP("link", "add", "link", interfaceName, "name", logicalInterfaceName, "type", "vlan", "id", vlanID)
        if err != nil {
-               logrus.Errorf("Failed to create br-int, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
+               log.Error(err, "Failed to create Vlan", "stdout", stdout, "stderr", stderr)
                return err
        }
-       // Create a single common distributed router for the cluster.
-       stdout, stderr, err = RunOVNNbctlUnix("--", "--may-exist", "lr-add", name, "--", "set", "logical_router", name, "external_ids:ovn4nfv-cluster-router=yes")
+       stdout, stderr, err = RunIP("link", "set", logicalInterfaceName, "alias", "nfn-"+logicalInterfaceName)
        if err != nil {
-               logrus.Errorf("Failed to create a single common distributed router for the cluster, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
+               log.Error(err, "Failed to create Vlan", "stdout", stdout, "stderr", stderr)
                return err
        }
-       // Create a logical switch called "ovn4nfv-join" that will be used to connect gateway routers to the distributed router.
-       // The "ovn4nfv-join" will be allocated IP addresses in the range 100.64.1.0/24.
-       stdout, stderr, err = RunOVNNbctlUnix("--may-exist", "ls-add", "ovn4nfv-join")
+        stdout, stderr, err = RunIP("link", "set", "dev", logicalInterfaceName, "up")
+        if err != nil {
+                log.Error(err, "Failed to enable Vlan", "stdout", stdout, "stderr", stderr)
+                return err
+        }
+       return nil
+}
+
+// DeleteVlan deletes VLAN with logicalInterface Name
+func DeleteVlan(logicalInterfaceName string) error {
+       if logicalInterfaceName == "" {
+               return fmt.Errorf("DeleteVlan invalid parameters")
+       }
+       stdout, stderr, err := RunIP("link", "del", "dev", logicalInterfaceName)
        if err != nil {
-               logrus.Errorf("Failed to create logical switch called \"ovn4nfv-join\", stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
+               log.Error(err, "Failed to create Vlan", "stdout", stdout, "stderr", stderr)
                return err
        }
-       // Connect the distributed router to "ovn4nfv-join".
-       routerMac, stderr, err := RunOVNNbctlUnix("--if-exist", "get", "logical_router_port", "rtoj-"+name, "mac")
+       return nil
+}
+
+// GetVlan returns a list of VLAN configured on the node
+func GetVlan() []string {
+       var intfList []string
+       links, err := netlink.LinkList()
+       if err != nil {
+
+       }
+       for _, l := range links {
+               if strings.Contains(l.Attrs().Alias, "nfn-") {
+                       intfList = append(intfList, l.Attrs().Name)
+               }
+       }
+       return intfList
+}
+
+// CreatePnBridge creates Provider network bridge and mappings
+func CreatePnBridge(nwName, brName, intfName string) error {
+       if nwName == "" || brName == "" || intfName == "" {
+               return fmt.Errorf("CreatePnBridge invalid parameters")
+       }
+       // Create Bridge
+       stdout, stderr, err := RunOVSVsctl("--may-exist", "add-br", brName)
        if err != nil {
-               logrus.Errorf("Failed to get logical router port rtoj-%v, stderr: %q, error: %v", name, stderr, err)
+               log.Error(err, "Failed to create Bridge", "stdout", stdout, "stderr", stderr)
                return err
        }
-       if routerMac == "" {
-               routerMac = generateMac()
-               stdout, stderr, err = RunOVNNbctlUnix("--", "--may-exist", "lrp-add", name, "rtoj-"+name, routerMac, "100.64.1.1/24", "--", "set", "logical_router_port", "rtoj-"+name, "external_ids:connect_to_ovn4nfvjoin=yes")
+       stdout, stderr, err = RunOVSVsctl("--may-exist", "add-port", brName, intfName)
+       if err != nil {
+               log.Error(err, "Failed to add port to Bridge", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       stdout, stderr, err = RunOVSVsctl("set", "bridge", brName, "external_ids:nfn="+nwName)
+       if err != nil {
+               log.Error(err, "Failed to set nfn-alias", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       // Update ovn-bridge-mappings
+       updateOvnBridgeMapping(brName, nwName, "add")
+       return nil
+}
+
+// DeletePnBridge creates Provider network bridge and mappings
+func DeletePnBridge(nwName, brName string) error {
+       if nwName == "" || brName == "" {
+               return fmt.Errorf("DeletePnBridge invalid parameters")
+       }
+       // Delete Bridge
+       stdout, stderr, err := RunOVSVsctl("--if-exist", "del-br", brName)
+       if err != nil {
+               log.Error(err, "Failed to delete Bridge", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       updateOvnBridgeMapping(brName, nwName, "delete")
+
+       return nil
+}
+
+// GetPnBridge returns Provider networks with external ids
+func GetPnBridge(externalID string) []string {
+       if externalID == "" {
+               log.Error(fmt.Errorf("GetBridge invalid parameters"), "Invalid")
+       }
+       stdout, stderr, err := RunOVSVsctl("list-br")
+       if err != nil {
+               log.Error(err, "No bridges found", "stdout", stdout, "stderr", stderr)
+               return nil
+       }
+       brNames := strings.Split(stdout, "\n")
+       var brList []string
+       for _, name := range brNames {
+               stdout, stderr, err = RunOVSVsctl("get", "bridge", name, "external_ids:"+externalID)
                if err != nil {
-                       logrus.Errorf("Failed to add logical router port rtoj-%v, stdout: %q, stderr: %q, error: %v", name, stdout, stderr, err)
+                       if !strings.Contains(stderr, "no key") {
+                               log.Error(err, "Unknown error reading external_ids", "stdout", stdout, "stderr", stderr)
+                       }
+                       continue
+               }
+               if stdout == "" {
+                       continue
+               } else {
+                       brList = append(brList, name)
+               }
+       }
+       return brList
+}
+
+// Update ovn-bridge-mappings
+func updateOvnBridgeMapping(brName, nwName, action string) error {
+       stdout, stderr, err := RunOVSVsctl("get", "open", ".", "external-ids:ovn-bridge-mappings")
+       if err != nil {
+               if !strings.Contains(stderr, "no key") {
+                       log.Error(err, "Failed to get ovn-bridge-mappings", "stdout", stdout, "stderr", stderr)
                        return err
                }
        }
-       // Connect the switch "ovn4nfv-join" to the router.
-       stdout, stderr, err = RunOVNNbctlUnix("--", "--may-exist", "lsp-add", "ovn4nfv-join", "jtor-"+name, "--", "set", "logical_switch_port", "jtor-"+name, "type=router", "options:router-port=rtoj-"+name, "addresses="+"\""+routerMac+"\"")
+       // Convert csv string to map
+       mm := make(map[string]string)
+       if len(stdout) > 0 {
+               am := strings.Split(stdout, ",")
+               for _, label := range am {
+                       l := strings.Split(label, ":")
+                       if len(l) == 0 {
+                               log.Error(fmt.Errorf("Syntax error label: %v", label), "ovnBridgeMapping")
+                               return nil
+                       }
+                       mm[strings.TrimSpace(l[0])] = strings.TrimSpace(l[1])
+               }
+       }
+       if action == "add" {
+               mm[nwName] = brName
+       } else if action == "delete" {
+               delete(mm, nwName)
+               if len(mm) == 0 {
+                       // No mapping needed
+                       stdout, stderr, err = RunOVSVsctl("remove", "open", ".", "external-ids", "ovn-bridge-mappings")
+                       if err != nil {
+                               log.Error(err, "Failed to remove ovn-bridge-mappings", "stdout", stdout, "stderr", stderr)
+                               return err
+                       }
+                       return nil
+               }
+       } else {
+               return fmt.Errorf("Invalid action %s", action)
+       }
+       var mapping string
+       for key, value := range mm {
+               mapping = mapping + fmt.Sprintf("%s:%s,", key, value)
+       }
+       // Remove trailing ,
+       mapping = mapping[:len(mapping)-1]
+       extIDMap := "external-ids:ovn-bridge-mappings=" + mapping
+
+       stdout, stderr, err = RunOVSVsctl("set", "open", ".", extIDMap)
        if err != nil {
-               logrus.Errorf("Failed to add logical switch port to logical router, stdout: %q, stderr: %q, error: %v", stdout, stderr, err)
+               log.Error(err, "Failed to set ovn-bridge-mappings", "stdout", stdout, "stderr", stderr)
                return err
        }
        return nil
@@ -68,6 +224,97 @@ func parseOvnNetworkObject(ovnnetwork string) ([]map[string]interface{}, error)
        return ovnNet, nil
 }
 
+func setupDistributedRouter(name string) error {
+
+       // Create a single common distributed router for the cluster.
+       stdout, stderr, err := RunOVNNbctl("--", "--may-exist", "lr-add", name, "--", "set", "logical_router", name, "external_ids:ovn4nfv-cluster-router=yes")
+       if err != nil {
+               log.Error(err, "Failed to create a single common distributed router for the cluster", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       // Create a logical switch called "ovn4nfv-join" that will be used to connect gateway routers to the distributed router.
+       // The "ovn4nfv-join" will be allocated IP addresses in the range 100.64.1.0/24.
+       stdout, stderr, err = RunOVNNbctl("--may-exist", "ls-add", "ovn4nfv-join")
+       if err != nil {
+               log.Error(err, "Failed to create logical switch called \"ovn4nfv-join\"", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       // Connect the distributed router to "ovn4nfv-join".
+       routerMac, stderr, err := RunOVNNbctl("--if-exist", "get", "logical_router_port", "rtoj-"+name, "mac")
+       if err != nil {
+               log.Error(err, "Failed to get logical router port rtoj-", "name", name, "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       if routerMac == "" {
+               routerMac = generateMac()
+               stdout, stderr, err = RunOVNNbctl("--", "--may-exist", "lrp-add", name, "rtoj-"+name, routerMac, "100.64.1.1/24", "--", "set", "logical_router_port", "rtoj-"+name, "external_ids:connect_to_ovn4nfvjoin=yes")
+               if err != nil {
+                       log.Error(err, "Failed to add logical router port rtoj", "name", name, "stdout", stdout, "stderr", stderr)
+                       return err
+               }
+       }
+       // Connect the switch "ovn4nfv-join" to the router.
+       stdout, stderr, err = RunOVNNbctl("--", "--may-exist", "lsp-add", "ovn4nfv-join", "jtor-"+name, "--", "set", "logical_switch_port", "jtor-"+name, "type=router", "options:router-port=rtoj-"+name, "addresses="+"\""+routerMac+"\"")
+       if err != nil {
+               log.Error(err, "Failed to add logical switch port to logical router", "stdout", stdout, "stderr", stderr)
+               return err
+       }
+       return nil
+}
+
+// CreateNetwork in OVN controller
+func createOvnLS(name, subnet, gatewayIP, excludeIps string) (gatewayIPMask string, err error) {
+       var stdout, stderr string
+
+       output, stderr, err := RunOVNNbctl("--data=bare", "--no-heading",
+               "--columns=name", "find", "logical_switch", "name="+name)
+       if err != nil {
+               log.Error(err, "Error in reading logical switch", "stderr", stderr)
+               return
+       }
+
+       if strings.Compare(name, output) == 0 {
+               log.V(1).Info("Logical Switch already exists, delete first to update/recreate", "name", name)
+               return "", fmt.Errorf("LS exists")
+       }
+
+       _, cidr, err := net.ParseCIDR(subnet)
+       if err != nil {
+               log.Error(err, "ovnNetwork '%s' invalid subnet CIDR", "name", name)
+               return
+
+       }
+       firstIP := NextIP(cidr.IP)
+       n, _ := cidr.Mask.Size()
+
+       var gwIP net.IP
+       if gatewayIP != "" {
+               gwIP, _, err = net.ParseCIDR(gatewayIP)
+               if err != nil {
+                       // Check if this is a valid IP address
+                       gwIP = net.ParseIP(gatewayIP)
+               }
+       }
+       // If no valid Gateway use the first IP address for GatewayIP
+       if gwIP == nil {
+               gatewayIPMask = fmt.Sprintf("%s/%d", firstIP.String(), n)
+       } else {
+               gatewayIPMask = fmt.Sprintf("%s/%d", gwIP.String(), n)
+       }
+
+       // Create a logical switch and set its subnet.
+       if excludeIps != "" {
+               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)
+       } else {
+               stdout, stderr, err = RunOVNNbctl("--wait=hv", "--", "--may-exist", "ls-add", name, "--", "set", "logical_switch", name, "other-config:subnet="+subnet, "external-ids:gateway_ip="+gatewayIPMask)
+       }
+       if err != nil {
+               log.Error(err, "Failed to create a logical switch", "name", name, "stdout", stdout, "stderr", stderr)
+               return
+       }
+       return
+}
+
 // generateMac generates mac address.
 func generateMac() string {
        prefix := "00:00:00"
@@ -92,3 +339,61 @@ func ipToInt(ip net.IP) *big.Int {
 func intToIP(i *big.Int) net.IP {
        return net.IP(i.Bytes())
 }
+
+// Get Subnet for a logical bridge
+func GetNetworkSubnet(nw string) (string, error) {
+        stdout, stderr, err := RunOVNNbctl("--if-exists",
+                "get", "logical_switch", nw,
+                "other_config:subnet")
+        if err != nil {
+                log.Error(err, "Failed to subnet for network", "stderr", stderr, "stdout", stdout)
+                return "", err
+        }
+        return stdout, nil
+}
+
+func GetIPAdressForPod(nw string, name string) (string, error) {
+        _, stderr, err := RunOVNNbctl("--data=bare", "--no-heading",
+                "--columns=name", "find", "logical_switch", "name="+nw)
+        if err != nil {
+                log.Error(err, "Error in obtaining list of logical switch", "stderr", stderr)
+                return "", err
+        }
+        stdout, stderr, err := RunOVNNbctl("lsp-list", nw)
+        if err != nil {
+                log.Error(err, "Failed to list ports", "stderr", stderr, "stdout", stdout)
+                return "", err
+        }
+        // stdout format
+        // <port-uuid> (<port-name>)
+        // <port-uuid> (<port-name>)
+        // ...
+        ll := strings.Split(stdout, "\n")
+        if len(ll) == 0 {
+                return "", fmt.Errorf("IPAdress Not Found")
+        }
+        for _, l := range ll {
+                pn := strings.Fields(l)
+                if len(pn) < 2 {
+                        return "", fmt.Errorf("IPAdress Not Found")
+                }
+                if strings.Contains(pn[1], name) {
+                        // Found Port
+                        s := strings.Replace(pn[1], "(", "", -1)
+                        s = strings.Replace(s, ")", "", -1)
+                        dna, stderr, err := RunOVNNbctl("get", "logical_switch_port", s, "dynamic_addresses")
+                        if err != nil {
+                                log.Error(err, "Failed to get dynamic_addresses", "stderr", stderr, "stdout", dna)
+                                return "", err
+                        }
+                        // format - mac:ip
+                        ipAddr := strings.Fields(dna)
+                        if len(ipAddr) < 2 {
+                                return "", fmt.Errorf("IPAdress Not Found")
+                        }
+                        return ipAddr[1], nil
+                }
+        }
+        return "", fmt.Errorf("IPAdress Not Found %s", name)
+}
+