From 6e599f13681561664df27fcc5b97dc2691e71088 Mon Sep 17 00:00:00 2001
From: Alex ubuntu vm <alexdecb@yahoo.es>
Date: Mon, 4 Mar 2024 11:13:04 +0100
Subject: [PATCH] operator: bug fix

fixed bug where multus was called before the operator, not allowing the pod to receive an ip address assigned
---
 examples/ping-pong/network.yaml |  2 +-
 examples/ping-pong/ping.yaml    |  3 +-
 examples/ping-pong/pong.yaml    |  2 +-
 src/operator/l2sm-operator.py   | 94 ++++++++++++++++++++++-----------
 4 files changed, 68 insertions(+), 33 deletions(-)

diff --git a/examples/ping-pong/network.yaml b/examples/ping-pong/network.yaml
index b96a334..900bab7 100644
--- a/examples/ping-pong/network.yaml
+++ b/examples/ping-pong/network.yaml
@@ -1,4 +1,4 @@
-apiVersion: l2sm.k8s.local/v1
+apiVersion: l2sm.l2sm.k8s.local/v1
 kind: L2SMNetwork
 metadata:
   name: ping-network
diff --git a/examples/ping-pong/ping.yaml b/examples/ping-pong/ping.yaml
index 665ec1d..fda5515 100644
--- a/examples/ping-pong/ping.yaml
+++ b/examples/ping-pong/ping.yaml
@@ -5,7 +5,8 @@ metadata:
   labels:
     app: ping-pong
   annotations:
-    l2sm/networks: ping-network
+    l2sm/networks: '[{"name": "ping-network", "ips":["192.168.1.6/24"]}]'
+    
 spec:
   containers:
   - name: router
diff --git a/examples/ping-pong/pong.yaml b/examples/ping-pong/pong.yaml
index e9fa4ab..fb20c99 100644
--- a/examples/ping-pong/pong.yaml
+++ b/examples/ping-pong/pong.yaml
@@ -5,7 +5,7 @@ metadata:
   labels:
     app: ping-pong
   annotations:
-    k8s.v1.cni.cncf.io/networks: ping-network
+    l2sm/networks: ping-network
 spec:
   containers:
   - name: router
diff --git a/src/operator/l2sm-operator.py b/src/operator/l2sm-operator.py
index e537416..c8b8a9b 100644
--- a/src/operator/l2sm-operator.py
+++ b/src/operator/l2sm-operator.py
@@ -152,7 +152,7 @@ def update_db(body, logger, annotations, **kwargs):
 
 #UPDATE DATABASE WHEN NETWORK IS CREATED, I.E: IS A MULTUS CRD WITH OUR L2SM INTERFACE PRESENT IN ITS CONFIG
 #@kopf.on.create('NetworkAttachmentDefinition', field="spec.config['device']", value='l2sm-vNet')
-@kopf.on.create('l2sm.k8s.local', 'v1', 'l2sm-networks')
+@kopf.on.create('l2sm.l2sm.k8s.local', 'v1', 'l2smnetworks')
 def create_vn(spec, name, namespace, logger, **kwargs):
     
     # Database connection setup
@@ -183,7 +183,6 @@ def pod_vn(body, name, namespace, logger, annotations, **kwargs):
     """Assign Pod to a network if a specific annotation is present."""
 
     # Avoid database overlap by introducing a random sleep time
-    time.sleep(random.uniform(0, 0.8))
 
     multus_networks = extract_multus_networks(annotations)
     if not multus_networks:
@@ -219,10 +218,10 @@ def pod_vn(body, name, namespace, logger, annotations, **kwargs):
     
 def update_network(l2sm_network_name,pod_name,namespace,api):
     l2sm_network = api.get_namespaced_custom_object(
-        group="l2sm.k8s.local",
+        group="l2sm.l2sm.k8s.local",
         version="v1",
         namespace=namespace,
-        plural="l2sm-networks",
+        plural="l2smnetworks",
         name=l2sm_network_name,
     )
     connected_pods = l2sm_network.get('status', {}).get('connectedPods', [])
@@ -232,10 +231,10 @@ def update_network(l2sm_network_name,pod_name,namespace,api):
         # Apply the patch to the L2SMNetwork
         
         api.patch_namespaced_custom_object(
-            group="l2sm.k8s.local",
+            group="l2sm.l2sm.k8s.local",
             version="v1",
             namespace=namespace,
-            plural="l2sm-networks",
+            plural="l2smnetworks",
             name=l2sm_network_name,
             body=patch
         )
@@ -243,10 +242,10 @@ def update_network(l2sm_network_name,pod_name,namespace,api):
 
 def remove_from_network(l2sm_network_name,pod_name,namespace,api):
     l2sm_network = api.get_namespaced_custom_object(
-        group="l2sm.k8s.local",
+        group="l2sm.l2sm.k8s.local",
         version="v1",
         namespace=namespace,
-        plural="l2sm-networks",
+        plural="l2smnetworks",
         name=l2sm_network_name,
     )
     connected_pods = l2sm_network.get('status', {}).get('connectedPods', [])
@@ -256,10 +255,10 @@ def remove_from_network(l2sm_network_name,pod_name,namespace,api):
         # Apply the patch to the L2SMNetwork
         
         api.patch_namespaced_custom_object(
-            group="l2sm.k8s.local",
+            group="l2sm.l2sm.k8s.local",
             version="v1",
             namespace=namespace,
-            plural="l2sm-networks",
+            plural="l2smnetworks",
             name=l2sm_network_name,
             body=patch
         )
@@ -284,7 +283,7 @@ def extract_multus_networks(annotations):
 def get_existing_networks(namespace):
     """Return existing networks in the namespace."""
     api = client.CustomObjectsApi()
-    networks = api.list_namespaced_custom_object('l2sm.k8s.local', 'v1', namespace, 'l2sm-networks').get('items')
+    networks = api.list_namespaced_custom_object('l2sm.l2sm.k8s.local', 'v1', namespace, 'l2smnetworks').get('items')
     return [network['metadata']['name'] for network in networks if "vnet" in network['spec']['type']]
 
 def filter_target_networks(multus_networks, existing_networks):
@@ -317,19 +316,19 @@ def update_pod_annotation(pod_name, namespace, networks_info):
     v1 = client.CoreV1Api()
     pod = v1.read_namespaced_pod(pod_name, namespace)
     pod_annotations = pod.metadata.annotations or {}
-    
+    print("pod")
+    print(pod_name)
+    print("networks")
+    print(networks_info)
     # Format the annotations based on whether IPs are provided
     formatted_networks = []
     for network_info in networks_info:
-        if network_info['ips']:  # Case B with IP addresses
-            formatted_networks.append(json.dumps({
-                "name": network_info['name'],
-                "ips": network_info['ips']
-            }))
-        else:  # Case A without specific IP addresses
-            formatted_networks.append(network_info['name'])
-            
+        if not network_info.get('ips'):  
+            network_info['ips'] = [generate_random_ipv6_fe80()]  # Ensure this is a list
+        formatted_networks.append(json.dumps(network_info))  # Convert dict to a JSON string
+
     pod_annotations['k8s.v1.cni.cncf.io/networks'] = '[' + ', '.join(formatted_networks) + ']'
+    print(pod_annotations)
     v1.patch_namespaced_pod(pod_name, namespace, {'metadata': {'annotations': pod_annotations}})
 
 
@@ -344,20 +343,19 @@ def update_network_assignments(pod_name, namespace, node_name, free_interfaces,
         assigned_interfaces = []
         with connection.cursor() as cursor:
             for i, interface in enumerate(free_interfaces[:len(target_networks)]):
-                update_interface_assignment(cursor, interface['id'], target_networks[i], pod_name, node_name)
-                assigned_interfaces.append(interface['name'])
-
+                update_interface_assignment(cursor, interface['id'], target_networks[i]['name'], pod_name, node_name)
+                assigned_interfaces.append({"name":interface['name'], "ips": target_networks[i]['ips']})
                 # Assuming function get_openflow_id and session.post logic are implemented elsewhere
                 if openflow_id:
                     port_number = extract_port_number(interface['name'])
-                    post_network_assignment(openflow_id, port_number, target_networks[i])
+                    post_network_assignment(openflow_id, port_number, target_networks[i]['name'])
                             
             update_pod_annotation(pod_name, namespace, assigned_interfaces)
 
         connection.commit()
     finally:
         connection.close()
-    logger.info(f"Pod {pod_name} attached to networks {', '.join(target_networks)}")
+    logger.info(f"Pod {pod_name} attached to networks {', '.join(network['name'] for network in target_networks)}")
 
 # Assuming these functions are implemented as per original logic
 def update_interface_assignment(cursor, interface_id, network_name, pod_name, node_name):
@@ -395,7 +393,7 @@ def post_network_assignment(openflow_id, port_number, network_name):
 
 #UPDATE DATABASE WHEN POD IS DELETED
 @kopf.on.delete('pods.v1', annotations={'l2sm/networks': kopf.PRESENT})
-def dpod_vn(name, logger, **kwargs):
+def dpod_vn(body, name, namespace, logger, annotations, **kwargs):
     connection = pymysql.connect(host=database_ip,
                                 user=database_username,
                                 password=database_password,
@@ -407,13 +405,31 @@ def dpod_vn(name, logger, **kwargs):
             cursor.execute(sql)
             
             connection.commit()
-            update_network()
+            multus_networks = extract_multus_networks(annotations)
+            if not multus_networks:
+                logger.info("No Multus networks specified. Exiting.")
+                return
+
+            existing_networks = get_existing_networks(namespace)
+            # Need to extract just the names from multus_networks for comparison
+            target_networks_info = filter_target_networks([net['name'] for net in multus_networks], existing_networks)
+            if not target_networks_info:
+                logger.info("No target networks found. Letting Multus handle the network assignment.")
+                return
+            
+            # Update `target_networks` to include IP information if available
+            target_networks = [net for net in multus_networks if net['name'] in target_networks_info]
+
+            api = CustomObjectsApi()
+            # Assign pods to each of the target networks
+            for network in target_networks:
+                update_network(network['name'], name, namespace, api)
     finally:
         connection.close()
         logger.info(f"Pod {name} removed")
 
 #UPDATE DATABASE WHEN NETWORK IS DELETED
-@kopf.on.delete('l2sm.k8s.local', 'v1', 'l2sm-networks')
+@kopf.on.delete('l2sm.l2sm.k8s.local', 'v1', 'l2smnetworks')
 def delete_vn(spec, name, logger, **kwargs):
     connection = pymysql.connect(host=database_ip,
                                 user=database_username,
@@ -426,12 +442,12 @@ def delete_vn(spec, name, logger, **kwargs):
             update_interfaces_sql = """
             UPDATE interfaces
             SET network_id = NULL
-            WHERE network_id = (SELECT id FROM networks WHERE name = %s AND type = '%s');
+            WHERE network_id = (SELECT id FROM networks WHERE name = %s AND type = %s);
             """
             cursor.execute(update_interfaces_sql, (name,spec['type']))
 
             # Then, delete the network from networks table
-            delete_network_sql = "DELETE FROM networks WHERE name = %s AND type = '%s';"
+            delete_network_sql = "DELETE FROM networks WHERE name = %s AND type = %s;"
             cursor.execute(delete_network_sql, (name,spec['type']))
             
             
@@ -464,3 +480,21 @@ def remove_node(body, logger, annotations, **kwargs):
         connection.close()
     logger.info(f"Node {body['spec']['nodeName']} has been deleted from the cluster")
 
+
+def generate_random_ipv6_fe80():
+    # IPv6 FE80::/64 starts with '1111 1110 10' and 54 bits of 0s
+    # So we can fix the first 10 bits as '1111 1110 10' 
+    # Then we generate the last 64 bits randomly for the interface ID
+    # Since IPv6 addresses are represented in hexadecimal, we convert the binary values to hexadecimal
+    
+    
+    # Generating the interface ID (64 bits)
+    interface_id = random.getrandbits(64)
+    # Formatting to a 16 character hexadecimal string
+    interface_id_hex = format(interface_id, '016x')
+
+    # Constructing the full IPv6 address in the fe80::/64 range
+    ipv6_address = f"fe80::{interface_id_hex[:4]}:{interface_id_hex[4:8]}:{interface_id_hex[8:12]}:{interface_id_hex[12:]}/64"
+    
+    return ipv6_address
+
-- 
GitLab