Openvim controller dhcp server over a ovs vxlan mesh 90/990/12
authorMirabal <leonardo.mirabal@altran.com>
Wed, 18 Jan 2017 16:10:58 +0000 (16:10 +0000)
committerMirabal <leonardo.mirabal@altran.com>
Wed, 8 Feb 2017 11:57:40 +0000 (11:57 +0000)
- Base on dnsmasq per openvim net
- Live in a namesapce and using veth pair
- Support CIDR

Change-Id: Ie152ac73804719ed769f24cc8dc8c1be1421e570
Signed-off-by: Mirabal <leonardo.mirabal@altran.com>
host_thread.py
httpserver.py
openvimd.cfg
openvimd.py
scripts/configure-dhcp-server-UBUNTU16.0.4.sh [new file with mode: 0755]
templates/network.yaml
test/networks/net-example4.yaml [new file with mode: 0644]
vim_db.py
vim_schema.py

index 2e02ede..c29f79a 100644 (file)
@@ -39,20 +39,25 @@ from jsonschema import validate as js_v, exceptions as js_e
 import imp
 from vim_schema import localinfo_schema, hostinfo_schema
 import random
-#from logging import Logger
-#import auxiliary_functions as af
+import os
 
-#TODO: insert a logging system
 
+# from logging import Logger
+# import auxiliary_functions as af
 
-#global lvirt_module
-#lvirt_module=None  #libvirt module is charged only if not in test mode
+# TODO: insert a logging system
+
+
+# global lvirt_module
+# lvirt_module=None  #libvirt module is charged only if not in test mode
 
 class host_thread(threading.Thread):
     lvirt_module = None
-    def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode, develop_bridge_iface):
+
+    def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode,
+                 develop_bridge_iface):
         '''Init a thread.
-        Arguments: 
+        Arguments:
             'id' number of thead
             'name' name of thread
             'host','user':  host ip or name to manage and user
@@ -731,7 +736,11 @@ class host_thread(threading.Thread):
         :return:
         """
 
-        command = 'sudo ovs-vsctl del-port br-int ovim-' + vlan
+        if self.test:
+            return
+
+        port_name = 'ovim-' + vlan
+        command = 'sudo ovs-vsctl del-port br-int ' + port_name
         print self.name, ': command:', command
         (_, stdout, _) = self.ssh_conn.exec_command(command)
         content = stdout.read()
@@ -740,21 +749,73 @@ class host_thread(threading.Thread):
         else:
             return False
 
-    def is_port_free(self, vlan, net_uuid):
+    def delete_dhcp_server(self, vlan, net_uuid, dhcp_path):
+        """
+        Delete dhcp server process lining in namespace
+        :param vlan: segmentation id
+        :param net_uuid: network uuid
+        :param dhcp_path: conf fiel path that live in namespace side
+        :return:
+        """
+        if self.test:
+            return
+        if not self.is_dhcp_port_free(vlan, net_uuid):
+            return True
+
+        net_namespace = 'ovim-' + vlan
+        dhcp_path = os.path.join(dhcp_path, net_namespace)
+        pid_file = os.path.join(dhcp_path, 'dnsmasq.pid')
+
+        command = 'sudo ip netns exec ' + net_namespace + ' cat ' + pid_file
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip netns exec ' + net_namespace + ' kill -9 ' + content
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        # if len(content) == 0:
+        #     return True
+        # else:
+        #     return False
+
+    def is_dhcp_port_free(self, host_id, net_uuid):
+        """
+        Check if any port attached to the a net in a vxlan mesh across computes nodes
+        :param host_id: host id
+        :param net_uuid: network id
+        :return: True if is not free
+        """
+        self.db_lock.acquire()
+        result, content = self.db.get_table(
+            FROM='ports',
+            WHERE={'p.type': 'instance:ovs', 'p.net_id': net_uuid}
+        )
+        self.db_lock.release()
+
+        if len(content) > 0:
+            return False
+        else:
+            return True
+
+    def is_port_free(self, host_id, net_uuid):
         """
         Check if there not ovs ports of a network in a compute host.
-        :param vlan: vlan port id
+        :param host_id:  host id
         :param net_uuid: network id
         :return: True if is not free
         """
+
         self.db_lock.acquire()
         result, content = self.db.get_table(
             FROM='ports as p join instances as i on p.instance_id=i.uuid',
-            WHERE={"i.host_id":  self.host_id, 'p.type': 'instance:ovs', 'p.net_id':  net_uuid}
+            WHERE={"i.host_id": self.host_id, 'p.type': 'instance:ovs', 'p.net_id': net_uuid}
         )
         self.db_lock.release()
 
-        if content > 0:
+        if len(content) > 0:
             return False
         else:
             return True
@@ -763,9 +824,14 @@ class host_thread(threading.Thread):
         """
         Add a bridge linux as a port to a OVS bridge and set a vlan for an specific linux bridge
         :param vlan: vlan port id
-        :return:
+        :return: True if success
         """
-        command = 'sudo ovs-vsctl add-port br-int ovim-' + vlan + ' tag=' + vlan
+
+        if self.test:
+            return
+
+        port_name = 'ovim-' + vlan
+        command = 'sudo ovs-vsctl add-port br-int ' + port_name + ' tag=' + vlan
         print self.name, ': command:', command
         (_, stdout, _) = self.ssh_conn.exec_command(command)
         content = stdout.read()
@@ -774,6 +840,22 @@ class host_thread(threading.Thread):
         else:
             return False
 
+    def delete_dhcp_port(self, vlan, net_uuid):
+        """
+        Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
+        :param vlan: segmentation id
+        :param net_uuid: network id
+        :return: True if success
+        """
+
+        if self.test:
+            return
+
+        if not self.is_dhcp_port_free(vlan, net_uuid):
+            return True
+        self.delete_dhcp_interfaces(vlan)
+        return True
+
     def delete_bridge_port_attached_to_ovs(self, vlan, net_uuid):
         """
         Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself.
@@ -783,11 +865,12 @@ class host_thread(threading.Thread):
         """
         if self.test:
             return
+
         if not self.is_port_free(vlan, net_uuid):
             return True
         self.delete_port_to_ovs_bridge(vlan, net_uuid)
         self.delete_linux_bridge(vlan)
-        return  True
+        return True
 
     def delete_linux_bridge(self, vlan):
         """
@@ -795,7 +878,20 @@ class host_thread(threading.Thread):
         :param vlan: vlan port id
         :return: True if success
         """
-        command = 'sudo ifconfig ovim-' + vlan + ' down &&  sudo brctl delbr ovim-' + vlan
+
+        if self.test:
+            return
+
+        port_name = 'ovim-' + vlan
+        command = 'sudo ip link set dev veth0-' + vlan + ' down'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+        #
+        # if len(content) != 0:
+        #     return False
+
+        command = 'sudo ifconfig ' + port_name + ' down &&  sudo brctl delbr ' + port_name
         print self.name, ': command:', command
         (_, stdout, _) = self.ssh_conn.exec_command(command)
         content = stdout.read()
@@ -821,15 +917,247 @@ class host_thread(threading.Thread):
         :param vlan: netowrk vlan id
         :return:
         """
-        command = 'sudo brctl addbr ovim-' + vlan + ' && sudo ifconfig ovim-' + vlan + ' up'
+
+        if self.test:
+            return
+
+        port_name = 'ovim-' + vlan
+        command = 'sudo brctl show | grep ' + port_name
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        # if exist nothing to create
+        # if len(content) == 0:
+        #     return False
+
+        command = 'sudo brctl addbr ' + port_name
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        # if len(content) == 0:
+        #     return True
+        # else:
+        #     return False
+
+        command = 'sudo brctl stp ' + port_name + ' on'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        # if len(content) == 0:
+        #     return True
+        # else:
+        #     return False
+        command = 'sudo ip link set dev ' + port_name + ' up'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        if len(content) == 0:
+            return True
+        else:
+            return False
+
+    def set_mac_dhcp_server(self, ip, mac, vlan, netmask, dhcp_path):
+        """
+        Write into dhcp conf file a rule to assigned a fixed ip given to an specific MAC address
+        :param ip: IP address asigned to a VM
+        :param mac: VM vnic mac to be macthed with the IP received
+        :param vlan: Segmentation id
+        :param netmask: netmask value
+        :param path: dhcp conf file path that live in namespace side
+        :return: True if success
+        """
+
+        if self.test:
+            return
+
+        net_namespace = 'ovim-' + vlan
+        dhcp_path = os.path.join(dhcp_path, net_namespace)
+        dhcp_hostsdir = os.path.join(dhcp_path, net_namespace)
+
+        if not ip:
+            return False
+
+        ip_data = mac.upper() + ',' + ip
+
+        command = 'sudo  ip netns exec ' + net_namespace + ' touch ' + dhcp_hostsdir
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo  ip netns exec ' + net_namespace + ' sudo bash -ec "echo ' + ip_data + ' >> ' + dhcp_hostsdir + '"'
+
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        if len(content) == 0:
+            return True
+        else:
+            return False
+
+    def delete_mac_dhcp_server(self, ip, mac, vlan, dhcp_path):
+        """
+        Delete into dhcp conf file the ip  assigned to a specific MAC address
+
+        :param ip: IP address asigned to a VM
+        :param mac:  VM vnic mac to be macthed with the IP received
+        :param vlan:  Segmentation id
+        :param dhcp_path: dhcp conf file path that live in namespace side
+        :return:
+        """
+
+        if self.test:
+            return
+
+        net_namespace = 'ovim-' + vlan
+        dhcp_path = os.path.join(dhcp_path, net_namespace)
+        dhcp_hostsdir = os.path.join(dhcp_path, net_namespace)
+
+        if not ip:
+            return False
+
+        ip_data = mac.upper() + ',' + ip
+
+        command = 'sudo  ip netns exec ' + net_namespace + ' sudo sed -i \'/' + ip_data + '/d\' ' + dhcp_hostsdir
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        if len(content) == 0:
+            return True
+        else:
+            return False
+
+    def launch_dhcp_server(self, vlan, ip_range, netmask, dhcp_path):
+        """
+        Generate a linux bridge and attache the port to a OVS bridge
+        :param self:
+        :param vlan: Segmentation id
+        :param ip_range: IP dhcp range
+        :param netmask: network netmask
+        :param dhcp_path: dhcp conf file path that live in namespace side
+        :return: True if success
+        """
+
+        if self.test:
+            return
+
+        interface = 'tap-' + vlan
+        net_namespace = 'ovim-' + vlan
+        dhcp_path = os.path.join(dhcp_path, net_namespace)
+        leases_path = os.path.join(dhcp_path, "dnsmasq.leases")
+        pid_file = os.path.join(dhcp_path, 'dnsmasq.pid')
+
+        dhcp_range = ip_range[0] + ',' + ip_range[1] + ',' + netmask
+
+        command = 'sudo ip netns exec ' + net_namespace + ' mkdir -p ' + dhcp_path
         print self.name, ': command:', command
         (_, stdout, _) = self.ssh_conn.exec_command(command)
         content = stdout.read()
 
-        if len(content) != 0:
+        pid_path = os.path.join(dhcp_path, 'dnsmasq.pid')
+        command = 'sudo  ip netns exec ' + net_namespace + ' cat ' + pid_path
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+        # check if pid is runing
+        pid_status_path = content
+        if content:
+            command = "ps aux | awk '{print $2 }' | grep " + pid_status_path
+            print self.name, ': command:', command
+            (_, stdout, _) = self.ssh_conn.exec_command(command)
+            content = stdout.read()
+        if not content:
+            command = 'sudo  ip netns exec ' + net_namespace + ' /usr/sbin/dnsmasq --strict-order --except-interface=lo ' \
+              '--interface=' + interface + ' --bind-interfaces --dhcp-hostsdir=' + dhcp_path + \
+              ' --dhcp-range ' + dhcp_range + ' --pid-file=' + pid_file + ' --dhcp-leasefile=' + leases_path + '  --listen-address ' + ip_range[0]
+
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.readline()
+
+        if len(content) == 0:
+            return True
+        else:
             return False
 
-        command = 'sudo brctl stp ovim-' + vlan + ' on'
+    def delete_dhcp_interfaces(self, vlan):
+        """
+        Create a linux bridge with STP active
+        :param vlan: netowrk vlan id
+        :return:
+        """
+
+        if self.test:
+            return
+
+        net_namespace = 'ovim-' + vlan
+        command = 'sudo ovs-vsctl del-port br-int ovs-tap-' + vlan
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + vlan + ' down'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip link set dev ovs-tap-' + vlan + ' down'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+    def create_dhcp_interfaces(self, vlan, ip, netmask):
+        """
+        Create a linux bridge with STP active
+        :param vlan: segmentation id
+        :param ip: Ip included in the dhcp range for the tap interface living in namesapce side
+        :param netmask: dhcp net CIDR
+        :return: True if success
+        """
+
+        if self.test:
+            return
+
+        net_namespace = 'ovim-' + vlan
+        namespace_interface = 'tap-' + vlan
+
+        command = 'sudo ip netns add ' + net_namespace
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip link add tap-' + vlan + ' type veth peer name ovs-tap-' + vlan
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ovs-vsctl add-port br-int ovs-tap-' + vlan + ' tag=' + vlan
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip link set tap-' + vlan + ' netns ' + net_namespace
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + vlan + ' up'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo ip link set dev ovs-tap-' + vlan + ' up'
+        print self.name, ': command:', command
+        (_, stdout, _) = self.ssh_conn.exec_command(command)
+        content = stdout.read()
+
+        command = 'sudo  ip netns exec ' + net_namespace + ' ' + ' ifconfig  ' + namespace_interface \
+                  + ' ' + ip + ' netmask ' + netmask
         print self.name, ': command:', command
         (_, stdout, _) = self.ssh_conn.exec_command(command)
         content = stdout.read()
@@ -1111,7 +1439,8 @@ class host_thread(threading.Thread):
                     continue
                 
                 self.db_lock.acquire()
-                result, content = self.db.get_table(FROM='images', SELECT=('path','metadata'),WHERE={'uuid':dev['image_id']} )
+                result, content = self.db.get_table(FROM='images', SELECT=('path', 'metadata'),
+                                                    WHERE={'uuid': dev['image_id']})
                 self.db_lock.release()
                 if result <= 0:
                     error_text = "ERROR", result, content, "when getting image", dev['image_id']
@@ -1591,8 +1920,8 @@ class host_thread(threading.Thread):
                 
             finally:
                 if conn is not None: conn.close()
-                            
-                              
+
+
 def create_server(server, db, db_lock, only_of_ports):
     #print "server"
     #print "server"
@@ -1859,9 +2188,10 @@ def create_server(server, db, db_lock, only_of_ports):
         control_iface['net_id']=control_iface.pop('uuid')
         #Get the brifge name
         db_lock.acquire()
-        result, content = db.get_table(FROM = 'nets',
-                                       SELECT = ('name','type', 'vlan', 'provider'),
-                                       WHERE = {'uuid':control_iface['net_id']})
+        result, content = db.get_table(FROM='nets',
+                                       SELECT=('name', 'type', 'vlan', 'provider', 'enable_dhcp',
+                                                 'dhcp_first_ip', 'dhcp_last_ip', 'cidr'),
+                                       WHERE={'uuid': control_iface['net_id']})
         db_lock.release()
         if result < 0: 
             pass
@@ -1879,6 +2209,12 @@ def create_server(server, db, db_lock, only_of_ports):
                     control_iface["type"] = "instance:bridge"
                 if network.get("vlan"):
                     control_iface["vlan"] = network["vlan"]
+
+                if network.get("enable_dhcp") == 'true':
+                    control_iface["enable_dhcp"] = network.get("enable_dhcp")
+                    control_iface["dhcp_first_ip"] = network["dhcp_first_ip"]
+                    control_iface["dhcp_last_ip"] = network["dhcp_last_ip"]
+                    control_iface["cidr"] = network["cidr"]
             else:
                 if network['type']!='data' and network['type']!='ptp':
                     return -1, "Error at field netwoks: network uuid %s for dataplane interface is not of type data or ptp" % control_iface['net_id']
index cf26544..4062ef1 100644 (file)
@@ -38,6 +38,7 @@ import datetime
 import hashlib
 import os
 import imp
+from netaddr import IPNetwork, IPAddress, all_matching_cidrs
 #import only if needed because not needed in test mode. To allow an easier installation   import RADclass
 from jsonschema import validate as js_v, exceptions as js_e
 import host_thread as ht
@@ -634,6 +635,7 @@ def http_post_hosts():
 
             if config_dic['network_type'] == 'ovs':
                 # create bridge
+                create_dhcp_ovs_bridge()
                 config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge")
                 # check if more host exist
                 create_vxlan_mesh(content['uuid'])
@@ -649,19 +651,142 @@ def http_post_hosts():
         return
 
 
+def get_dhcp_controller():
+    """
+    Create an host_thread object for manage openvim controller and not create a thread for itself
+    :return: dhcp_host openvim controller object
+    """
+
+    if 'openvim_controller' in config_dic['host_threads']:
+        return config_dic['host_threads']['openvim_controller']
+
+    bridge_ifaces = []
+    controller_ip = config_dic['ovs_controller_ip']
+    ovs_controller_user = config_dic['ovs_controller_user']
+
+    host_test_mode = True if config_dic['mode'] == 'test' or config_dic['mode'] == "OF only" else False
+    host_develop_mode = True if config_dic['mode'] == 'development' else False
+
+    dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip, db=config_dic['db'],
+                               db_lock=config_dic['db_lock'], test=host_test_mode,
+                               image_path=config_dic['image_path'], version=config_dic['version'],
+                               host_id='openvim_controller', develop_mode=host_develop_mode,
+                               develop_bridge_iface=bridge_ifaces)
+
+    config_dic['host_threads']['openvim_controller'] = dhcp_host
+    dhcp_host.ssh_connect()
+    return dhcp_host
+
+
+def delete_dhcp_ovs_bridge(vlan, net_uuid):
+    """
+    Delete bridges and port created during dhcp launching at openvim controller
+    :param vlan: net vlan id
+    :param net_uuid: network identifier
+    :return:
+    """
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.delete_dhcp_port(vlan, net_uuid)
+    controller_host.delete_dhcp_server(vlan, net_uuid, dhcp_path)
+
+
+def create_dhcp_ovs_bridge():
+    """
+    Initialize bridge to allocate the dhcp server at openvim controller
+    :return:
+    """
+    controller_host = get_dhcp_controller()
+    controller_host.create_ovs_bridge()
+
+
+def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac):
+    """"
+    Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
+    :param vm_ip: IP address asigned to a VM
+    :param vlan: Segmentation id
+    :param first_ip: First dhcp range ip
+    :param last_ip: Last dhcp range ip
+    :param cidr: net cidr
+    :param mac: VM vnic mac to be macthed with the IP received
+    """
+    if not vm_ip:
+        return
+    ip_tools = IPNetwork(cidr)
+    cidr_len = ip_tools.prefixlen
+    dhcp_netmask = str(ip_tools.netmask)
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    new_cidr = [first_ip + '/' + str(cidr_len)]
+    if not len(all_matching_cidrs(vm_ip, new_cidr)):
+        vm_ip = None
+
+    controller_host = get_dhcp_controller()
+    controller_host.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path)
+
+
+def delete_mac_dhcp(vm_ip, vlan, mac):
+    """
+    Delete into dhcp conf file the ip  assigned to a specific MAC address
+    :param vm_ip: IP address asigned to a VM
+    :param vlan: Segmentation id
+    :param mac:  VM vnic mac to be macthed with the IP received
+    :return:
+    """
+
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path)
+
+
+def launch_dhcp_server(vlan, first_ip, last_ip, cidr):
+    """
+    Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
+    :param vlan: vlan identifier
+    :param first_ip: First dhcp range ip
+    :param last_ip: Last dhcp range ip
+    :param cidr: net cidr
+    :return:
+    """
+    ip_tools = IPNetwork(cidr)
+    dhcp_netmask = str(ip_tools.netmask)
+    ip_range = [first_ip, last_ip]
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.create_linux_bridge(vlan)
+    controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
+    controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path)
+
+
 def create_vxlan_mesh(host_id):
+    """
+    Create vxlan mesh across all openvimc controller and computes.
+    :param host_id: host identifier
+    :param host_id: host identifier
+    :return:
+    """
+    dhcp_compute_name = get_vxlan_interface("dhcp")
     existing_hosts = get_hosts()
-    if len(existing_hosts['hosts']) > 1:
+    if len(existing_hosts['hosts']) > 0:
+        # vlxan mesh creation between openvim controller and computes
         computes_available = existing_hosts['hosts']
+        controller_host = get_dhcp_controller()
+        for compute in computes_available:
+            vxlan_interface_name = get_vxlan_interface(compute['id'][:8])
+            config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, controller_host.host)
+            controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute['ip_name'])
 
-        # TUNEL openvim controller
-
+        # vlxan mesh creation between openvim computes
         for count, compute_owner in enumerate(computes_available):
             for compute in computes_available:
                 if compute_owner['id'] == compute['id']:
                     pass
                 else:
                     vxlan_interface_name = get_vxlan_interface(compute_owner['id'][:8])
+                    controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute_owner['ip_name'])
                     config_dic['host_threads'][compute['id']].insert_task("new-vxlan",
                                                                           vxlan_interface_name,
                                                                           compute_owner['ip_name'])
@@ -674,13 +799,20 @@ def delete_vxlan_mesh(host_id):
     """
     existing_hosts = get_hosts()
     computes_available = existing_hosts['hosts']
+    #
     vxlan_interface_name = get_vxlan_interface(host_id[:8])
-
+    controller_host = get_dhcp_controller()
+    controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
+    # remove bridge from openvim controller if no more computes exist
+    if len(existing_hosts):
+        controller_host.delete_ovs_bridge()
+    # Remove vxlan mesh
     for compute in computes_available:
         if host_id == compute['id']:
             pass
         else:
-            config_dic['host_threads'][compute['id']].insert_task("del-vxlan",vxlan_interface_name)
+            controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
+            config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name)
 
 
 def get_vxlan_interface(local_uuid):
@@ -1446,6 +1578,11 @@ def http_post_server_id(tenant_id):
         print
         if server_start == 'no':
             content['status'] = 'INACTIVE'
+        dhcp_nets_id = []
+        for net in http_content['server']['networks']:
+            if net['type'] == 'instance:ovs':
+                dhcp_nets_id.append(get_network_id(net['net_id']))
+
         ports_to_free=[]
         new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
         if new_instance_result < 0:
@@ -1466,9 +1603,8 @@ def http_post_server_id(tenant_id):
                 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c
 
             
-            
-        #look for dhcp ip address 
-        r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": new_instance})
+        #look for dhcp ip address
+        r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance})
         if r2 >0:
             for iface in c2:
                 if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]:
@@ -1481,14 +1617,23 @@ def http_post_server_id(tenant_id):
                 server_net = get_network_id(iface['net_id'])
                 if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
                     vlan = str(server_net['network']['provider:vlan'])
-                    config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
+                    dhcp_enable = bool(server_net['network']['enable_dhcp'])
+                    if dhcp_enable:
+                        dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
+                        dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
+                        dhcp_cidr = str(server_net['network']['cidr'])
+                        vm_dhcp_ip = c2[0]["ip_address"]
+                        config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
+
+                        set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
+                        launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr)
+
         #Start server
-        
         server['uuid'] = new_instance
         server_start = server.get('start', 'yes')
 
         if server_start != 'no':
-            server['paused'] = True if server_start == 'paused' else False 
+            server['paused'] = True if server_start == 'paused' else False
             server['action'] = {"start":None}
             server['status'] = "CREATING"
             #Program task
@@ -1650,8 +1795,12 @@ def http_server_action(server_id, tenant_id, action):
                         print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c 
         # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
         for net in net_ovs_list:
+            mac = str(net[3])
+            vm_ip = str(net[2])
             vlan = str(net[1])
             net_id = net[0]
+            delete_dhcp_ovs_bridge(vlan, net_id)
+            delete_mac_dhcp(vm_ip, vlan, mac)
             config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
     return format_out(data)
 
@@ -1705,7 +1854,7 @@ def http_get_networks():
         print "http_get_networks error %d %s" % (result, content)
         bottle.abort(-result, content)
     else:
-        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp') )
+        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
         delete_nulls(content)      
         change_keys_http2db(content, http2db_network, reverse=True)  
         data={'networks' : content}
@@ -1730,7 +1879,7 @@ def get_network_id(network_id):
         print "http_get_networks_id network '%s' not found" % network_id
         bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
     else:
-        convert_boolean(content, ('shared', 'admin_state_up', 'enale_dhcp') )
+        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
         change_keys_http2db(content, http2db_network, reverse=True)        
         #get ports
         result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',), 
@@ -1762,6 +1911,11 @@ def http_post_networks():
     #check valid params
     net_provider = network.get('provider')
     net_type =     network.get('type')
+    net_type = network.get('type')
+    net_enable_dhcp = network.get('enable_dhcp')
+    if net_enable_dhcp:
+        net_cidr = network.get('cidr')
+
     net_vlan =     network.get("vlan")
     net_bind_net = network.get("bind_net")
     net_bind_type= network.get("bind_type")
@@ -1867,12 +2021,15 @@ def http_post_networks():
     network['provider'] = net_provider
     network['type']     = net_type
     network['vlan']     = net_vlan
+
+    if 'enable_dhcp' in network and network['enable_dhcp']:
+        check_dhcp_data_integrity(network)
+
     result, content = my.db.new_row('nets', network, True, True)
     
     if result >= 0:
         if bridge_net!=None:
             bridge_net[3] = content
-        
         if config_dic.get("dhcp_server") and config_dic['network_type'] == 'bridge':
             if network["name"] in config_dic["dhcp_server"].get("nets", () ):
                 config_dic["dhcp_nets"].append(content)
@@ -1887,6 +2044,24 @@ def http_post_networks():
         return
 
 
+def check_dhcp_data_integrity(network):
+    """
+    Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
+    :param network: list with user nets paramters
+    :return:
+    """
+    control_iface = []
+
+    if "cidr" in network:
+        cidr = network["cidr"]
+
+        ips = IPNetwork(cidr)
+        if "dhcp_first_ip" not in network:
+            network["dhcp_first_ip"] = str(ips[2])
+        if "dhcp_last_ip" not in network:
+            network["dhcp_last_ip"] = str(ips[-2])
+
+
 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
 def http_put_network_id(network_id):
     '''update a network_id into the database.'''
@@ -2284,3 +2459,4 @@ def http_delete_port_id(port_id):
         bottle.abort(-result, content)
     return
     
+
index 545b739..47238d3 100644 (file)
@@ -76,6 +76,10 @@ network_vlan_range_end:   4000
 # - ovs :   (by default) Use a vlxan mesh between computes to handle the network overlay.
 # - bridge: Use pre-populated linux bridges with L2 conectivity at compte nodes.
 network_type : ovs
+ovs_controller_ip   :   localhost                   # dhcp controller IP address, must be change in order to
+ovs_controller_user :   "osm_dhcp"                  # User for the dchp controller for OVS networks
+ovs_controller_file_path  :   "/var/lib/openvim"    # Path for dhcp daemon configuration, by default '/var/lib/openvim'
+
 
 #host bridge interfaces for networks
 # Apply only for 'network_type: bridge'
index eb6c2f0..c1a57d9 100755 (executable)
@@ -71,6 +71,8 @@ def load_configuration(configuration_file):
                      'log_level_of': 'ERROR',
                      'bridge_ifaces': {},
                      'network_type': 'ovs',
+                     'ovs_controller_user': 'osm_dhcp',
+                     'ovs_controller_file_path': '/var/lib/',
             }
     try:
         #First load configuration from configuration file
@@ -220,7 +222,7 @@ if __name__=="__main__":
                 ( 'development_bridge' not in config_dic or config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None) ):
             logger.error("'%s' is not a valid 'development_bridge', not one of the 'bridge_ifaces'", config_file)
             exit(-1)
-            
+
         if config_dic['mode'] != 'normal':
             print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
             print "!! Warning, openvimd in TEST mode '%s'" % config_dic['mode']
diff --git a/scripts/configure-dhcp-server-UBUNTU16.0.4.sh b/scripts/configure-dhcp-server-UBUNTU16.0.4.sh
new file mode 100755 (executable)
index 0000000..9eb675e
--- /dev/null
@@ -0,0 +1,148 @@
+#!/bin/bash
+##
+# This file is part of openvim
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+#
+# Authors: Leonardo Mirabal
+# February 2017
+
+
+
+function _usage(){
+    echo -e "Usage: sudo $0  <user-name>  "
+    echo -e "  Configure dhcp server for VIM usage. (version 1.0). Params:"
+    echo -e "     <user-name> Create if not exist and configure this user for openvim to connect"
+    echo -e "     -h --help    this help"
+    exit 1
+}
+
+function _install_packages_dependencies()
+{
+    # Required packages by openvim
+    apt-get -y update
+    apt-get -y install   ethtool build-essential dnsmasq openvswitch-switch
+    echo "Remove unneeded packages....."
+    apt-get -y autoremove
+}
+
+function _add_user_to_visudo()
+{
+# Allow admin users to access without password
+if ! grep -q "#openmano" /etc/sudoers
+then
+    cat >> /home/${option_user}/script_visudo.sh << EOL
+#!/bin/bash
+echo "#openmano allow to group admin to grant root privileges without password" >> \$1
+echo "${option_user} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+EOL
+    chmod +x /home/${option_user}/script_visudo.sh
+    echo "allowing admin user to get root privileges withut password"
+    export EDITOR=/home/${option_user}/script_visudo.sh && sudo -E visudo
+    rm -f /home/${option_user}/script_visudo.sh
+fi
+
+}
+
+function _create_ovs_controller_config_path() {
+    mkdir -p '/var/lib/openvim'
+}
+
+function _install_user() {
+    # create user given by the user and add to groups need it.
+    # Add required groups
+    groupadd -f admin
+
+    # Adds user, default password same as name
+    if grep -q "^${option_user}:" /etc/passwd
+    then
+        #user exist, add to group
+        echo "adding user ${option_user} to group admin"
+        usermod -a -G admin -g admin ${option_user}
+    else
+        #create user if it does not exist
+        [ -z "$FORCE" ] && read -p "user '${option_user}' does not exist, create (Y/n)" kk
+        if ! [ -z "$kk" -o "$kk"="y" -o "$kk"="Y" ]
+        then
+            exit
+        fi
+        echo "creating and configuring user ${option_user}"
+        useradd -m -G admin -g admin ${option_user}
+        #Password
+        if [ -z "$FORCE" ]
+            then
+                echo "Provide a password for ${option_user}"
+                passwd ${option_user}
+            else
+                echo -e "$option_user\n$option_user" | passwd --stdin ${option_user}
+        fi
+    fi
+
+}
+
+
+
+
+#1.2 input parameters
+FORCE=""
+while getopts "h" o; do
+    case "${o}" in
+        h)
+            _usage
+            exit -1
+            ;;
+    esac
+done
+shift $((OPTIND-1))
+
+
+
+if [ $# -lt 1 ]
+then
+  usage
+  exit
+fi
+
+[ -z "$1" ] && echo -e "ERROR: User argument is mandatory, --user=<user>\n"  && _usage
+
+option_user=$1
+
+#check root privileges
+[ "${USER}" != "root" ] && echo "Needed root privileges" >&2 && exit 2
+
+
+echo '
+#################################################################
+#####       INSTALL USER                                    #####
+#################################################################'
+
+_install_user
+_add_user_to_visudo
+
+echo '
+#################################################################
+#####       INSTALL NEEDED PACKETS                          #####
+#################################################################'
+_install_packages_dependencies
+
+_create_ovs_controller_config_path
+
+echo
+echo "Do not forget to copy the public ssh key into /home/${option_user}/.ssh/authorized_keys for authomatic login from openvim controller"
+echo
+
+echo "Reboot the system to make the changes effective"
index d49dd18..da62fae 100644 (file)
@@ -29,4 +29,6 @@ network:
                               #    default            : attached to the default host interface
                               #    null               : for data or ptp types. (To be changed in future versions)
     shared:              true # true, false: if shared it will consider by OPENVIM an EXTERNAL network available at OPENMANO
+    enable_dhcp : true # true, false to activate network dhcp over copmutes OVS mesh
+    cidr:  10.0.0.0/24        # Network CIDR from which to include or exclude addresses used for DHCP service lease offerings.
 
diff --git a/test/networks/net-example4.yaml b/test/networks/net-example4.yaml
new file mode 100644 (file)
index 0000000..1402de7
--- /dev/null
@@ -0,0 +1,28 @@
+##
+# Copyright 2015 Telefónica Investigación y Desarrollo, S.A.U.
+# This file is part of openvim
+# All Rights Reserved.
+#
+# 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.
+#
+# For those usages not covered by the Apache License, Version 2.0 please
+# contact with: nfvlabs@tid.es
+##
+
+network:
+    name:               mgmt
+    type:               bridge_man
+    shared:             true
+    enable_dhcp:        true
+    cidr:               192.168.10.0/24
+
index f2e448d..cdc3dcc 100644 (file)
--- a/vim_db.py
+++ b/vim_db.py
@@ -37,6 +37,7 @@ import uuid as myUuid
 import auxiliary_functions as af
 import json
 import logging
+from netaddr import IPNetwork, IPSet, IPRange, all_matching_cidrs
 
 HTTP_Bad_Request = 400
 HTTP_Unauthorized = 401 
@@ -1389,8 +1390,21 @@ class vim_db():
                     #insert resources
                     nb_bridge_ifaces = nb_cores = nb_ifaces = nb_numas = 0
                     #insert bridged_ifaces
+
                     for iface in bridgedifaces:
                         #generate and insert a iface uuid
+                        if 'enable_dhcp' in iface and iface['enable_dhcp']:
+                            dhcp_first_ip = iface["dhcp_first_ip"]
+                            del iface["dhcp_first_ip"]
+                            dhcp_last_ip = iface["dhcp_last_ip"]
+                            del iface["dhcp_last_ip"]
+                            dhcp_cidr = iface["cidr"]
+                            del iface["cidr"]
+                            del iface["enable_dhcp"]
+                            used_dhcp_ips = self._get_dhcp_ip_used_list(iface["net_id"])
+                            iface["ip_address"] = self.get_free_ip_from_range(dhcp_first_ip, dhcp_last_ip,
+                                                                              dhcp_cidr, used_dhcp_ips)
+
                         iface['uuid'] = str(myUuid.uuid1()) # create_uuid
                         cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'ports')" % (iface['uuid'], uuid)
                         self.logger.debug(cmd)
@@ -1497,6 +1511,58 @@ class vim_db():
                 r,c = self.format_error(e, "new_instance", cmd)
                 if r!=-HTTP_Request_Timeout or retry_==1: return r,c
 
+    def get_free_ip_from_range(self, first_ip, last_ip, cidr, ip_used_list):
+        """
+        Calculate a free IP from a range given
+        :param first_ip: First dhcp ip range
+        :param last_ip: Last dhcp ip range
+        :param cidr: net cidr
+        :param ip_used_list: contain all used ips to avoid ip collisions
+        :return:
+        """
+
+        ip_tools = IPNetwork(cidr)
+        cidr_len = ip_tools.prefixlen
+        ips = IPNetwork(first_ip + '/' + str(cidr_len))
+        ip_used_list.append(str(ips[0])) # first ip
+        ip_used_list.append(str(ips[1])) # gw ip
+        ip_used_list.append(str(ips[-1])) # broadcast ip
+        for vm_ip in ips:
+            if str(vm_ip) not in ip_used_list:
+                return vm_ip
+
+        return None
+
+    def _get_dhcp_ip_used_list(self, net_id):
+        """
+        REtreive from DB all ips already used by the dhcp server for a given net
+        :param net_id:
+        :return:
+        """
+        WHERE={'type': 'instance:ovs', 'net_id': net_id}
+        for retry_ in range(0, 2):
+            cmd = ""
+            self.cur = self.con.cursor(mdb.cursors.DictCursor)
+            select_ = "SELECT uuid, ip_address FROM ports "
+
+            if WHERE is None or len(WHERE) == 0:
+                where_ = ""
+            else:
+                where_ = "WHERE " + " AND ".join(
+                    map(lambda x: str(x) + (" is Null" if WHERE[x] is None else "='" + str(WHERE[x]) + "'"),
+                        WHERE.keys()))
+            limit_ = "LIMIT 100"
+            cmd = " ".join((select_, where_, limit_))
+            self.logger.debug(cmd)
+            self.cur.execute(cmd)
+            ports = self.cur.fetchall()
+            ip_address_list = []
+            for port in ports:
+                ip_address_list.append(port['ip_address'])
+
+            return ip_address_list
+
+
     def delete_instance(self, instance_id, tenant_id, net_dataplane_list, ports_to_free, net_ovs_list, logcause="requested by http"):
         for retry_ in range(0,2):
             cmd=""
@@ -1520,7 +1586,7 @@ class vim_db():
                         net_dataplane_list.append(net[0])
 
                     # get ovs manangement nets
-                    cmd = "SELECT DISTINCT net_id, vlan FROM ports WHERE instance_id='{}' AND net_id is not Null AND "\
+                    cmd = "SELECT DISTINCT net_id, vlan, ip_address, mac FROM ports WHERE instance_id='{}' AND net_id is not Null AND "\
                             "type='instance:ovs'".format(instance_id)
                     self.logger.debug(cmd)
                     self.cur.execute(cmd)
index 5bf555d..6d508a6 100644 (file)
@@ -118,6 +118,10 @@ config_schema = {
         "log_level_db": log_level_schema,
         "log_level_of": log_level_schema,
         "network_type": {"type": "string", "enum": ["ovs", "bridge"]},
+        "ovs_controller_file_path": path_schema,
+        "ovs_controller_user": nameshort_schema,
+
+        "ovs_controller_ip": nameshort_schema
     },
     "patternProperties": {
         "of_*" : {"type": ["string", "integer", "boolean"]}
@@ -626,8 +630,8 @@ network_update_schema = {
                 "provider:physical":net_bind_schema,
                 "cidr":cidr_schema,
                 "enable_dhcp": {"type":"boolean"},
-                "dhcp_first_ip": ip_schema,
-                "dhcp_last_ip": ip_schema,
+                "dhcp_first_ip": ip_schema,
+                "dhcp_last_ip": ip_schema,
                 "bind_net":name_schema, #can be name, or uuid
                 "bind_type":{"oneOf":[{"type":"null"},{"type":"string", "pattern":"^vlan:[0-9]{1,4}$"}]}
             },