From: tierno Date: Wed, 8 Feb 2017 13:21:28 +0000 (+0100) Subject: Merge branch 'v1.1' X-Git-Tag: v2.0.0~58 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2Fopenvim.git;a=commitdiff_plain;h=c1d1d47cee5bd382fd9e2ca4d829aef0f545a0d1;hp=3043d13cc7d1496cd9bdb7a456f12c9fffc25287 Merge branch 'v1.1' Change-Id: I0c27f01914959623bf25905786af9e7dabc141b6 Signed-off-by: tierno --- diff --git a/database_utils/migrate_vim_db.sh b/database_utils/migrate_vim_db.sh index 727a553..362edd3 100755 --- a/database_utils/migrate_vim_db.sh +++ b/database_utils/migrate_vim_db.sh @@ -175,8 +175,9 @@ DATABASE_TARGET_VER_NUM=0 [ $OPENVIM_VER_NUM -ge 4001 ] && DATABASE_TARGET_VER_NUM=5 #0.4.1 => 5 [ $OPENVIM_VER_NUM -ge 4002 ] && DATABASE_TARGET_VER_NUM=6 #0.4.2 => 6 [ $OPENVIM_VER_NUM -ge 4005 ] && DATABASE_TARGET_VER_NUM=7 #0.4.5 => 7 -[ $OPENVIM_VER_NUM -ge 4010 ] && DATABASE_TARGET_VER_NUM=8 #0.4.10 => 8 +[ $OPENVIM_VER_NUM -ge 4010 ] && DATABASE_TARGET_VER_NUM=8 #0.4.10 => 8 [ $OPENVIM_VER_NUM -ge 5001 ] && DATABASE_TARGET_VER_NUM=9 #0.5.1 => 9 +[ $OPENVIM_VER_NUM -ge 5002 ] && DATABASE_TARGET_VER_NUM=10 #0.5.2 => 10 #TODO ... put next versions here @@ -468,6 +469,18 @@ function downgrade_from_9(){ echo "DELETE FROM schema_version WHERE version_int = '9';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 } +function upgrade_to_10(){ + echo " upgrade database from version 0.9 to version 0.10" + echo " change types at 'ports'" + echo "ALTER TABLE ports CHANGE COLUMN type type ENUM('instance:bridge','instance:data','external','instance:ovs','controller:ovs') NOT NULL DEFAULT 'instance:bridge' AFTER status;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (10, '0.10', '0.5.2', 'change ports type, adding instance:ovs', '2017-02-01');"| $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} +function downgrade_from_10(){ + echo " downgrade database from version 0.10 to version 0.9" + echo " change back types at 'ports'" + echo "ALTER TABLE ports CHANGE COLUMN type type ENUM('instance:bridge','instance:data','external') NOT NULL DEFAULT 'instance:bridge' AFTER status;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "DELETE FROM schema_version WHERE version_int = '10';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} #TODO ... put funtions here diff --git a/host_thread.py b/host_thread.py index b9778af..4da5328 100644 --- a/host_thread.py +++ b/host_thread.py @@ -25,9 +25,8 @@ This is thread that interact with the host and the libvirt to manage VM One thread will be launched per host ''' -__author__="Pablo Montes, Alfonso Tierno" -__date__ ="$10-jul-2014 12:07:15$" - +__author__ = "Pablo Montes, Alfonso Tierno, Leonardo Mirabal" +__date__ = "$10-jul-2014 12:07:15$" import json import yaml @@ -40,17 +39,23 @@ 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 + +# TODO: insert a logging system + class host_thread(threading.Thread): - lvirt_module = None # libvirt module is charged only if not in test mode - def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode, develop_bridge_iface): + lvirt_module = None + + 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 @@ -63,7 +68,8 @@ class host_thread(threading.Thread): self.db = db self.db_lock = db_lock self.test = test - if not test and host_thread.lvirt_module == None: + + if not test and not host_thread.lvirt_module: try: module_info = imp.find_module("libvirt") host_thread.lvirt_module = imp.load_module("libvirt", *module_info) @@ -88,7 +94,8 @@ class host_thread(threading.Thread): self.queueLock = threading.Lock() self.taskQueue = Queue.Queue(2000) - + self.ssh_conn = None + def ssh_connect(self): try: #Connect SSH @@ -331,6 +338,27 @@ class host_thread(threading.Thread): elif task[0] == 'restore-iface': print self.name, ": processing task restore-iface %s mac=%s" % (task[1], task[2]) self.restore_iface(task[1], task[2]) + elif task[0] == 'new-ovsbridge': + print self.name, ": Creating compute OVS bridge" + self.create_ovs_bridge() + break + elif task[0] == 'new-vxlan': + print self.name, ": Creating vxlan tunnel=" + task[1] + ", remote ip=" + task[2] + self.create_ovs_vxlan_tunnel(task[1], task[2]) + break + elif task[0] == 'del-ovsbridge': + print self.name, ": Deleting OVS bridge" + self.delete_ovs_bridge() + break + elif task[0] == 'del-vxlan': + print self.name, ": Deleting vxlan " + task[1] + " tunnel" + self.delete_ovs_vxlan_tunnel(task[1]) + break + elif task[0] == 'create-ovs-bridge-port': + print self.name, ": Adding port ovim-" + task[1] + " to OVS bridge" + self.create_ovs_bridge_port(task[1]) + elif task[0] == 'del-ovs-port': + self.delete_bridge_port_attached_to_ovs(task[1], task[2]) else: print self.name, ": unknown task", task @@ -589,6 +617,10 @@ class host_thread(threading.Thread): self.tab() + "" elif model==None: model = "virtio" + elif content[0]['provider'][0:3] == "OVS": + vlan = content[0]['provider'].replace('OVS:', '') + text += self.tab() + "" + \ + self.inc_tab() + "" else: return -1, 'Unknown Bridge net provider ' + content[0]['provider'] if model!=None: @@ -677,7 +709,517 @@ class host_thread(threading.Thread): """Decrement and return indentation according to xml_level""" self.xml_level -= 1 return self.tab() - + + def create_ovs_bridge(self): + """ + Create a bridge in compute OVS to allocate VMs + :return: True if success + """ + if self.test: + return + command = 'sudo ovs-vsctl --may-exist add-br br-int -- set Bridge br-int stp_enable=true' + 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_port_to_ovs_bridge(self, vlan, net_uuid): + """ + Delete linux bridge port attched to a OVS bridge, if port is not free the port is not removed + :param vlan: vlan port id + :param net_uuid: network id + :return: + """ + + 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() + if len(content) == 0: + return True + else: + return False + + 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 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} + ) + self.db_lock.release() + + if len(content) > 0: + return False + else: + return True + + def add_port_to_ovs_bridge(self, vlan): + """ + 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: True if success + """ + + 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() + if len(content) == 0: + return True + 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. + :param vlan: + :param net_uuid: + :return: True if success + """ + 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 + + def delete_linux_bridge(self, vlan): + """ + Delete a linux bridge in a scpecific compute. + :param vlan: vlan port id + :return: True if success + """ + + 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() + if len(content) == 0: + return True + else: + return False + + def create_ovs_bridge_port(self, vlan): + """ + Generate a linux bridge and attache the port to a OVS bridge + :param vlan: vlan port id + :return: + """ + if self.test: + return + self.create_linux_bridge(vlan) + self.add_port_to_ovs_bridge(vlan) + + def create_linux_bridge(self, vlan): + """ + Create a linux bridge with STP active + :param vlan: netowrk vlan id + :return: + """ + + 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() + + 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 + + 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() + + if len(content) == 0: + return True + else: + return False + + def create_ovs_vxlan_tunnel(self, vxlan_interface, remote_ip): + """ + Create a vlxn tunnel between to computes with an OVS installed. STP is also active at port level + :param vxlan_interface: vlxan inteface name. + :param remote_ip: tunnel endpoint remote compute ip. + :return: + """ + if self.test: + return + command = 'sudo ovs-vsctl add-port br-int ' + vxlan_interface + \ + ' -- set Interface ' + vxlan_interface + ' type=vxlan options:remote_ip=' + remote_ip + \ + ' -- set Port ' + vxlan_interface + ' other_config:stp-path-cost=10' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + print content + if len(content) == 0: + return True + else: + return False + + def delete_ovs_vxlan_tunnel(self, vxlan_interface): + """ + Delete a vlxan tunnel port from a OVS brdige. + :param vxlan_interface: vlxan name to be delete it. + :return: True if success. + """ + if self.test: + return + command = 'sudo ovs-vsctl del-port br-int ' + vxlan_interface + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + print content + if len(content) == 0: + return True + else: + return False + + def delete_ovs_bridge(self): + """ + Delete a OVS bridge from a compute. + :return: True if success + """ + if self.test: + return + command = 'sudo ovs-vsctl del-br br-int' + 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 get_file_info(self, path): command = 'ls -lL --time-style=+%Y-%m-%dT%H:%M:%S ' + path print self.name, ': command:', command @@ -895,7 +1437,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'] @@ -1000,7 +1543,7 @@ class host_thread(threading.Thread): else: new_status = None domain_dict[uuid] = new_status - conn.close + conn.close() except host_thread.lvirt_module.libvirtError as e: print self.name, ": get_state() Exception '", e.get_error_message() return @@ -1152,8 +1695,8 @@ class host_thread(threading.Thread): else: new_status = 'ACTIVE' elif 'start' in req['action']: - #La instancia está sólo en la base de datos pero no en la libvirt. es necesario crearla - rebuild = True if req['action']['start']=='rebuild' else False + # The instance is only create in DB but not yet at libvirt domain, needs to be create + rebuild = True if req['action']['start'] == 'rebuild' else False r = self.launch_server(conn, req, rebuild, dom) if r[0] <0: new_status = 'ERROR' @@ -1197,7 +1740,7 @@ class host_thread(threading.Thread): conn.close() except host_thread.lvirt_module.libvirtError as e: - if conn is not None: conn.close + if conn is not None: conn.close() text = e.get_error_message() new_status = "ERROR" last_error = text @@ -1270,7 +1813,7 @@ class host_thread(threading.Thread): ret=-1 finally: if lib_conn is None and conn is not None: - conn.close + conn.close() return ret, error_text @@ -1374,9 +1917,9 @@ class host_thread(threading.Thread): print self.name, ": edit_iface(",port["instance_id"],") libvirt exception:", text finally: - if conn is not None: conn.close - - + if conn is not None: conn.close() + + def create_server(server, db, db_lock, only_of_ports): #print "server" #print "server" @@ -1643,7 +2186,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'),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 @@ -1655,6 +2201,18 @@ def create_server(server, db, db_lock, only_of_ports): if network['type']!='bridge_data' and network['type']!='bridge_man': return -1, "Error at field netwoks: network uuid %s for control interface is not of type bridge_man or bridge_data" % control_iface['net_id'] resources['bridged-ifaces'].append(control_iface) + if network.get("provider") and network["provider"][0:3] == "OVS": + control_iface["type"] = "instance:ovs" + else: + 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'] diff --git a/httpserver.py b/httpserver.py index 08ea403..1237fb4 100644 --- a/httpserver.py +++ b/httpserver.py @@ -26,7 +26,7 @@ This is the thread for the http server North API. Two thread will be launched, with normal and administrative permissions. ''' -__author__="Alfonso Tierno, Gerardo Garcia" +__author__="Alfonso Tierno, Gerardo Garcia, Leonardo Mirabal" __date__ ="$10-jul-2014 12:07:15$" import bottle @@ -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 @@ -497,8 +498,12 @@ def enable_cors(): @bottle.route(url_base + '/hosts', method='GET') def http_get_hosts(): - select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_host, - ('id','name','description','status','admin_state_up') ) + return format_out(get_hosts()) + + +def get_hosts(): + select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_host, + ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name')) myself = config_dic['http_threads'][ threading.current_thread().name ] result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_) @@ -511,7 +516,7 @@ def http_get_hosts(): for row in content: row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, ) data={'hosts' : content} - return format_out(data) + return data @bottle.route(url_base + '/hosts/', method='GET') def http_get_host_id(host_id): @@ -633,6 +638,13 @@ def http_post_hosts(): thread.start() config_dic['host_threads'][ content['uuid'] ] = thread + 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']) + #return host data change_keys_http2db(content, http2db_host, reverse=True) if len(warning_text)>0: @@ -643,6 +655,180 @@ def http_post_hosts(): bottle.abort(HTTP_Bad_Request, content) 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']) > 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']) + + # 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']) + + +def delete_vxlan_mesh(host_id): + """ + Create a task for remove a specific compute of the vlxan mesh + :param host_id: host id to be deleted. + """ + 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: + 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): + """ + Genearte a vxlan interface name + :param local_uuid: host id + :return: vlxan-8digits + """ + return 'vxlan-' + local_uuid[:8] + + @bottle.route(url_base + '/hosts/', method='PUT') def http_put_host_id(host_id): '''modify a host into the database. All resources are got and inserted''' @@ -664,12 +850,21 @@ def http_put_host_id(host_id): change_keys_http2db(content, http2db_host, reverse=True) data={'host' : content} + if config_dic['network_type'] == 'ovs': + delete_vxlan_mesh(host_id) + config_dic['host_threads'][host_id].insert_task("del-ovsbridge") + #reload thread config_dic['host_threads'][host_id].name = content.get('name',content['ip_name']) config_dic['host_threads'][host_id].user = content['user'] config_dic['host_threads'][host_id].host = content['ip_name'] config_dic['host_threads'][host_id].insert_task("reload") + if config_dic['network_type'] == 'ovs': + # create mesh with new host data + config_dic['host_threads'][host_id].insert_task("new-ovsbridge") + create_vxlan_mesh(host_id) + #print data return format_out(data) else: @@ -687,9 +882,13 @@ def http_delete_host_id(host_id): result, content = my.db.delete_row('hosts', host_id) if result == 0: bottle.abort(HTTP_Not_Found, content) - elif result >0: - #terminate thread + elif result > 0: + if config_dic['network_type'] == 'ovs': + delete_vxlan_mesh(host_id) + # terminate thread if host_id in config_dic['host_threads']: + if config_dic['network_type'] == 'ovs': + config_dic['host_threads'][host_id].insert_task("del-ovsbridge") config_dic['host_threads'][host_id].insert_task("exit") #return data data={'result' : content} @@ -1411,6 +1610,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: @@ -1431,23 +1635,37 @@ 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}) - if r2 >0 and config_dic.get("dhcp_server"): + #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 iface["net_id"] in config_dic["dhcp_nets"]: + if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]: #print "dhcp insert add task" r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"]) if r < 0: - print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c - - #Start server - + print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c + + #ensure compute contain the bridge for ovs networks: + server_net = get_network_id(iface['net_id']) + if server_net["network"].get('provider:physical', "")[:3] == 'OVS': + vlan = str(server_net['network']['provider: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') + 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 @@ -1585,9 +1803,11 @@ def http_server_action(server_id, tenant_id, action): if new_status != None and new_status == 'DELETING': nets=[] ports_to_free=[] - #look for dhcp ip address + + net_ovs_list = [] + #look for dhcp ip address r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id}) - r,c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, "requested by http") + r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http") for port in ports_to_free: r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port ) if r1 < 0: @@ -1606,7 +1826,15 @@ def http_server_action(server_id, tenant_id, action): #print "dhcp insert del task" if r < 0: 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) @@ -1659,7 +1887,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} @@ -1667,8 +1895,12 @@ def http_get_networks(): @bottle.route(url_base + '/networks/', method='GET') def http_get_network_id(network_id): - my = config_dic['http_threads'][ threading.current_thread().name ] - #obtain data + data = get_network_id(network_id) + return format_out(data) + +def get_network_id(network_id): + my = config_dic['http_threads'][threading.current_thread().name] + # obtain data where_ = bottle.request.query where_['uuid'] = network_id result, content = my.db.get_table(FROM='nets', WHERE=where_, LIMIT=100) @@ -1680,7 +1912,7 @@ def http_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',), @@ -1689,7 +1921,7 @@ def http_get_network_id(network_id): content[0]['ports'] = ports delete_nulls(content[0]) data={'network' : content[0]} - return format_out(data) + return data @bottle.route(url_base + '/networks', method='POST') def http_post_networks(): @@ -1712,6 +1944,10 @@ def http_post_networks(): #check valid params net_provider = network.get('provider') 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") @@ -1786,7 +2022,7 @@ def http_post_networks(): # if bridge_net==None: # bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name) # return - elif net_type=='bridge_data' or net_type=='bridge_man': + elif config_dic['network_type'] == 'bridge' and ( net_type =='bridge_data' or net_type == 'bridge_man' ): #look for a free precreated nets for brnet in config_dic['bridge_nets']: if brnet[3]==None: # free @@ -1805,22 +2041,29 @@ def http_post_networks(): print "using net", bridge_net net_provider = "bridge:"+bridge_net[0] net_vlan = bridge_net[1] - if net_vlan==None and (net_type=="data" or net_type=="ptp"): + elif net_type == 'bridge_data' or net_type == 'bridge_man' and config_dic['network_type'] == 'ovs': + net_provider = 'OVS' + if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"): net_vlan = my.db.get_free_net_vlan() if net_vlan < 0: bottle.abort(HTTP_Internal_Server_Error, "Error getting an available vlan") return - + if net_provider == 'OVS': + net_provider = 'OVS' + ":" + str(net_vlan) + 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"): + 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) print "dhcp_server: add new net", content @@ -1834,6 +2077,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/', method='PUT') def http_put_network_id(network_id): '''update a network_id into the database.''' @@ -2152,7 +2413,7 @@ def http_put_port_id(port_id): if new_net is not None: nets.append(new_net) #put first the new net, so that new openflow rules are created before removing the old ones if old_net is not None: nets.append(old_net) - if port['type'] == 'instance:bridge': + if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs': bottle.abort(HTTP_Forbidden, "bridge interfaces cannot be attached to a different net") return elif port['type'] == 'external': diff --git a/onos.py b/onos.py new file mode 100644 index 0000000..53def9d --- /dev/null +++ b/onos.py @@ -0,0 +1,452 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## +# Copyright 2016, I2T Research Group (UPV/EHU) +# 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: alaitz.mendiola@ehu.eus or alaitz.mendiola@gmail.com +## + +''' +ImplementS the pluging for the Open Network Operating System (ONOS) openflow +controller. It creates the class OF_conn to create dataplane connections +with static rules based on packet destination MAC address +''' + +__author__="Alaitz Mendiola" +__date__ ="$22-nov-2016$" + + +import json +import requests +import base64 +import logging + +class OF_conn(): + '''ONOS connector. No MAC learning is used''' + def __init__(self, params): + ''' Constructor. + Params: dictionary with the following keys: + of_dpid: DPID to use for this controller ?? Does a controller have a dpid? + of_ip: controller IP address + of_port: controller TCP port + of_user: user credentials, can be missing or None + of_password: password credentials + of_debug: debug level for logging. Default to ERROR + other keys are ignored + Raise an exception if same parameter is missing or wrong + ''' + #check params + + if "of_ip" not in params or params["of_ip"]==None or "of_port" not in params or params["of_port"]==None: + raise ValueError("IP address and port must be provided") + #internal variables + self.name = "onos" + self.headers = {'content-type':'application/json', + 'accept':'application/json', + } + + self.auth="None" + self.pp2ofi={} # From Physical Port to OpenFlow Index + self.ofi2pp={} # From OpenFlow Index to Physical Port + + self.dpid = str(params["of_dpid"]) + self.id = 'of:'+str(self.dpid.replace(':', '')) + self.url = "http://%s:%s/onos/v1/" %( str(params["of_ip"]), str(params["of_port"] ) ) + + # TODO This may not be straightforward + if "of_user" in params and params["of_user"]!=None: + if not params.get("of_password"): + of_password="" + else: + of_password=str(params["of_password"]) + self.auth = base64.b64encode(str(params["of_user"])+":"+of_password) + self.headers['authorization'] = 'Basic ' + self.auth + + + self.logger = logging.getLogger('vim.OF.onos') + self.logger.setLevel( getattr(logging, params.get("of_debug", "ERROR")) ) + + def get_of_switches(self): + ''' Obtain a a list of switches or DPID detected by this controller + Return + >=0, list: list length, and a list where each element a tuple pair (DPID, IP address) + <0, text_error: if fails + ''' + try: + self.headers['content-type'] = 'text/plain' + of_response = requests.get(self.url + "devices", headers=self.headers) + error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("get_of_switches " + error_text) + return -1, error_text + + self.logger.debug("get_of_switches " + error_text) + info = of_response.json() + + if type(info) != dict: + self.logger.error("get_of_switches. Unexpected response, not a dict: %s", str(info)) + return -1, "Unexpected response, not a dict. Wrong version?" + + node_list = info.get('devices') + + if type(node_list) is not list: + self.logger.error( + "get_of_switches. Unexpected response, at 'devices', not found or not a list: %s", + str(type(node_list))) + return -1, "Unexpected response, at 'devices', not found or not a list. Wrong version?" + + switch_list = [] + for node in node_list: + node_id = node.get('id') + if node_id is None: + self.logger.error("get_of_switches. Unexpected response at 'device':'id', not found: %s", + str(node)) + return -1, "Unexpected response at 'device':'id', not found . Wrong version?" + + node_ip_address = node.get('annotations').get('managementAddress') + if node_ip_address is None: + self.logger.error( + "get_of_switches. Unexpected response at 'device':'managementAddress', not found: %s", + str(node)) + return -1, "Unexpected response at 'device':'managementAddress', not found. Wrong version?" + + node_id_hex = hex(int(node_id.split(':')[1])).split('x')[1].zfill(16) + + switch_list.append( + (':'.join(a + b for a, b in zip(node_id_hex[::2], node_id_hex[1::2])), node_ip_address)) + + return len(switch_list), switch_list + + except (requests.exceptions.RequestException, ValueError) as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_switches " + error_text) + return -1, error_text + + + + def obtain_port_correspondence(self): + '''Obtain the correspondence between physical and openflow port names + return: + 0, dictionary: with physical name as key, openflow name as value + -1, error_text: if fails + ''' + try: + self.headers['content-type'] = 'text/plain' + of_response = requests.get(self.url + "devices/" + self.id + "/ports", headers=self.headers) + error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("obtain_port_correspondence " + error_text) + return -1, error_text + + self.logger.debug("obtain_port_correspondence " + error_text) + info = of_response.json() + + node_connector_list = info.get('ports') + if type(node_connector_list) is not list: + self.logger.error( + "obtain_port_correspondence. Unexpected response at 'ports', not found or not a list: %s", + str(node_connector_list)) + return -1, "Unexpected response at 'ports', not found or not a list. Wrong version?" + + for node_connector in node_connector_list: + if (node_connector['port'] != "local"): + self.pp2ofi[str(node_connector['annotations']['portName'])] = str(node_connector['port']) + self.ofi2pp[str(node_connector['port'])] = str(node_connector['annotations']['portName']) + + node_ip_address = info['annotations']['managementAddress'] + if node_ip_address is None: + self.logger.error( + "obtain_port_correspondence. Unexpected response at 'managementAddress', not found: %s", + str(self.id)) + return -1, "Unexpected response at 'managementAddress', not found. Wrong version?" + self.ip_address = node_ip_address + + # print self.name, ": obtain_port_correspondence ports:", self.pp2ofi + return 0, self.pp2ofi + + except (requests.exceptions.RequestException, ValueError) as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("obtain_port_correspondence " + error_text) + return -1, error_text + + def get_of_rules(self, translate_of_ports=True): + ''' Obtain the rules inserted at openflow controller + Params: + translate_of_ports: if True it translates ports from openflow index to physical switch name + Return: + 0, dict if ok: with the rule name as key and value is another dictionary with the following content: + priority: rule priority + name: rule name (present also as the master dict key) + ingress_port: match input port of the rule + dst_mac: match destination mac address of the rule, can be missing or None if not apply + vlan_id: match vlan tag of the rule, can be missing or None if not apply + actions: list of actions, composed by a pair tuples: + (vlan, None/int): for stripping/setting a vlan tag + (out, port): send to this port + switch: DPID, all + -1, text_error if fails + ''' + + + if len(self.ofi2pp) == 0: + r, c = self.obtain_port_correspondence() + if r < 0: + return r, c + # get rules + try: + of_response = requests.get(self.url + "flows/" + self.id, headers=self.headers) + error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) + + # The configured page does not exist if there are no rules installed. In that case we return an empty dict + if of_response.status_code == 404: + return 0, {} + + elif of_response.status_code != 200: + self.logger.warning("get_of_rules " + error_text) + return -1, error_text + self.logger.debug("get_of_rules " + error_text) + + info = of_response.json() + + if type(info) != dict: + self.logger.error("get_of_rules. Unexpected response, not a dict: %s", str(info)) + return -1, "Unexpected openflow response, not a dict. Wrong version?" + + flow_list = info.get('flows') + + if flow_list is None: + return 0, {} + + if type(flow_list) is not list: + self.logger.error( + "get_of_rules. Unexpected response at 'flows', not a list: %s", + str(type(flow_list))) + return -1, "Unexpected response at 'flows', not a list. Wrong version?" + + rules = dict() # Response dictionary + + for flow in flow_list: + if not ('id' in flow and 'selector' in flow and 'treatment' in flow and \ + 'instructions' in flow['treatment'] and 'criteria' in \ + flow['selector']): + return -1, "unexpected openflow response, one or more elements are missing. Wrong version?" + + rule = dict() + rule['switch'] = self.dpid + rule['priority'] = flow.get('priority') + rule['name'] = flow['id'] + + for criteria in flow['selector']['criteria']: + if criteria['type'] == 'IN_PORT': + in_port = str(criteria['port']) + if in_port != "CONTROLLER": + if not in_port in self.ofi2pp: + return -1, "Error: Ingress port " + in_port + " is not in switch port list" + if translate_of_ports: + in_port = self.ofi2pp[in_port] + rule['ingress_port'] = in_port + + elif criteria['type'] == 'VLAN_VID': + rule['vlan_id'] = criteria['vlanId'] + + elif criteria['type'] == 'ETH_DST': + rule['dst_mac'] = str(criteria['mac']).lower() + + actions = [] + for instruction in flow['treatment']['instructions']: + if instruction['type'] == "OUTPUT": + out_port = str(instruction['port']) + if out_port != "CONTROLLER": + if not out_port in self.ofi2pp: + return -1, "Error: Output port " + out_port + " is not in switch port list" + + if translate_of_ports: + out_port = self.ofi2pp[out_port] + + actions.append( ('out', out_port) ) + + if instruction['type'] == "L2MODIFICATION" and instruction['subtype'] == "VLAN_POP": + actions.append( ('vlan', 'None') ) + if instruction['type'] == "L2MODIFICATION" and instruction['subtype'] == "VLAN_ID": + actions.append( ('vlan', instruction['vlanId']) ) + + rule['actions'] = actions + rules[flow['id']] = dict(rule) + + return 0, rules + + except (requests.exceptions.RequestException, ValueError) as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_rules " + error_text) + return -1, error_text + + def del_flow(self, flow_name): + ''' Delete an existing rule + Params: flow_name, this is the rule name + Return + 0, None if ok + -1, text_error if fails + ''' + + try: + self.headers['content-type'] = None + of_response = requests.delete(self.url + "flows/" + self.id + "/" + flow_name, headers=self.headers) + error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) + + if of_response.status_code != 204: + self.logger.warning("del_flow " + error_text) + return -1 , error_text + self.logger.debug("del_flow OK " + error_text) + return 0, None + + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("del_flow " + error_text) + return -1, error_text + + def new_flow(self, data): + ''' Insert a new static rule + Params: data: dictionary with the following content: + priority: rule priority + name: rule name + ingress_port: match input port of the rule + dst_mac: match destination mac address of the rule, missing or None if not apply + vlan_id: match vlan tag of the rule, missing or None if not apply + actions: list of actions, composed by a pair tuples with these posibilities: + ('vlan', None/int): for stripping/setting a vlan tag + ('out', port): send to this port + Return + 0, None if ok + -1, text_error if fails + ''' + + if len(self.pp2ofi) == 0: + r,c = self.obtain_port_correspondence() + if r<0: + return r,c + try: + # Build the dictionary with the flow rule information for ONOS + flow = dict() + #flow['id'] = data['name'] + flow['tableId'] = 0 + flow['priority'] = data.get('priority') + flow['timeout'] = 0 + flow['isPermanent'] = "true" + flow['appId'] = 10 # FIXME We should create an appId for OSM + flow['selector'] = dict() + flow['selector']['criteria'] = list() + + # Flow rule matching criteria + if not data['ingress_port'] in self.pp2ofi: + error_text = 'Error. Port ' + data['ingress_port'] + ' is not present in the switch' + self.logger.warning("new_flow " + error_text) + return -1, error_text + + ingress_port_criteria = dict() + ingress_port_criteria['type'] = "IN_PORT" + ingress_port_criteria['port'] = self.pp2ofi[data['ingress_port']] + flow['selector']['criteria'].append(ingress_port_criteria) + + if 'dst_mac' in data: + dst_mac_criteria = dict() + dst_mac_criteria["type"] = "ETH_DST" + dst_mac_criteria["mac"] = data['dst_mac'] + flow['selector']['criteria'].append(dst_mac_criteria) + + if data.get('vlan_id'): + vlan_criteria = dict() + vlan_criteria["type"] = "VLAN_VID" + vlan_criteria["vlanId"] = int(data['vlan_id']) + flow['selector']['criteria'].append(vlan_criteria) + + # Flow rule treatment + flow['treatment'] = dict() + flow['treatment']['instructions'] = list() + flow['treatment']['deferred'] = list() + + for action in data['actions']: + new_action = dict() + if action[0] == "vlan": + new_action['type'] = "L2MODIFICATION" + if action[1] == None: + new_action['subtype'] = "VLAN_POP" + else: + new_action['subtype'] = "VLAN_ID" + new_action['vlanId'] = int(action[1]) + elif action[0] == 'out': + new_action['type'] = "OUTPUT" + if not action[1] in self.pp2ofi: + error_msj = 'Port '+ action[1] + ' is not present in the switch' + return -1, error_msj + new_action['port'] = self.pp2ofi[action[1]] + else: + error_msj = "Unknown item '%s' in action list" % action[0] + self.logger.error("new_flow " + error_msj) + return -1, error_msj + + flow['treatment']['instructions'].append(new_action) + + self.headers['content-type'] = 'application/json' + path = self.url + "flows/" + self.id + of_response = requests.post(path, headers=self.headers, data=json.dumps(flow) ) + + error_text = "Openflow response %d: %s" % (of_response.status_code, of_response.text) + if of_response.status_code != 201: + self.logger.warning("new_flow " + error_text) + return -1 , error_text + + + flowId = of_response.headers['location'][path.__len__() + 1:] + + data['name'] = flowId + + self.logger.debug("new_flow OK " + error_text) + return 0, None + + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("new_flow " + error_text) + return -1, error_text + + def clear_all_flows(self): + ''' Delete all existing rules + Return: + 0, None if ok + -1, text_error if fails + ''' + try: + c, rules = self.get_of_rules(True) + if c < 0: + return -1, "Error retrieving the flows" + + for rule in rules: + self.del_flow(rule) + + self.logger.debug("clear_all_flows OK ") + return 0, None + + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("clear_all_flows " + error_text) + return -1, error_text + + diff --git a/openflow b/openflow index 7ea59c4..80cf624 100755 --- a/openflow +++ b/openflow @@ -205,6 +205,8 @@ def of_add(args): if r<0: print c return -1 + if args.print_id: + print rule["name"] return 0 def of_delete(args): @@ -280,6 +282,7 @@ if __name__=="__main__": add_parser.add_argument("--setvlan", action="append", dest="act", type=int, help="alternative to --actions. Use before --out to set vlan") add_parser.add_argument("--out", action="append", dest="act", type=str, help="alternative to --actions. out= can be used several times") add_parser.add_argument('--debug', '-d', action='store_true', help="show debug information") + add_parser.add_argument('--print-id', action='store_true', help="print the flow id after added") add_parser.set_defaults(func=of_add) delete_parser = subparsers.add_parser('delete', help="delete an openflow rule") diff --git a/openvimd.cfg b/openvimd.cfg index bb852e5..148e992 100644 --- a/openvimd.cfg +++ b/openvimd.cfg @@ -72,8 +72,18 @@ tenant_id: fc7b43b6-6bfa-11e4-84d2-5254006d6777 # Default tenant identifier fo network_vlan_range_start: 3000 network_vlan_range_end: 4000 +# Overlay network implementation. Options are: +# - 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 -# Openvim cannot create bridge networks automatically, in the same way as other CMS do. +# Apply only for 'network_type: bridge' +# Indicates the bridges at compute nodes to be used for the overlay networks # Bridge networks need to be pre-provisioned on each host and Openvim uses those pre-provisioned bridge networks. # Openvim assumes that the following bridge interfaces have been created on each host, appropriately associated to a physical port. # The following information needs to be provided: @@ -82,21 +92,21 @@ network_vlan_range_end: 4000 # - The speed of the physical port in Gbps, where that bridge interface was created # For instance, next example assumes that 10 bridges have been created on each host # using vlans 2001 to 2010, associated to a 1Gbps physical port -bridge_ifaces: - #name: [vlan, speed in Gbps] - virbrMan1: [2001, 1] - virbrMan2: [2002, 1] - virbrMan3: [2003, 1] - virbrMan4: [2004, 1] - virbrMan5: [2005, 1] - virbrMan6: [2006, 1] - virbrMan7: [2007, 1] - virbrMan8: [2008, 1] - virbrMan9: [2009, 1] - virbrMan10: [2010, 1] +#bridge_ifaces: +# #name: [vlan, speed in Gbps] +# virbrMan1: [2001, 1] +# virbrMan2: [2002, 1] +# virbrMan3: [2003, 1] +# virbrMan4: [2004, 1] +# virbrMan5: [2005, 1] +# virbrMan6: [2006, 1] +# virbrMan7: [2007, 1] +# virbrMan8: [2008, 1] +# virbrMan9: [2009, 1] +# virbrMan10: [2010, 1] #Used only when 'mode' is at development'. Indicates which 'bridge_ifaces' is used for dataplane networks -development_bridge: virbrMan10 +#development_bridge: virbrMan10 #DHCP SERVER PARAMETERS. #In case some of the previous 'bridge_ifaces' are connected to an EXTERNAL dhcp server, provide @@ -126,3 +136,5 @@ dhcp_server: log_level: ERROR log_level_db: DEBUG log_level_of: DEBUG + + diff --git a/openvimd.py b/openvimd.py index 9cd321c..7c260b9 100755 --- a/openvimd.py +++ b/openvimd.py @@ -30,9 +30,9 @@ and host controllers __author__="Alfonso Tierno" __date__ ="$10-jul-2014 12:07:15$" -__version__="0.5.2-r516" +__version__="0.5.2-r519" version_date="Jan 2017" -database_version="0.9" #expected database schema version +database_version="0.10" #expected database schema version import httpserver import auxiliary_functions as af @@ -69,6 +69,10 @@ def load_configuration(configuration_file): 'log_level': "DEBUG", 'log_level_db': "ERROR", '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 @@ -214,10 +218,11 @@ if __name__=="__main__": #allow backward compatibility of test_mode option if 'test_mode' in config_dic and config_dic['test_mode']==True: config_dic['mode'] = 'test' - if config_dic['mode'] == 'development' and ( 'development_bridge' not in config_dic or config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None) ): + if config_dic['mode'] == 'development' and config_dic['network_type'] == 'bridge' and \ + ( '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-compute-node-RHEL7.2.sh b/scripts/configure-compute-node-RHEL7.2.sh index b8e06a4..09f5fb2 100644 --- a/scripts/configure-compute-node-RHEL7.2.sh +++ b/scripts/configure-compute-node-RHEL7.2.sh @@ -46,14 +46,27 @@ # Changed the vlan tag used by virbrVIM from 2000 to 1100 function usage(){ - echo -e "Usage: sudo $0 [-y] [ [|dhcp] ]" + echo -e "Usage: sudo $0 [-f] [ [|dhcp] ]" echo -e " Configure compute host for VIM usage. (version 0.4). Params:" - echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password" + echo -e " -f do not prompt for confirmation. If a new user is created, the user name is set as password" echo -e " Create if not exist and configure this user for openvim to connect" - echo -e " if suplied creates bridge interfaces on this interface, needed for openvim" + echo -e " if suplied creates bridge interfaces on this interface, needed for older openvim versions" echo -e " ip or dhcp if suplied, configure the interface with this ip address (/24) or 'dhcp' " + } +function _install_openvswitch(){ + + echo "Installing openvswitch" + curl -O http://openvswitch.org/releases/openvswitch-2.5.1.tar.gz + mkdir -p ~/rpmbuild/SOURCES + cp openvswitch-2.5.1.tar.gz ~/rpmbuild/SOURCES/ + tar -zxvf openvswitch-2.5.1.tar.gz + cp -r openvswitch-2.5.1 ~/rpmbuild/SOURCES/ + rpmbuild -bb --without check ~/rpmbuild/SOURCES/openvswitch-2.5.1/rhel/openvswitch.spec + yum -y localinstall /root/rpmbuild/RPMS/x86_64/openvswitch-2.5.1-1.x86_64.rpm + systemctl start openvswitch.service +} #1 CHECK input parameters #1.1 root privileges @@ -61,9 +74,9 @@ function usage(){ #1.2 input parameters FORCE="" -while getopts "y" o; do +while getopts "f" o; do case "${o}" in - y) + f) FORCE="yes" ;; *) @@ -85,6 +98,7 @@ user_name=$1 interface=$2 ip_iface=$3 + if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null then echo "Error: interface '$interface' is not present in the system" @@ -92,6 +106,7 @@ then exit 1 fi + echo ' ################################################################# ##### INSTALL NEEDED PACKETS ##### @@ -101,7 +116,12 @@ echo ' yum repolist yum check-update yum update -y -yum install -y screen virt-manager ethtool gcc gcc-c++ xorg-x11-xauth xorg-x11-xinit xorg-x11-deprecated-libs libXtst guestfish hwloc libhugetlbfs-utils libguestfs-tools numactl +yum install -y screen virt-manager ethtool gcc gcc-c++ xorg-x11-xauth xorg-x11-xinit xorg-x11-deprecated-libs libXtst \ + guestfish hwloc libhugetlbfs-utils libguestfs-tools numactl + + +# gcc make python-devel openssl-devel kernel-devel graphviz \ kernel-debug-devel autoconf automake rpm-build redhat-rpm-config \ libtool + # Selinux management yum install -y policycoreutils-python @@ -389,6 +409,8 @@ BOOTPROTO=none IPV6INIT=no" >> $interface.tmp mv $interface.tmp ifcfg-$interface +if [ -z $interface ] +then # Management interfaces # integrated_interfaces="" # nb_ifaces=0 @@ -458,7 +480,7 @@ BOOTPROTO=none MTU=9000 BRIDGE=virbrMan$i" > ifcfg-${interface}.20$i2digits done - +fi iface=$interface if [ -n "$ip_iface" ] then @@ -549,8 +571,9 @@ dracut --force # chmod +x /etc/rc.d/rc.local #fi +_install_openvswitch -echo +echo echo "Do not forget to create a shared (NFS, Samba, ...) where original virtual machine images are allocated" echo echo "Do not forget to copy the public ssh key of openvim user into /home/${user_name}/.ssh/authorized_keys for authomatic login from openvim controller" diff --git a/scripts/configure-compute-node-UBUNTU16.0.4.sh b/scripts/configure-compute-node-UBUNTU16.0.4.sh index 9d82c19..ff049db 100755 --- a/scripts/configure-compute-node-UBUNTU16.0.4.sh +++ b/scripts/configure-compute-node-UBUNTU16.0.4.sh @@ -21,7 +21,7 @@ # contact with: nfvlabs@tid.es ## -# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno +# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno, Leonardo Mirabal # June 2015 # Personalize RHEL7.1 on compute nodes @@ -41,13 +41,14 @@ # @base, @core, @development, @network-file-system-client, @virtualization-hypervisor, @virtualization-platform, @virtualization-tools + function usage(){ - echo -e "Usage: sudo $0 [-y] [ [|dhcp] ]" + echo -e "Usage: sudo $0 [-f] []" echo -e " Configure compute host for VIM usage. (version 0.4). Params:" - echo -e " -y do not prompt for confirmation. If a new user is created, the user name is set as password" + echo -e " -f do not prompt for confirmation. If a new user is created, the user name is set as password" echo -e " Create if not exist and configure this user for openvim to connect" - echo -e " if suplied creates bridge interfaces on this interface, needed for openvim" - echo -e " ip or dhcp if suplied, configure the interface with this ip address (/24) or 'dhcp' " + echo -e " [] Only needed for old openvim versions. Iface to create pre-provioned bridges for network conectivity" + } @@ -57,9 +58,9 @@ function usage(){ #1.2 input parameters FORCE="" -while getopts "y" o; do +while getopts "f" o; do case "${o}" in - y) + f) FORCE="yes" ;; *) @@ -81,9 +82,9 @@ fi user_name=$1 interface=$2 -ip_iface=$3 -if [ -n "$interface" ] && ! ifconfig $interface &> /dev/null + +if [ -z $interface ] && ! ifconfig $interface &> /dev/null then echo "Error: interface '$interface' is not present in the system" usage @@ -96,17 +97,14 @@ echo ' #################################################################' # Required packages -apt-get -y update -#apt-get -y install grub-common screen virt-manager ethtool build-essential x11-common x11-utils x11-apps libguestfs-tools hwloc libguestfs-tools numactl vlan nfs-common nfs-kernel-server -apt-get -y install grub-common screen virt-manager ethtool build-essential x11-common x11-utils libguestfs-tools hwloc libguestfs-tools numactl vlan nfs-common nfs-kernel-server - +apt-get -y update +apt-get -y install grub-common screen virt-manager ethtool build-essential x11-common x11-utils \ + libguestfs-tools hwloc libguestfs-tools numactl vlan nfs-common nfs-kernel-server openvswitch-switch echo "Remove unneeded packages....." apt-get -y autoremove # Selinux management #yum install -y policycoreutils-python - - echo ' ################################################################# ##### INSTALL USER ##### @@ -141,24 +139,19 @@ else fi fi -## Allow admin users to access without password -#if ! grep -q "#openmano" /etc/sudoers -#then -# cat >> /home/${user_name}/script_visudo.sh << EOL -##!/bin/bash -#cat \$1 | awk '(\$0~"requiretty"){print "#"\$0}(\$0!~"requiretty"){print \$0}' > tmp -#cat tmp > \$1 -#rm tmp -#echo "" >> \$1 -#echo "#openmano allow to group admin to grant root privileges without password" >> \$1 -#echo "%admin ALL=(ALL) NOPASSWD: ALL" >> \$1 -#EOL -# chmod +x /home/${user_name}/script_visudo.sh -# echo "allowing admin user to get root privileges withut password" -# export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo -# rm -f /home/${user_name}/script_visudo.sh -#fi - +# Allow admin users to access without password +if ! grep -q "#openmano" /etc/sudoers +then + cat >> /home/${user_name}/script_visudo.sh << EOL +#!/bin/bash +echo "#openmano allow to group admin to grant root privileges without password" >> \$1 +echo "${user_name} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +EOL + chmod +x /home/${user_name}/script_visudo.sh + echo "allowing admin user to get root privileges withut password" + export EDITOR=/home/${user_name}/script_visudo.sh && sudo -E visudo + rm -f /home/${user_name}/script_visudo.sh +fi echo ' ################################################################# @@ -365,107 +358,88 @@ echo "Interface ==> $interface" if [ -n "$interface" ] then - - # For management and data interfaces - rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs + # For management and data interfaces + rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs - # Set ONBOOT=on and MTU=9000 on the interface used for the bridges - echo "configuring iface $interface" + # Set ONBOOT=on and MTU=9000 on the interface used for the bridges + echo "configuring iface $interface" -#MTU for interfaces and bridges -MTU=9000 + #MTU for interfaces and bridges + MTU=9000 -cp /etc/network/interfaces interfaces.tmp - - - #Create infrastructure bridge, normally used for connecting to compute nodes, openflow controller, ... + cp /etc/network/interfaces interfaces.tmp + #Create infrastructure bridge, normally used for connecting to compute nodes, openflow controller, ... #Create VLAN for infrastructure bridge - echo " +echo " ######### CUTLINE ######### auto ${interface} iface ${interface} inet manual - post-up ip link set dev ${interface} mtu ${MTU} +post-up ip link set dev ${interface} mtu ${MTU} auto ${interface}.1001 iface ${interface}.1001 inet manual - vlan-raw-device ${interface} - post-up ip link set mtu $MTU dev ${interface}.1001 +vlan-raw-device ${interface} +post-up ip link set mtu $MTU dev ${interface}.1001 " >> interfaces.tmp -# echo "ifconfig ${interface} mtu $MTU -# ifconfig ${interface} up -#" > mtu.tmp + # echo "ifconfig ${interface} mtu $MTU + # ifconfig ${interface} up + #" > mtu.tmp - #Create bridge interfaces - echo "Creating bridge ifaces: " - for ((i=1;i<=20;i++)) - do - i2digits=$i - [ $i -lt 10 ] && i2digits="0$i" - echo " virbrMan$i vlan 20$i2digits" + #Create bridge interfaces + echo "Creating bridge ifaces: " + for ((i=1;i<=20;i++)) + do + i2digits=$i + [ $i -lt 10 ] && i2digits="0$i" + echo " virbrMan$i vlan 20$i2digits" - j=$i + j=$i - echo " +echo " auto ${interface}.20$i2digits iface ${interface}.20$i2digits inet manual - post-up ip link set mtu $MTU dev ${interface}.20$i2digits +vlan-raw-device ${interface} +post-up ip link set mtu $MTU dev ${interface}.20$i2digits auto virbrMan$j iface virbrMan$j inet manual - bridge_ports ${interface}.20$i2digits - post-up ip link set dev virbrMan$j && ip link set mtu 9000 dev virbrMan$j +bridge_ports ${interface}.20$i2digits +post-up ip link set dev virbrMan$j && ip link set mtu $MTU dev virbrMan$j " >> interfaces.tmp -# echo "ifconfig ${interface}.20$i2digits mtu $MTU -#ifconfig virbrMan$j mtu $MTU -#ifconfig virbrMan$j up -#" >> mtu.tmp + done - done + # echo "ifconfig em2.1001 mtu $MTU + #ifconfig virbrInf mtu $MTU + #ifconfig virbrInf up + #" >> mtu.tmp - echo " -auto em2.1001 -iface em2.1001 inet manual - post-up ip link set dev em2.1001 mtu 9000 - -auto virbrInf -iface virbrInf inet manual - bridge_ports em2.1001 - post-up ip link set dev virbrInf && ip link set mtu 9000 dev virbrInf -" >> interfaces.tmp - -# echo "ifconfig em2.1001 mtu $MTU -#ifconfig virbrInf mtu $MTU -#ifconfig virbrInf up -#" >> mtu.tmp - -if ! grep -q "#### CUTLINE ####" /etc/network/interfaces -then - echo "====== Copying interfaces.tmp to /etc/network/interfaces" - cp interfaces.tmp /etc/network/interfaces -fi + if ! grep -q "#### CUTLINE ####" /etc/network/interfaces + then + echo "====== Copying interfaces.tmp to /etc/network/interfaces" + cp interfaces.tmp /etc/network/interfaces + fi #popd fi - -# Activate 8 Virtual Functions per PF on Niantic cards (ixgbe driver) +################### Activate 8 Virtual Functions per PF on Niantic cards (ixgbe driver) if [[ `lsmod | cut -d" " -f1 | grep "ixgbe" | grep -v vf` ]] -then - if ! grep -q "ixgbe" /etc/modprobe.d/ixgbe.conf - then - echo "options ixgbe max_vfs=8" >> /etc/modprobe.d/ixgbe.conf - fi + then + if ! grep -q "ixgbe" /etc/modprobe.d/ixgbe.conf + then + echo "options ixgbe max_vfs=8" >> /etc/modprobe.d/ixgbe.conf + fi -fi + fi echo "#!/bin/bash" > /etc/activate-vfs.sh chmod +x /etc/activate-vfs.sh @@ -524,7 +498,7 @@ exit 0" >> /etc/rc.local #fi chmod a+rwx /var/lib/libvirt/images -mkdir /usr/libexec/ +mkdir -p /usr/libexec/ pushd /usr/libexec/ ln -s /usr/bin/qemu-system-x86_64 qemu-kvm popd diff --git a/scripts/configure-compute-node-develop-UBUNTU16.04.sh b/scripts/configure-compute-node-develop-UBUNTU16.04.sh index 6024a62..2f6d72e 100755 --- a/scripts/configure-compute-node-develop-UBUNTU16.04.sh +++ b/scripts/configure-compute-node-develop-UBUNTU16.04.sh @@ -21,7 +21,7 @@ # contact with: nfvlabs@tid.es ## -# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno +# Authors: Antonio Lopez, Pablo Montes, Alfonso Tierno, Leonardo Mirabal # June 2015 # Personalize RHEL7.1 on compute nodes @@ -46,42 +46,31 @@ set_mtu_path='/etc/' VLAN_INDEX=20 function _usage(){ - echo -e "Usage: sudo $0 [-y] " + echo -e "Usage: sudo $0 [-f] --user= --overlay=ovs --iface-name=" echo -e " Configure compute host for VIM usage. (version 0.4). OPTIONS:" echo -e " -h --help this help" echo -e " -f --force: do not prompt for confirmation. If a new user is created, the user name is set as password" echo -e " -u --user: Create if not exist and configure this user for openvim to connect" - echo -e " --in --iface-name: creates bridge interfaces on this interface, needed for openvim overlay networks" - exit 1 -} - -function _interface_cfg_generator(){ - #$1 interface name | $2 MTU | $3 type + echo -e " -o --overlay: ovs, bridge and bridge-and-ovs. Specify the networking overlay used by openvim, by default ovs is used" + echo -e " -in --iface-name: creates bridge interfaces on this interface, needed for openvim overlay networks" -echo " -auto ${1} -iface ${1} inet ${3} - mtu ${2} - ${bridge_ports} -" >> ${interfaced_path}${1}."cfg" + exit 1 } - function _interface_cfg_generator(){ #$1 interface name | $2 vlan | $3 virbrMan | $4 MTU echo " auto ${1}.${2} iface ${1}.${2} inet manual - mtu ${4} - post-up vconfig add ${1} ${2} - post-down vconfig rem ${1}.${2} + vlan-raw-device ${1} + post-up ip link set mtu $MTU dev ${1}.${2} auto ${3} iface ${3} inet manual bridge_ports ${1}.${2} - mtu ${4} - vlan-raw-device $1 + post-up ip link set dev ${3} && ip link set mtu $MTU dev ${3} + " >> ${interfaced_path}${1}.${2}."cfg" } @@ -89,14 +78,14 @@ function _install_user() { # create user given by the user and add to groups need it. # Add required groups groupadd -f admin - groupadd -f libvirt #for other operating systems may be libvirtd + groupadd -f libvirtd #for other operating systems may be libvirtd # 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 groups libvirt,admin" - usermod -a -G libvirt,admin -g admin ${option_user} + usermod -a -G libvirtd,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 @@ -105,7 +94,7 @@ function _install_user() { exit fi echo "creating and configuring user ${option_user}" - useradd -m -G libvirt,admin -g admin ${option_user} + useradd -m -G libvirtd,admin -g admin ${option_user} #Password if [ -z "$FORCE" ] then @@ -139,13 +128,13 @@ function _openmano_img_2_libvirt_img(){ fi } -function _install_pacckags_dependences() +function _install_packages_dependencies() { # Required packages by openvim apt-get -y update apt-get -y install grub-common screen virt-manager ethtool build-essential \ x11-common x11-utils libguestfs-tools hwloc libguestfs-tools \ - numactl vlan nfs-common nfs-kernel-server + numactl vlan nfs-common nfs-kernel-server openvswitch-switch echo "Remove unneeded packages....." apt-get -y autoremove } @@ -154,8 +143,6 @@ function _network_configuration(){ # adding vlan support grep -q '8021q' '/etc/modules'; [ $? -eq 1 ] && sudo su -c 'echo "8021q" >> /etc/modules' - #grep -q ${interface} '/etc/network/interfaces.d/50-cloud-init.cfg'; [ $? -eq 0 ] && sed -e '/'${interface}'/ s/^#*/#/' -i '/etc/network/interfaces.d/50-cloud-init.cfg' - # Network interfaces static configuration echo "Interface ==> $interface" if [ -n "$interface" ] @@ -164,24 +151,25 @@ function _network_configuration(){ rm -f /etc/udev/rules.d/pci_config.rules # it will be created to define VFs # Set ONBOOT=on and MTU=9000 on the interface used for the bridges echo "configuring iface $interface" + if [ "$option_overlay" == "bridge" ] || [ "$option_overlay" == "bridge-and-ovs" ] + then + # Static network interface configuration and MTU + MTU=9000 + virbrMan_interface_number=20 - # Static network interface configuration and MTU - MTU=9000 - virbrMan_interface_number=20 - - #Create bridge interfaces - echo "Creating bridge ifaces: " - for ((i =1; i <= ${virbrMan_interface_number}; i++)) - do - i2digits=${i} - [ ${i} -lt 10 ] && i2digits="0${i}" - echo " ${interface} ${VLAN_INDEX}${i2digits}" - echo " virbrMan${i} vlan ${VLAN_INDEX}${i2digits}" - j=${i} - #$1 interface name | $2 vlan | $3 MTU | $3 virbrMan | $4 bridge_ports - _interface_cfg_generator ${interface} ${VLAN_INDEX}${i2digits} 'virbrMan'${i} ${MTU} - done - + #Create bridge interfaces + echo "Creating bridge ifaces: " + for ((i =1; i <= ${virbrMan_interface_number}; i++)) + do + i2digits=${i} + [ ${i} -lt 10 ] && i2digits="0${i}" + echo " ${interface} ${VLAN_INDEX}${i2digits}" + echo " virbrMan${i} vlan ${VLAN_INDEX}${i2digits}" + j=${i} + #$1 interface name | $2 vlan | $3 MTU | $3 virbrMan | $4 bridge_ports + _interface_cfg_generator ${interface} ${VLAN_INDEX}${i2digits} 'virbrMan'${i} ${MTU} + done + fi fi } @@ -234,8 +222,30 @@ function _hostinfo_config() echo "#if compute node contain a different name it must be indicated in this file" >> /opt/VNF/images/hostinfo.yaml echo "#with the format extandard-name: compute-name" >> /opt/VNF/images/hostinfo.yaml chmod o+r /opt/VNF/images/hostinfo.yaml + if [ "$interface" != "" -a "$interface" != "em1" ] + then + echo "iface_names:" >> /opt/VNF/images/hostinfo.yaml + echo " em1: ${interface}" >> /opt/VNF/images/hostinfo.yaml + fi } +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 _get_opts() { options="$1" @@ -370,15 +380,19 @@ function _parse_opts() [ -n "$option_force" ] && FORCE="yes" [ -z "$option_user" ] && echo -e "ERROR: User argument is mandatory, --user=\n" >&2 && _usage - #echo "user_name = "$option_user [ -z "$option_iface_name" ] && echo -e "ERROR: iface-name argument is mandatory, --iface-name=\n" && _usage interface=$option_iface_name + if [ "$option_overlay" != "bridge" ] && [ "$option_overlay" != "ovs" ] && [ "$option_overlay" != "bridge-and-ovs" ]; + then + option_overlay='ovs' + echo 'ERROR: overlay argument must be "ovs", "bridge", "bridge-and-ovs"' && _usage + fi } #Parse opts -_get_opts "help:h force:f user:u= iface-name:in= " $* || exit 1 +_get_opts "help:h force:f user:u= overlay:o= iface-name:in= " $* || exit 1 _parse_opts #check root privileges @@ -393,12 +407,12 @@ echo ' ##### INSTALL USER ##### #################################################################' _install_user - +_add_user_to_visudo echo ' ################################################################# ##### INSTALL NEEDED PACKETS ##### #################################################################' -_install_pacckags_dependences +_install_packages_dependencies echo ' ################################################################# @@ -412,7 +426,10 @@ echo ' ################################################################# ##### NETWORK CONFIGURATION ##### #################################################################' + _network_configuration + + _disable_aaparmor _user_remainder_pront diff --git a/scripts/configure-dhcp-server-UBUNTU16.0.4.sh b/scripts/configure-dhcp-server-UBUNTU16.0.4.sh new file mode 100755 index 0000000..9eb675e --- /dev/null +++ b/scripts/configure-dhcp-server-UBUNTU16.0.4.sh @@ -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 " + echo -e " Configure dhcp server for VIM usage. (version 1.0). Params:" + echo -e " 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=\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" diff --git a/scripts/initopenvim.sh b/scripts/initopenvim.sh index 44fe9e2..ae839ba 100755 --- a/scripts/initopenvim.sh +++ b/scripts/initopenvim.sh @@ -195,7 +195,7 @@ then ${DIRvim}/openvim net-create $DIRvim/test/networks/net-example3.yaml || ! echo "fail" >&2 || $_exit 1 printf "%-50s" "Creating openvim tenant 'TEST-admin': " - result=`openvim tenant-create '{"tenant": {"name":"TEST-admin", "description":"admin"}}'` + result=`${DIRvim}/openvim tenant-create '{"tenant": {"name":"TEST-admin", "description":"admin"}}'` vimtenant=`echo $result |gawk '{print $1}'` #check a valid uuid is obtained ! is_valid_uuid $vimtenant && echo "FAIL" && echo " $result" && $_exit 1 diff --git a/templates/network.yaml b/templates/network.yaml index d49dd18..da62fae 100644 --- a/templates/network.yaml +++ b/templates/network.yaml @@ -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 index 0000000..1402de7 --- /dev/null +++ b/test/networks/net-example4.yaml @@ -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 + diff --git a/test/test_openflow.sh b/test/test_openflow.sh index 3f76771..2c9b504 100755 --- a/test/test_openflow.sh +++ b/test/test_openflow.sh @@ -146,7 +146,7 @@ TODELETE="restore" printf "%-50s" "new rule vlan,mac -> no vlan: " rule_name=fromVlanMac_to_NoVlan1 -openflow add $rule_name --priority 1000 --matchmac "aa:bb:cc:dd:ee:ff" --matchvlan 500 --inport $port0 --stripvlan --out $port1 $debug +rule_name=`openflow add $rule_name --priority 1000 --matchmac "aa:bb:cc:dd:ee:ff" --matchvlan 500 --inport $port0 --stripvlan --out $port1 $debug --print-id` [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1 expected="$OF_CONTROLLER_DPID 1000 $rule_name $port0 aa:bb:cc:dd:ee:ff 500 vlan=None,out=$port1" result=`openflow list | grep $rule_name` @@ -158,7 +158,7 @@ TODELETE="$rule_name $TODELETE" printf "%-50s" "new rule mac -> vlan: " rule_name=fromMac_to_Vlan2 -openflow add $rule_name --priority 1001 --matchmac "ff:ff:ff:ff:ff:ff" --inport $port1 --setvlan 501 --out $port2 --out $port3 $debug +rule_name=`openflow add $rule_name --priority 1001 --matchmac "ff:ff:ff:ff:ff:ff" --inport $port1 --setvlan 501 --out $port2 --out $port3 $debug --print-id` [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1 expected="$OF_CONTROLLER_DPID 1001 $rule_name $port1 ff:ff:ff:ff:ff:ff any vlan=501,out=$port2,out=$port3" result=`openflow list | grep $rule_name` @@ -170,7 +170,7 @@ TODELETE="$rule_name $TODELETE" printf "%-50s" "new rule None -> None: " rule_name=fromNone_to_None -openflow add $rule_name --priority 1002 --inport $port2 --out $port0 $debug +rule_name=`openflow add $rule_name --priority 1002 --inport $port2 --out $port0 $debug --print-id` [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1 expected="$OF_CONTROLLER_DPID 1002 $rule_name $port2 any any out=$port0" result=`openflow list | grep $rule_name` @@ -182,7 +182,7 @@ TODELETE="$rule_name $TODELETE" printf "%-50s" "new rule vlan -> vlan: " rule_name=fromVlan_to_Vlan1 -openflow add $rule_name --priority 1003 --matchvlan 504 --inport $port3 --setvlan 505 --out $port0 $debug +rule_name=`openflow add $rule_name --priority 1003 --matchvlan 504 --inport $port3 --setvlan 505 --out $port0 $debug --print-id` [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1 expected="$OF_CONTROLLER_DPID 1003 $rule_name $port3 any 504 vlan=505,out=$port0" result=`openflow list | grep $rule_name` @@ -198,7 +198,7 @@ then printf "%-50s" "new rule Vlan -> Vlan_Vlan: " rule_name=fromVlan_to_Vlan1Vlan1 - openflow add $rule_name --priority 1005 --inport $port3 --matchvlan 505 --setvlan 510 --out $port0 --setvlan 511 --out $port1 --stripvlan --out=$port2 $debug + rule_name=`openflow add $rule_name --priority 1005 --inport $port3 --matchvlan 505 --setvlan 510 --out $port0 --setvlan 511 --out $port1 --stripvlan --out=$port2 $debug --print-id` [[ $? != 0 ]] && echo "FAIL cannot insert new rule" && $_exit 1 expected="$OF_CONTROLLER_DPID 1005 $rule_name $port3 any 505 vlan=510,out=$port0,vlan=511,out=$port1,vlan=None,out=$port2" result=`openflow list | grep $rule_name` diff --git a/vim_db.py b/vim_db.py index a458a8e..a024427 100644 --- 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 @@ -173,7 +174,7 @@ class vim_db(): def __get_used_net_vlan(self): #get used from database if needed try: - cmd = "SELECT vlan FROM nets WHERE vlan>='%s' and (type='ptp' or type='data') ORDER BY vlan LIMIT 25" % self.net_vlan_lastused + cmd = "SELECT vlan FROM nets WHERE vlan>='%s' ORDER BY vlan LIMIT 25" % self.net_vlan_lastused with self.con: self.cur = self.con.cursor() self.logger.debug(cmd) @@ -1035,15 +1036,16 @@ class vim_db(): with self.con: self.cur = self.con.cursor(mdb.cursors.DictCursor) #get INSTANCE - cmd = "SELECT uuid, name, description, progress, host_id, flavor_id, image_id, status, last_error, tenant_id, ram, vcpus, created_at \ - FROM instances WHERE uuid = '" + str(instance_id) +"'" + cmd = "SELECT uuid, name, description, progress, host_id, flavor_id, image_id, status, last_error, "\ + "tenant_id, ram, vcpus, created_at FROM instances WHERE uuid='{}'".format(instance_id) self.logger.debug(cmd) self.cur.execute(cmd) if self.cur.rowcount == 0 : return 0, "instance '" + str(instance_id) +"'not found." instance = self.cur.fetchone() #get networks - cmd = "SELECT uuid as iface_id, net_id, mac as mac_address, ip_address, name, Mbps as bandwidth, vpci, model \ - FROM ports WHERE type = 'instance:bridge' AND instance_id = '" + instance_id + "'" + cmd = "SELECT uuid as iface_id, net_id, mac as mac_address, ip_address, name, Mbps as bandwidth, "\ + "vpci, model FROM ports WHERE (type='instance:bridge' or type='instance:ovs') AND "\ + "instance_id= '{}'".format(instance_id) self.logger.debug(cmd) self.cur.execute(cmd) if self.cur.rowcount > 0 : @@ -1113,8 +1115,9 @@ class vim_db(): numa_dict['threads-source'] = thread_source #get dedicated ports and SRIOV - cmd = "SELECT port_id as iface_id, p.vlan as vlan, p.mac as mac_address, net_id, if(model='PF','yes',if(model='VF','no','yes:sriov')) as dedicated,\ - rp.Mbps as bandwidth, name, vpci, pci as source \ + cmd = "SELECT port_id as iface_id, p.vlan as vlan, p.mac as mac_address, net_id, if(model='PF',\ + 'yes',if(model='VF','no','yes:sriov')) as dedicated, rp.Mbps as bandwidth, name, vpci, \ + pci as source \ FROM resources_port as rp join ports as p on port_id=uuid WHERE p.instance_id = '%s' AND numa_id = '%s' and p.type='instance:data'" % (instance_id, numa_id) self.logger.debug(cmd) self.cur.execute(cmd) @@ -1395,15 +1398,28 @@ 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) self.cur.execute(cmd) #insert iface iface['instance_id'] = uuid - iface['type'] = 'instance:bridge' + # iface['type'] = 'instance:bridge' if 'name' not in iface: iface['name']="br"+str(nb_bridge_ifaces) iface['Mbps']=iface.pop('bandwidth', None) if 'mac_address' not in iface: @@ -1503,7 +1519,59 @@ class vim_db(): r,c = self.format_error(e, "new_instance", cmd) if r!=-HTTP_Request_Timeout or retry_==1: return r,c - def delete_instance(self, instance_id, tenant_id, net_list, ports_to_free, logcause="requested by http"): + 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="" try: @@ -1523,7 +1591,14 @@ class vim_db(): self.cur.execute(cmd) net_list__ = self.cur.fetchall() for net in net_list__: - net_list.append(net[0]) + net_dataplane_list.append(net[0]) + + # get ovs manangement nets + 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) + net_ovs_list += self.cur.fetchall() #get dataplane interfaces releases by this VM; both PF and VF with no other VF cmd="SELECT source_name, mac FROM (SELECT root_id, count(instance_id) as used FROM resources_port WHERE instance_id='%s' GROUP BY root_id ) AS A" % instance_id \ @@ -1634,8 +1709,8 @@ class vim_db(): if net['tenant_id']==tenant_id and net['shared']=='false': return -1, "needed admin privileges to attach to the net %s" % net_id #check types - if (net['type'] in ('p2p','data') and 'port_type' == 'instance:bridge') or \ - (net['type'] in ('bridge_data','bridge_man') and 'port_type' != 'instance:bridge') : + if (net['type'] in ('p2p','data') and port_type != 'instance:data') or \ + (net['type'] in ('bridge_data','bridge_man') and port_type not in ('instance:bridge', 'instance:ovs')): return -1, "can not attach a port of type %s into a net of type %s" % (port_type, net['type']) if net['type'] == 'ptp': #look how many diff --git a/vim_schema.py b/vim_schema.py index 0a5929d..6d508a6 100644 --- a/vim_schema.py +++ b/vim_schema.py @@ -117,12 +117,17 @@ config_schema = { "log_level": log_level_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"]} }, "required": ['db_host', 'db_user', 'db_passwd', 'db_name', - 'of_controller_ip', 'of_controller_port', 'of_controller_dpid', 'bridge_ifaces', 'of_controller'], + 'of_controller_ip', 'of_controller_port', 'of_controller_dpid', 'of_controller'], "additionalProperties": False } @@ -625,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}$"}]} },