From 34a93a0d414304db883361c8cb9ca39dd9a04800 Mon Sep 17 00:00:00 2001
From: Alex ubuntu vm <alexdecb@yahoo.es>
Date: Wed, 14 Feb 2024 15:36:43 +0100
Subject: [PATCH] database: updated schema

Updated l2sm database schema and operator to go along with it. Now there are more constraints in the database and errors are handled accordingly, so the application is more robust against errors
---
 .vscode/launch.json                           |  18 +
 .../mysql/database-schema.yaml                |  61 ++
 .../mysql/mysql-development.yaml              |  58 ++
 src/operator/l2sm-operator.py                 | 540 +++++++++++-------
 4 files changed, 456 insertions(+), 221 deletions(-)
 create mode 100644 .vscode/launch.json
 create mode 100644 deployments/custom-installation/mysql/database-schema.yaml
 create mode 100644 deployments/custom-installation/mysql/mysql-development.yaml

diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d891394
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,18 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Kopf Run: l2sm-operator",
+            "type": "node-terminal",
+            "request": "launch",
+            "command": "kopf run ${workspaceFolder}/src/operator/l2sm-operator.py",
+            "env": {
+                "CONTROLLER_IP": "10.152.183.3", 
+                "DATABASE_IP": "10.152.183.132",
+                "MYSQL_USER": "l2sm",
+                "MYSQL_PASSWORD": "l2sm",
+                "MYSQL_DATABASE": "l2sm"
+            }
+        }
+    ]
+}
diff --git a/deployments/custom-installation/mysql/database-schema.yaml b/deployments/custom-installation/mysql/database-schema.yaml
new file mode 100644
index 0000000..9acea17
--- /dev/null
+++ b/deployments/custom-installation/mysql/database-schema.yaml
@@ -0,0 +1,61 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mysql-schema
+data:
+  init.sql: |
+  
+    CREATE DATABASE IF NOT EXISTS l2sm;
+    USE l2sm;
+
+    CREATE TABLE networks (
+      id INT PRIMARY KEY AUTO_INCREMENT,
+      name VARCHAR(255) NOT NULL,
+      type ENUM('vlink', 'vnet', 'ext-vnet') NOT NULL,
+      UNIQUE KEY unique_network_name (name, type)
+    );
+
+    CREATE TABLE switches (
+      id INT PRIMARY KEY AUTO_INCREMENT,
+      node_name VARCHAR(255) NOT NULL,
+      openflowId TEXT,
+      ip VARCHAR(15)
+    );
+
+    CREATE TABLE neds (
+      id INT PRIMARY KEY AUTO_INCREMENT,
+      node_name VARCHAR(255) NOT NULL,
+      provider VARCHAR(255) NOT NULL,
+      openflowId TEXT,
+      ip VARCHAR(15)
+    );
+
+    CREATE TABLE interfaces (
+      id INT PRIMARY KEY AUTO_INCREMENT,
+      name VARCHAR(255),
+      pod VARCHAR(255),
+      switch_id INT,
+      ned_id INT,
+      network_id INT,
+      FOREIGN KEY (switch_id) REFERENCES switches(id),
+      FOREIGN KEY (ned_id) REFERENCES neds(id),
+      FOREIGN KEY (network_id) REFERENCES networks(id)
+    );
+
+    -- Define the one-to-many relationship between switches and interfaces
+    ALTER TABLE interfaces
+    ADD CONSTRAINT fk_switch_interface
+    FOREIGN KEY (switch_id)
+    REFERENCES switches(id);
+
+    -- Define the one-to-many relationship between neds and interfaces
+    ALTER TABLE interfaces
+    ADD CONSTRAINT fk_ned_interface
+    FOREIGN KEY (ned_id)
+    REFERENCES neds(id);
+
+    -- Define the many-to-one relationship between networks and interfaces
+    ALTER TABLE interfaces
+    ADD CONSTRAINT fk_network_interface
+    FOREIGN KEY (network_id)
+    REFERENCES networks(id);
diff --git a/deployments/custom-installation/mysql/mysql-development.yaml b/deployments/custom-installation/mysql/mysql-development.yaml
new file mode 100644
index 0000000..461bf30
--- /dev/null
+++ b/deployments/custom-installation/mysql/mysql-development.yaml
@@ -0,0 +1,58 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: mysql-development-pod
+  labels:
+    app: mysql
+spec:
+  containers:
+  - name: mysql
+    image: mysql:5.7
+    envFrom:
+    - secretRef:
+        name: mysql-secret
+    ports:
+      - containerPort: 3306
+        name: mysql
+    volumeMounts:
+      - name: mysql-persistent-storage
+        mountPath: /var/lib/mysql
+      - name: initdb-volume
+        mountPath: /docker-entrypoint-initdb.d
+  volumes:
+    - name: mysql-persistent-storage
+      persistentVolumeClaim:
+        claimName: mysql-pv-claim
+    - name: initdb-volume
+      configMap:
+        name: mysql-schema
+        items:
+        - key: init.sql
+          path: init.sql
+  nodeName: l2sm1
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mysql-development-service
+spec:
+  type: NodePort
+  ports:
+    - port: 3306
+      targetPort: 3306
+      nodePort: 30001
+      protocol: TCP
+  selector:
+    app: mysql
+
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: mysql-secret
+type: Opaque
+data:
+  MYSQL_ROOT_PASSWORD: cGFzc3dvcmQ=  # Base64 encoded "password"
+  MYSQL_USER: bDJzbQ==              # Base64 encoded "l2sm"
+  MYSQL_PASSWORD: bDJzbQ==          # Base64 encoded "l2sm"
+  MYSQL_DATABASE: bDJzbQ==
diff --git a/src/operator/l2sm-operator.py b/src/operator/l2sm-operator.py
index 782ed0c..20e2b5d 100644
--- a/src/operator/l2sm-operator.py
+++ b/src/operator/l2sm-operator.py
@@ -16,9 +16,17 @@ import re
 import sys
 from requests.auth import HTTPBasicAuth
 
-databaseIP = "127.0.0.1"
-baseControllerUrl = 'http://' + os.environ['CONTROLLER_IP'] + ':8181' + '/onos/v1'
-def beginSessionController(baseControllerUrl,username,password):
+database_ip = os.environ['DATABASE_IP']
+database_username = os.environ['MYSQL_USER']
+database_password = os.environ['MYSQL_PASSWORD']
+database_name = os.environ['MYSQL_DATABASE']
+
+base_controller_url = 'http://' + os.environ['CONTROLLER_IP'] + ':8181' + '/onos/v1'
+
+print(base_controller_url)
+print(database_ip)
+
+def begin_session_controller(base_controller_url,username,password):
 
   # Create a session with basic authentication
   auth = HTTPBasicAuth(username, password)
@@ -27,7 +35,7 @@ def beginSessionController(baseControllerUrl,username,password):
   session.auth = auth
 
   #Check if connection is possible
-  response = session.get(baseControllerUrl + '/l2sm/networks/status')
+  response = session.get(base_controller_url + '/l2sm/networks/status')
   if response.status_code == 200:
     # Successful request
     print("Initialized session between operator and controller.")
@@ -35,264 +43,354 @@ def beginSessionController(baseControllerUrl,username,password):
   else:
     print("Could not initialize session with l2sm-controller")
     sys.exit()
-    return None
   
 
   
-session = beginSessionController(baseControllerUrl,"karaf","karaf")
-
-def getSwitchId(cur, node):
-    switchQuery = "SELECT * FROM switches WHERE node = '%s'" % (node)
-    cur.execute(switchQuery)
-    switchRecord = cur.fetchone()
-
-    if switchRecord is not None:
-        switchId = switchRecord[0]
-
-        if switchId is not None:
-            return switchId  # If openflowId is already set, return it
-
-    # If openflowId is not set, make a request to get the information from the API
-    response = session.get(baseControllerUrl + '/devices')
-    devices = response.json().get('devices', [])
-
-    for device in devices:
-        if 'id' in device and 'annotations' in device and 'managementAddress' in device['annotations']:
-            if device['annotations']['managementAddress'] == switchRecord[1]:
-                openflowId = device['id']
-                switchId = openflowId
-
-                # Save the openflowId in the database
-                updateQuery = "UPDATE switches SET openflowId = '%s' WHERE node = '%s'" % (switchId, node)
-                cur.execute(updateQuery)
-
-                return switchId  # Return the openflowId
+session = begin_session_controller(base_controller_url,"karaf","karaf")
+
+def get_openflow_id(node_name):
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try: 
+        with connection.cursor() as cursor:
+            switch_query = "SELECT id, openflowId, ip FROM switches WHERE node_name = %s"
+            cursor.execute(switch_query, (node_name,))
+            switch_record = cursor.fetchone()
+
+            print(switch_record)
+            if switch_record is not None:
+                switchId = switch_record['openflowId']
+
+                if switchId is not None:
+                    return switchId  # If openflowId is already set, return it
+
+            # If openflowId is not set, make a request to get the information from the API
+            response = session.get(base_controller_url + '/devices')
+            devices = response.json().get('devices', [])
+
+            for device in devices:
+                if 'id' in device and 'annotations' in device and 'managementAddress' in device['annotations']:
+                    if device['annotations']['managementAddress'] == switch_record['ip']:
+                        openflowId = device['id']
+                        switchId = openflowId
+
+                        # Save the openflowId in the database
+                        updateQuery = "UPDATE switches SET openflowId = %s WHERE id = %s"
+                        cursor.execute(updateQuery, (openflowId, switch_record['id']))
+                        connection.commit()
+
+                        return switchId  # Return the openflowId
+            connection.commit()
+    finally:
+        connection.close()
+                    
     return None  # Return None if no matching device is found
   
 #POPULATE DATABASE ENTRIES WHEN A NEW L2SM POD IS CREATED (A NEW NODE APPEARS)
 @kopf.on.create('pods.v1', labels={'l2sm-component': 'l2sm-switch'})
 def build_db(body, logger, annotations, **kwargs):
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    cur = db.cursor()
-    
-    #CREATE TABLES IF THEY DO NOT EXIST
-    table1 = "CREATE TABLE IF NOT EXISTS networks (network TEXT NOT NULL, id TEXT NOT NULL);"
-    table2 = "CREATE TABLE IF NOT EXISTS interfaces (interface TEXT NOT NULL, node TEXT NOT NULL, network TEXT, pod TEXT);"
-    table3 = "CREATE TABLE IF NOT EXISTS switches (openflowId TEXT, ip TEXT, node TEXT NOT NULL);"
-    cur.execute(table1)
-    cur.execute(table2)
-    cur.execute(table3)
-    db.commit()
-    
-    #MODIFY THE END VALUE TO ADD MORE INTERFACES
-    values = []
-    for i in range(1,11):
-      values.append(['veth'+str(i), body['spec']['nodeName'], '-1', ''])
-    sqlInterfaces = "INSERT INTO interfaces (interface, node, network, pod) VALUES (%s, %s, %s, %s)"
-    cur.executemany(sqlInterfaces, values)
-    db.commit()
-
-    #ADD The switch identification to the database, without the of13 id yet, as it may not be connected yet.
-    sqlSwitch = "INSERT INTO switches (node) VALUES ('" + body['spec']['nodeName'] + "')"
-    cur.execute(sqlSwitch)
-    db.commit()
-    
-    db.close()
-    logger.info(f"Node {body['spec']['nodeName']} has been registered in the operator")
+    connection = pymysql.connect(host=database_ip,
+                             user=database_username,
+                             password=database_password,
+                             database=database_name,
+                             cursorclass=pymysql.cursors.DictCursor)
+    if 'spec' in body and 'nodeName' in body['spec']:
+      try:
+        with connection.cursor() as cursor:
+          # Step 1: Check if the switch already exists
+          select_switch_sql = "SELECT id FROM switches WHERE node_name = %s;"
+          cursor.execute(select_switch_sql, body['spec']['nodeName'])
+          result = cursor.fetchone()
+          
+          if result:
+              # Switch exists, so update it (if needed)
+              switch_id = result['id']
+              # Example update (modify as needed)
+              # update_switch_sql = "UPDATE switches SET openflowId = %s, IP = %s WHERE id = %s;"
+              # cursor.execute(update_switch_sql, (newOpenflowId, newIP, switch_id))
+          else:
+              # Step 2: Insert a switch since it doesn't exist
+              insert_switch_sql = "INSERT INTO switches (node_name, openflowId, IP) VALUES (%s, NULL, NULL);"
+              cursor.execute(insert_switch_sql, body['spec']['nodeName'])
+              switch_id = cursor.lastrowid
+              # Step 3: Insert interfaces
+              for i in range(1, 11):
+                  interface_name = f"veth{i}"
+                  # Consider adding a check here to see if the interface already exists for the switch
+                  insert_interface_sql = "INSERT INTO interfaces (name, switch_id) VALUES (%s, %s);"
+                  cursor.execute(insert_interface_sql, (interface_name, switch_id))
+              
+          # Commit the changes
+          connection.commit()
+      finally:
+        connection.close()
+      logger.info(f"Node {body['spec']['nodeName']} has been registered in the operator")
+    else:
+        raise kopf.TemporaryError("The Pod is not yet ready", delay=5)
 
 @kopf.on.field('pods.v1', labels={'l2sm-component': 'l2sm-switch'}, field='status.podIP')
 def update_db(body, logger, annotations, **kwargs):
     if 'status' in body and 'podIP' in body['status']:
-      db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-      cur = db.cursor()
-      updateQuery = "UPDATE switches SET ip = '%s', OpenFlowId = NULL WHERE node = '%s'" % (body['status']['podIP'], body['spec']['nodeName'])
-      cur.execute(updateQuery)
-      db.commit()
-      db.close()
+      connection = pymysql.connect(host=database_ip,
+                             user=database_username,
+                             password=database_password,
+                             database=database_name,
+                             cursorclass=pymysql.cursors.DictCursor)
+      try:
+        with connection.cursor() as cursor:
+          updateQuery = "UPDATE switches SET ip = '%s', OpenFlowId = NULL WHERE node_name = '%s'" % (body['status']['podIP'], body['spec']['nodeName'])
+          cursor.execute(updateQuery)
+          connection.commit()
+      finally:
+        connection.close()
       logger.info(f"Updated switch ip")
 
 
-#UPDATE DATABASE WHEN NETWORK IS CREATED, I.E: IS A MULTUS CRD WITH OUR DUMMY INTERFACE PRESENT IN ITS CONFIG
+#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('NetworkAttachmentDefinition', when=lambda spec, **_: '"device": "l2sm-vNet"' in spec['config'])
+@kopf.on.create('NetworkAttachmentDefinition', when=lambda spec, **_: '"type": "l2sm"' in spec['config'])
 def create_vn(spec, name, namespace, logger, **kwargs):
-  
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    cur = db.cursor()
-    id = secrets.token_hex(32)
-    sql = "INSERT INTO networks (network, id) VALUES ('%s', '%s')" % (name.strip(), id.strip())
-    cur.execute(sql)
-    db.commit()
-    db.close()
     
-    # Create the network in the controller, using a post request
-    data = {
-        "networkId": name.strip()
-    }
-    # json_payload = {
-    #     "Content-Type": "application/json",
-    #     "data": payload
-    # }
-    response = session.post(baseControllerUrl + '/l2sm/networks', json=data)
-
-    # Check the response status
-    if response.status_code == 204:
-        logger.info(f"Network has been created")
-        #print("Response:", response.json())
-    else:
-        # Handle errors
-        logger.info(f"Network could not be created, check controller status")
+    # Database connection setup
+    connection = pymysql.connect(host=database_ip,
+                                 user=database_username,
+                                 password=database_password,
+                                 database=database_name,
+                                 cursorclass=pymysql.cursors.DictCursor)
+    try:
+        # Start database transaction
+        with connection.cursor() as cursor:
+            sql = "INSERT INTO networks (name, type) VALUES (%s, %s) ON DUPLICATE KEY UPDATE name = VALUES(name), type = VALUES(type)"
+            cursor.execute(sql, (name.strip(), "vnet"))
+      
+        # Prepare data for the REST API call
+        data = {"networkId": name.strip()}
+        response = session.post(base_controller_url + '/l2sm/networks', json=data)
+        
+        # Check the response status
+        if response.status_code == 204:
+            # Commit database changes only if the network is successfully created in the controller
+            connection.commit()
+            logger.info(f"Network '{name}' has been successfully created in both the database and the L2SM controller.")
+        else:
+            # Roll back the database transaction if the network creation in the controller fails
+            connection.rollback()
+            logger.error(f"Failed to create network '{name}' in the L2SM controller. Database transaction rolled back.")
+            
+    except Exception as e:
+        # Roll back the database transaction in case of any error
+        connection.rollback()
+        logger.error(f"An error occurred: {e}. Rolled back the database transaction.")
+    finally:
+        # Ensure the database connection is closed
+        connection.close()
 
 
 
 #ASSIGN POD TO NETWORK (TRIGGERS ONLY IF ANNOTATION IS PRESENT)
 @kopf.on.create('pods.v1', annotations={'k8s.v1.cni.cncf.io/networks': kopf.PRESENT})
 def pod_vn(body, name, namespace, logger, annotations, **kwargs):
-    #GET MULTUS INTERFACES IN THE DESCRIPTOR
-    #IN QUARANTINE: SLOWER THAN MULTUS!!!!!
-    time.sleep(random.uniform(0,0.8)) #Make sure the database is not consulted at the same time to avoid overlaping
-
-    multusInt = annotations.get('k8s.v1.cni.cncf.io/networks').split(",")
-    #VERIFY IF NETWORK IS PRESENT IN THE CLUSTER
-    api = client.CustomObjectsApi()
-    items = api.list_namespaced_custom_object('k8s.cni.cncf.io', 'v1', namespace, 'network-attachment-definitions').get('items')
-    resources = []
-    # NETWORK POSITION IN ANNOTATION
-    network = []
-
-    #FIND OUR NETWORKS IN MULTUS
-    for i in items:
-      if '"device": "l2sm-vNet"' in i['spec']['config']:
-        resources.append(i['metadata']['name'])
-
-    for k in range(len(multusInt)):
-      multusInt[k] = multusInt[k].strip()
-      if multusInt[k] in resources:
-        network.append(k)
-
-    #IF THERE ARE NO NETWORKS, LET MULTUS HANDLE THIS
-    if not network:
-      return
-
-    #CHECK IF NODE HAS FREE VIRTUAL INTERFACES LEFT
-    v1 = client.CoreV1Api()
-    ret = v1.read_namespaced_pod(name, namespace)
-    node = body['spec']['nodeName']
-
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    nsql = "SELECT * FROM interfaces WHERE node = '%s' AND network = '-1'" % (node.strip())
-    cur = db.cursor()
-    cur.execute(nsql)
-    data = cur.fetchall()
-    if not data or len(data)<len(network):
-      db.close()
-      raise kopf.PermanentError("l2sm could not deploy the pod: Node " + node.strip() + "has no free interfaces left")
-
-    #IF THERE IS ALREADY A MULTUS ANNOTATION, APPEND IT TO THE END.
-    interface_to_attach = []
-    network_array = []
-    j = 0
-    for interface in data[0:len(network)]:
-      network_array.append(multusInt[network[j]])
-      multusInt[network[j]] = interface[0].strip()
-      interface_to_attach.append(interface[0].strip())
-      j = j + 1
-
-    ret.metadata.annotations['k8s.v1.cni.cncf.io/networks'] = ', '.join(multusInt)
-
-  
-
-    #GET NETWORK ID'S
-    #for j in items:
-    #  if network in j['metadata']['name']:
-    #    idsql = "SELECT id FROM networks WHERE network = '%s'" % (network.strip())
-    #    cur.execute(idsql)
-    #    retrieve = cur.fetchone()
-    #    networkN = retrieve[0].strip()
-    #    break
-
-    switchId = getSwitchId(cur, node) # TODO: diferenciar caso en el que es un veth el conectado y el de que es una red de vdd.
-
-    if switchId is None:
-      logger.info(f"The l2sm switch is not connected to controller. Not connecting the pod")
-      return
-    vethPattern = re.compile(r'\d+$')
-    portNumbers = [int(vethPattern.search(interface).group()) for interface in interface_to_attach]
-
-    for m in range(len(network)):
-      sql = "UPDATE interfaces SET network = '%s', pod = '%s' WHERE interface = '%s' AND node = '%s'" % (network_array[m], name, interface_to_attach[m], node)
-      cur.execute(sql)
-
-      payload = {
-        "networkId": network_array[m],
-        "networkEndpoints": [switchId + '/' + str(portNumbers[m])]
-      }
+    """Assign Pod to a network if a specific annotation is present."""
     
-     
-      
-      response = session.post(baseControllerUrl + '/l2sm/networks/port', json=payload)
-
-    #PATCH NETWORK WITH ANNOTATION
-    v1.patch_namespaced_pod(name, namespace, ret)
 
-    db.commit()
-    db.close()
-    
+    # 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:
+        logger.info("No Multus networks specified. Exiting.")
+        return
+
+    existing_networks = get_existing_networks(namespace)
+    target_networks = filter_target_networks(multus_networks, existing_networks)
+    if not target_networks:
+        logger.info("No target networks found. Letting Multus handle the network assignment.")
+        return
+    if 'spec' in body and 'nodeName' in body['spec']:
+        node_name = body['spec']['nodeName']
+           
+        free_interfaces = get_free_interfaces(node_name)
+        if len(free_interfaces) < len(target_networks):
+            raise kopf.PermanentError(f"Node {node_name} has no free interfaces left")
+        
+        openflow_id = get_openflow_id(node_name)
+
+        update_network_assignments(name, namespace, node_name, free_interfaces, target_networks, logger, openflow_id)
+    else:
+        raise kopf.TemporaryError("The Pod is not yet ready", delay=1)
    
-    
 
 
-    # Check the response status
-    if response.status_code == 200:
-        # Successful request
-        print("Request successful!")
-    else:
-        # Handle errors
-        print(f"Error: {response.status_code}")
-    logger.info(f"Pod {name} attached to network {network_array}")
+def extract_multus_networks(annotations):
+  """Extract and return Multus networks from annotations."""
+  return [network.strip() for network in annotations.get('k8s.v1.cni.cncf.io/networks').split(",")]
+
+def get_existing_networks(namespace):
+    """Return existing networks in the namespace."""
+    api = client.CustomObjectsApi()
+    networks = api.list_namespaced_custom_object('k8s.cni.cncf.io', 'v1', namespace, 'network-attachment-definitions').get('items')
+    return [network['metadata']['name'] for network in networks if '"type": "l2sm"' in network['spec']['config']]
+
+def filter_target_networks(multus_networks, existing_networks):
+    """Filter and return networks that are both requested and exist."""
+    return [network for network in multus_networks if network in existing_networks]
+
+def get_free_interfaces(node_name):
+    """Query the database for free interfaces on a node."""
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try:
+        with connection.cursor() as cursor:
+            sql = """
+                    SELECT i.id, i.name
+                    FROM interfaces i
+                    JOIN switches s ON i.switch_id = s.id
+                    WHERE i.network_id IS NULL AND s.node_name = %s;
+                    """            
+            cursor.execute(sql, (node_name.strip(),))
+            free_interfaces = cursor.fetchall()
+    finally:
+        connection.close()
+    return free_interfaces
+
+def update_pod_annotation(pod_name, namespace, interfaces):
+    """Update the Pod's annotation with assigned interfaces."""
+    v1 = client.CoreV1Api()
+    pod = v1.read_namespaced_pod(pod_name, namespace)
+    pod_annotations = pod.metadata.annotations or {}
+    pod_annotations['k8s.v1.cni.cncf.io/networks'] = ', '.join(interfaces)
+    v1.patch_namespaced_pod(pod_name, namespace, {'metadata': {'annotations': pod_annotations}})
+
+def update_network_assignments(pod_name, namespace, node_name, free_interfaces, target_networks, logger, openflow_id):
+    """Update the network assignments in the database and controller."""
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try:
+        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'])
+
+                # 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])
+                            
+            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)}")
+
+# Assuming these functions are implemented as per original logic
+def update_interface_assignment(cursor, interface_id, network_name, pod_name, node_name):
+    """Update a single interface's network assignment in the database."""
+    # First, find the network_id from the network name
+    cursor.execute("SELECT id FROM networks WHERE name = %s", (network_name,))
+    network = cursor.fetchone()
+    if network is None:
+        raise ValueError(f"Network {network_name} does not exist")
+    network_id = network['id']
+
+    # Update the interface with the network_id and pod name
+    sql = """
+    UPDATE interfaces
+    SET pod = %s, network_id = %s
+    WHERE id = %s;
+    """
+    cursor.execute(sql, (pod_name, network_id, interface_id))
+
+def extract_port_number(interface_name):
+    """Extract and return the port number from an interface name."""
+    return int(re.search(r'\d+$', interface_name).group())
+
+def post_network_assignment(openflow_id, port_number, network_name):
+    """Post network assignment to the controller."""
+    payload = {
+        "networkId": network_name,
+        "networkEndpoints": [f"{openflow_id}/{port_number}"]
+    }
+    response = session.post(f"{base_controller_url}/l2sm/networks/port", json=payload)
+    if response.status_code != 204:
+        raise Exception(f"Network assignment failed with status code: {response.status_code}")
+     
 
 
 #UPDATE DATABASE WHEN POD IS DELETED
 @kopf.on.delete('pods.v1', annotations={'k8s.v1.cni.cncf.io/networks': kopf.PRESENT})
 def dpod_vn(name, logger, **kwargs):
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    cur = db.cursor()
-    sql = "UPDATE interfaces SET network = '-1', pod = '' WHERE pod = '%s'" % (name)
-    cur.execute(sql)
-    db.commit()
-    db.close()
-    logger.info(f"Pod {name} removed")
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try:
+        with connection.cursor() as cursor:
+            sql = "UPDATE interfaces SET network_id = NULL, pod = NULL WHERE pod = '%s'" % (name)
+            cursor.execute(sql)
+            connection.commit()
+    finally:
+        connection.close()
+        logger.info(f"Pod {name} removed")
 
 #UPDATE DATABASE WHEN NETWORK IS DELETED
-@kopf.on.delete('NetworkAttachmentDefinition', when=lambda spec, **_: '"device": "l2sm-vNet"' in spec['config'])
+@kopf.on.delete('NetworkAttachmentDefinition', when=lambda spec, **_: '"type": "l2sm"' in spec['config'])
 def delete_vn(spec, name, logger, **kwargs):
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    cur = db.cursor()
-    sql = "DELETE FROM networks WHERE network = '%s'" % (name)
-    cur.execute(sql)
-    
-    
-    response = session.delete(baseControllerUrl + '/l2sm/networks/' + name)
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try:
+        with connection.cursor() as cursor:
+            sql = "DELETE FROM networks WHERE name = '%s' AND type = 'vnet'" % (name)
+            cursor.execute(sql)
+            
+   
     
-    if response.status_code == 204:
-        # Successful request
-      logger.info(f"Network has been deleted")
-      db.commit()
-    else:
-        # Handle errors
-      logger.info(f"Error: {response.status_code}")
-    db.close()
+            response = session.delete(base_controller_url + '/l2sm/networks/' + name)
+            
+            if response.status_code == 204:
+                # Successful request
+                logger.info(f"Network has been deleted")
+                connection.commit()
+            else:
+                # Handle errors
+                logger.info(f"Error: {response.status_code}")
+    finally:
+        connection.close()
+        logger.info(f"Pod {name} removed")
 
 #DELETE DATABASE ENTRIES WHEN A NEW L2SM SWITCH IS DELETED (A NEW NODE GETS OUT OF THE CLUSTER)
 @kopf.on.delete('pods.v1', labels={'l2sm-component': 'l2sm-switch'})
 def remove_node(body, logger, annotations, **kwargs):
-    db = pymysql.connect(host=databaseIP,user="l2sm",password="l2sm;",db="L2SM")
-    cur = db.cursor()
-    sql = "DELETE FROM interfaces WHERE node = '%s'" % (body['spec']['nodeName'])
-    switchSql = "DELETE FROM switches WHERE node = '%s'" % (body['spec']['nodeName'])
-    cur.execute(sql)
-    cur.execute(switchSql)
-    db.commit()
-    db.close()
+    connection = pymysql.connect(host=database_ip,
+                                user=database_username,
+                                password=database_password,
+                                database=database_name,
+                                cursorclass=pymysql.cursors.DictCursor)
+    try:
+        with connection.cursor() as cursor:
+            sql = """
+            DELETE FROM interfaces
+            WHERE switch_id = (SELECT id FROM switches WHERE node_name = '%s');
+            """
+            switchSql = "DELETE FROM switches WHERE node_name = '%s';"
+            cursor.execute(sql,body['spec']['nodeName'])
+            cursor.execute(switchSql,body['spec']['nodeName'])
+            connection.commit()
+    finally:
+        connection.close()
     logger.info(f"Node {body['spec']['nodeName']} has been deleted from the cluster")
 
-- 
GitLab