From e9317ff999987c09369b9eab6bcd044ced8f1048 Mon Sep 17 00:00:00 2001 From: Mirabal Date: Wed, 18 Jan 2017 16:10:58 +0000 Subject: [PATCH] Openvim controller dhcp server over a ovs vxlan mesh - Base on dnsmasq per openvim net - Live in a namesapce and using veth pair - Support CIDR Change-Id: Ie152ac73804719ed769f24cc8dc8c1be1421e570 Signed-off-by: Mirabal --- host_thread.py | 386 ++++++++++++++++-- httpserver.py | 204 ++++++++- openvimd.cfg | 4 + openvimd.py | 4 +- scripts/configure-dhcp-server-UBUNTU16.0.4.sh | 148 +++++++ templates/network.yaml | 2 + test/networks/net-example4.yaml | 28 ++ vim_db.py | 68 ++- vim_schema.py | 8 +- 9 files changed, 809 insertions(+), 43 deletions(-) create mode 100755 scripts/configure-dhcp-server-UBUNTU16.0.4.sh create mode 100644 test/networks/net-example4.yaml diff --git a/host_thread.py b/host_thread.py index 2e02ede..c29f79a 100644 --- a/host_thread.py +++ b/host_thread.py @@ -39,20 +39,25 @@ from jsonschema import validate as js_v, exceptions as js_e import imp from vim_schema import localinfo_schema, hostinfo_schema import random -#from logging import Logger -#import auxiliary_functions as af +import os -#TODO: insert a logging system +# from logging import Logger +# import auxiliary_functions as af -#global lvirt_module -#lvirt_module=None #libvirt module is charged only if not in test mode +# TODO: insert a logging system + + +# global lvirt_module +# lvirt_module=None #libvirt module is charged only if not in test mode class host_thread(threading.Thread): lvirt_module = None - def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode, develop_bridge_iface): + + def __init__(self, name, host, user, db, db_lock, test, image_path, host_id, version, develop_mode, + develop_bridge_iface): '''Init a thread. - Arguments: + Arguments: 'id' number of thead 'name' name of thread 'host','user': host ip or name to manage and user @@ -731,7 +736,11 @@ class host_thread(threading.Thread): :return: """ - command = 'sudo ovs-vsctl del-port br-int ovim-' + vlan + if self.test: + return + + port_name = 'ovim-' + vlan + command = 'sudo ovs-vsctl del-port br-int ' + port_name print self.name, ': command:', command (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -740,21 +749,73 @@ class host_thread(threading.Thread): else: return False - def is_port_free(self, vlan, net_uuid): + def delete_dhcp_server(self, vlan, net_uuid, dhcp_path): + """ + Delete dhcp server process lining in namespace + :param vlan: segmentation id + :param net_uuid: network uuid + :param dhcp_path: conf fiel path that live in namespace side + :return: + """ + if self.test: + return + if not self.is_dhcp_port_free(vlan, net_uuid): + return True + + net_namespace = 'ovim-' + vlan + dhcp_path = os.path.join(dhcp_path, net_namespace) + pid_file = os.path.join(dhcp_path, 'dnsmasq.pid') + + command = 'sudo ip netns exec ' + net_namespace + ' cat ' + pid_file + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec ' + net_namespace + ' kill -9 ' + content + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # if len(content) == 0: + # return True + # else: + # return False + + def is_dhcp_port_free(self, host_id, net_uuid): + """ + Check if any port attached to the a net in a vxlan mesh across computes nodes + :param host_id: host id + :param net_uuid: network id + :return: True if is not free + """ + self.db_lock.acquire() + result, content = self.db.get_table( + FROM='ports', + WHERE={'p.type': 'instance:ovs', 'p.net_id': net_uuid} + ) + self.db_lock.release() + + if len(content) > 0: + return False + else: + return True + + def is_port_free(self, host_id, net_uuid): """ Check if there not ovs ports of a network in a compute host. - :param vlan: vlan port id + :param host_id: host id :param net_uuid: network id :return: True if is not free """ + self.db_lock.acquire() result, content = self.db.get_table( FROM='ports as p join instances as i on p.instance_id=i.uuid', - WHERE={"i.host_id": self.host_id, 'p.type': 'instance:ovs', 'p.net_id': net_uuid} + WHERE={"i.host_id": self.host_id, 'p.type': 'instance:ovs', 'p.net_id': net_uuid} ) self.db_lock.release() - if content > 0: + if len(content) > 0: return False else: return True @@ -763,9 +824,14 @@ class host_thread(threading.Thread): """ Add a bridge linux as a port to a OVS bridge and set a vlan for an specific linux bridge :param vlan: vlan port id - :return: + :return: True if success """ - command = 'sudo ovs-vsctl add-port br-int ovim-' + vlan + ' tag=' + vlan + + if self.test: + return + + port_name = 'ovim-' + vlan + command = 'sudo ovs-vsctl add-port br-int ' + port_name + ' tag=' + vlan print self.name, ': command:', command (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -774,6 +840,22 @@ class host_thread(threading.Thread): else: return False + def delete_dhcp_port(self, vlan, net_uuid): + """ + Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself. + :param vlan: segmentation id + :param net_uuid: network id + :return: True if success + """ + + if self.test: + return + + if not self.is_dhcp_port_free(vlan, net_uuid): + return True + self.delete_dhcp_interfaces(vlan) + return True + def delete_bridge_port_attached_to_ovs(self, vlan, net_uuid): """ Delete from an existing OVS bridge a linux bridge port attached and the linux bridge itself. @@ -783,11 +865,12 @@ class host_thread(threading.Thread): """ if self.test: return + if not self.is_port_free(vlan, net_uuid): return True self.delete_port_to_ovs_bridge(vlan, net_uuid) self.delete_linux_bridge(vlan) - return True + return True def delete_linux_bridge(self, vlan): """ @@ -795,7 +878,20 @@ class host_thread(threading.Thread): :param vlan: vlan port id :return: True if success """ - command = 'sudo ifconfig ovim-' + vlan + ' down && sudo brctl delbr ovim-' + vlan + + if self.test: + return + + port_name = 'ovim-' + vlan + command = 'sudo ip link set dev veth0-' + vlan + ' down' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + # + # if len(content) != 0: + # return False + + command = 'sudo ifconfig ' + port_name + ' down && sudo brctl delbr ' + port_name print self.name, ': command:', command (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -821,15 +917,247 @@ class host_thread(threading.Thread): :param vlan: netowrk vlan id :return: """ - command = 'sudo brctl addbr ovim-' + vlan + ' && sudo ifconfig ovim-' + vlan + ' up' + + if self.test: + return + + port_name = 'ovim-' + vlan + command = 'sudo brctl show | grep ' + port_name + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # if exist nothing to create + # if len(content) == 0: + # return False + + command = 'sudo brctl addbr ' + port_name + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # if len(content) == 0: + # return True + # else: + # return False + + command = 'sudo brctl stp ' + port_name + ' on' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # if len(content) == 0: + # return True + # else: + # return False + command = 'sudo ip link set dev ' + port_name + ' up' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if len(content) == 0: + return True + else: + return False + + def set_mac_dhcp_server(self, ip, mac, vlan, netmask, dhcp_path): + """ + Write into dhcp conf file a rule to assigned a fixed ip given to an specific MAC address + :param ip: IP address asigned to a VM + :param mac: VM vnic mac to be macthed with the IP received + :param vlan: Segmentation id + :param netmask: netmask value + :param path: dhcp conf file path that live in namespace side + :return: True if success + """ + + if self.test: + return + + net_namespace = 'ovim-' + vlan + dhcp_path = os.path.join(dhcp_path, net_namespace) + dhcp_hostsdir = os.path.join(dhcp_path, net_namespace) + + if not ip: + return False + + ip_data = mac.upper() + ',' + ip + + command = 'sudo ip netns exec ' + net_namespace + ' touch ' + dhcp_hostsdir + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec ' + net_namespace + ' sudo bash -ec "echo ' + ip_data + ' >> ' + dhcp_hostsdir + '"' + + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if len(content) == 0: + return True + else: + return False + + def delete_mac_dhcp_server(self, ip, mac, vlan, dhcp_path): + """ + Delete into dhcp conf file the ip assigned to a specific MAC address + + :param ip: IP address asigned to a VM + :param mac: VM vnic mac to be macthed with the IP received + :param vlan: Segmentation id + :param dhcp_path: dhcp conf file path that live in namespace side + :return: + """ + + if self.test: + return + + net_namespace = 'ovim-' + vlan + dhcp_path = os.path.join(dhcp_path, net_namespace) + dhcp_hostsdir = os.path.join(dhcp_path, net_namespace) + + if not ip: + return False + + ip_data = mac.upper() + ',' + ip + + command = 'sudo ip netns exec ' + net_namespace + ' sudo sed -i \'/' + ip_data + '/d\' ' + dhcp_hostsdir + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if len(content) == 0: + return True + else: + return False + + def launch_dhcp_server(self, vlan, ip_range, netmask, dhcp_path): + """ + Generate a linux bridge and attache the port to a OVS bridge + :param self: + :param vlan: Segmentation id + :param ip_range: IP dhcp range + :param netmask: network netmask + :param dhcp_path: dhcp conf file path that live in namespace side + :return: True if success + """ + + if self.test: + return + + interface = 'tap-' + vlan + net_namespace = 'ovim-' + vlan + dhcp_path = os.path.join(dhcp_path, net_namespace) + leases_path = os.path.join(dhcp_path, "dnsmasq.leases") + pid_file = os.path.join(dhcp_path, 'dnsmasq.pid') + + dhcp_range = ip_range[0] + ',' + ip_range[1] + ',' + netmask + + command = 'sudo ip netns exec ' + net_namespace + ' mkdir -p ' + dhcp_path print self.name, ': command:', command (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - if len(content) != 0: + pid_path = os.path.join(dhcp_path, 'dnsmasq.pid') + command = 'sudo ip netns exec ' + net_namespace + ' cat ' + pid_path + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + # check if pid is runing + pid_status_path = content + if content: + command = "ps aux | awk '{print $2 }' | grep " + pid_status_path + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + if not content: + command = 'sudo ip netns exec ' + net_namespace + ' /usr/sbin/dnsmasq --strict-order --except-interface=lo ' \ + '--interface=' + interface + ' --bind-interfaces --dhcp-hostsdir=' + dhcp_path + \ + ' --dhcp-range ' + dhcp_range + ' --pid-file=' + pid_file + ' --dhcp-leasefile=' + leases_path + ' --listen-address ' + ip_range[0] + + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.readline() + + if len(content) == 0: + return True + else: return False - command = 'sudo brctl stp ovim-' + vlan + ' on' + def delete_dhcp_interfaces(self, vlan): + """ + Create a linux bridge with STP active + :param vlan: netowrk vlan id + :return: + """ + + if self.test: + return + + net_namespace = 'ovim-' + vlan + command = 'sudo ovs-vsctl del-port br-int ovs-tap-' + vlan + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + vlan + ' down' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link set dev ovs-tap-' + vlan + ' down' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + def create_dhcp_interfaces(self, vlan, ip, netmask): + """ + Create a linux bridge with STP active + :param vlan: segmentation id + :param ip: Ip included in the dhcp range for the tap interface living in namesapce side + :param netmask: dhcp net CIDR + :return: True if success + """ + + if self.test: + return + + net_namespace = 'ovim-' + vlan + namespace_interface = 'tap-' + vlan + + command = 'sudo ip netns add ' + net_namespace + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link add tap-' + vlan + ' type veth peer name ovs-tap-' + vlan + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ovs-vsctl add-port br-int ovs-tap-' + vlan + ' tag=' + vlan + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link set tap-' + vlan + ' netns ' + net_namespace + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + vlan + ' up' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link set dev ovs-tap-' + vlan + ' up' + print self.name, ': command:', command + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec ' + net_namespace + ' ' + ' ifconfig ' + namespace_interface \ + + ' ' + ip + ' netmask ' + netmask print self.name, ': command:', command (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -1111,7 +1439,8 @@ class host_thread(threading.Thread): continue self.db_lock.acquire() - result, content = self.db.get_table(FROM='images', SELECT=('path','metadata'),WHERE={'uuid':dev['image_id']} ) + result, content = self.db.get_table(FROM='images', SELECT=('path', 'metadata'), + WHERE={'uuid': dev['image_id']}) self.db_lock.release() if result <= 0: error_text = "ERROR", result, content, "when getting image", dev['image_id'] @@ -1591,8 +1920,8 @@ class host_thread(threading.Thread): finally: if conn is not None: conn.close() - - + + def create_server(server, db, db_lock, only_of_ports): #print "server" #print "server" @@ -1859,9 +2188,10 @@ def create_server(server, db, db_lock, only_of_ports): control_iface['net_id']=control_iface.pop('uuid') #Get the brifge name db_lock.acquire() - result, content = db.get_table(FROM = 'nets', - SELECT = ('name','type', 'vlan', 'provider'), - WHERE = {'uuid':control_iface['net_id']}) + result, content = db.get_table(FROM='nets', + SELECT=('name', 'type', 'vlan', 'provider', 'enable_dhcp', + 'dhcp_first_ip', 'dhcp_last_ip', 'cidr'), + WHERE={'uuid': control_iface['net_id']}) db_lock.release() if result < 0: pass @@ -1879,6 +2209,12 @@ def create_server(server, db, db_lock, only_of_ports): control_iface["type"] = "instance:bridge" if network.get("vlan"): control_iface["vlan"] = network["vlan"] + + if network.get("enable_dhcp") == 'true': + control_iface["enable_dhcp"] = network.get("enable_dhcp") + control_iface["dhcp_first_ip"] = network["dhcp_first_ip"] + control_iface["dhcp_last_ip"] = network["dhcp_last_ip"] + control_iface["cidr"] = network["cidr"] else: if network['type']!='data' and network['type']!='ptp': return -1, "Error at field netwoks: network uuid %s for dataplane interface is not of type data or ptp" % control_iface['net_id'] diff --git a/httpserver.py b/httpserver.py index cf26544..4062ef1 100644 --- a/httpserver.py +++ b/httpserver.py @@ -38,6 +38,7 @@ import datetime import hashlib import os import imp +from netaddr import IPNetwork, IPAddress, all_matching_cidrs #import only if needed because not needed in test mode. To allow an easier installation import RADclass from jsonschema import validate as js_v, exceptions as js_e import host_thread as ht @@ -634,6 +635,7 @@ def http_post_hosts(): if config_dic['network_type'] == 'ovs': # create bridge + create_dhcp_ovs_bridge() config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge") # check if more host exist create_vxlan_mesh(content['uuid']) @@ -649,19 +651,142 @@ def http_post_hosts(): return +def get_dhcp_controller(): + """ + Create an host_thread object for manage openvim controller and not create a thread for itself + :return: dhcp_host openvim controller object + """ + + if 'openvim_controller' in config_dic['host_threads']: + return config_dic['host_threads']['openvim_controller'] + + bridge_ifaces = [] + controller_ip = config_dic['ovs_controller_ip'] + ovs_controller_user = config_dic['ovs_controller_user'] + + host_test_mode = True if config_dic['mode'] == 'test' or config_dic['mode'] == "OF only" else False + host_develop_mode = True if config_dic['mode'] == 'development' else False + + dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip, db=config_dic['db'], + db_lock=config_dic['db_lock'], test=host_test_mode, + image_path=config_dic['image_path'], version=config_dic['version'], + host_id='openvim_controller', develop_mode=host_develop_mode, + develop_bridge_iface=bridge_ifaces) + + config_dic['host_threads']['openvim_controller'] = dhcp_host + dhcp_host.ssh_connect() + return dhcp_host + + +def delete_dhcp_ovs_bridge(vlan, net_uuid): + """ + Delete bridges and port created during dhcp launching at openvim controller + :param vlan: net vlan id + :param net_uuid: network identifier + :return: + """ + dhcp_path = config_dic['ovs_controller_file_path'] + + controller_host = get_dhcp_controller() + controller_host.delete_dhcp_port(vlan, net_uuid) + controller_host.delete_dhcp_server(vlan, net_uuid, dhcp_path) + + +def create_dhcp_ovs_bridge(): + """ + Initialize bridge to allocate the dhcp server at openvim controller + :return: + """ + controller_host = get_dhcp_controller() + controller_host.create_ovs_bridge() + + +def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac): + """" + Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes + :param vm_ip: IP address asigned to a VM + :param vlan: Segmentation id + :param first_ip: First dhcp range ip + :param last_ip: Last dhcp range ip + :param cidr: net cidr + :param mac: VM vnic mac to be macthed with the IP received + """ + if not vm_ip: + return + ip_tools = IPNetwork(cidr) + cidr_len = ip_tools.prefixlen + dhcp_netmask = str(ip_tools.netmask) + dhcp_path = config_dic['ovs_controller_file_path'] + + new_cidr = [first_ip + '/' + str(cidr_len)] + if not len(all_matching_cidrs(vm_ip, new_cidr)): + vm_ip = None + + controller_host = get_dhcp_controller() + controller_host.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path) + + +def delete_mac_dhcp(vm_ip, vlan, mac): + """ + Delete into dhcp conf file the ip assigned to a specific MAC address + :param vm_ip: IP address asigned to a VM + :param vlan: Segmentation id + :param mac: VM vnic mac to be macthed with the IP received + :return: + """ + + dhcp_path = config_dic['ovs_controller_file_path'] + + controller_host = get_dhcp_controller() + controller_host.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path) + + +def launch_dhcp_server(vlan, first_ip, last_ip, cidr): + """ + Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes + :param vlan: vlan identifier + :param first_ip: First dhcp range ip + :param last_ip: Last dhcp range ip + :param cidr: net cidr + :return: + """ + ip_tools = IPNetwork(cidr) + dhcp_netmask = str(ip_tools.netmask) + ip_range = [first_ip, last_ip] + dhcp_path = config_dic['ovs_controller_file_path'] + + controller_host = get_dhcp_controller() + controller_host.create_linux_bridge(vlan) + controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask) + controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path) + + def create_vxlan_mesh(host_id): + """ + Create vxlan mesh across all openvimc controller and computes. + :param host_id: host identifier + :param host_id: host identifier + :return: + """ + dhcp_compute_name = get_vxlan_interface("dhcp") existing_hosts = get_hosts() - if len(existing_hosts['hosts']) > 1: + if len(existing_hosts['hosts']) > 0: + # vlxan mesh creation between openvim controller and computes computes_available = existing_hosts['hosts'] + controller_host = get_dhcp_controller() + for compute in computes_available: + vxlan_interface_name = get_vxlan_interface(compute['id'][:8]) + config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, controller_host.host) + controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute['ip_name']) - # TUNEL openvim controller - + # vlxan mesh creation between openvim computes for count, compute_owner in enumerate(computes_available): for compute in computes_available: if compute_owner['id'] == compute['id']: pass else: vxlan_interface_name = get_vxlan_interface(compute_owner['id'][:8]) + controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute_owner['ip_name']) config_dic['host_threads'][compute['id']].insert_task("new-vxlan", vxlan_interface_name, compute_owner['ip_name']) @@ -674,13 +799,20 @@ def delete_vxlan_mesh(host_id): """ existing_hosts = get_hosts() computes_available = existing_hosts['hosts'] + # vxlan_interface_name = get_vxlan_interface(host_id[:8]) - + controller_host = get_dhcp_controller() + controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name) + # remove bridge from openvim controller if no more computes exist + if len(existing_hosts): + controller_host.delete_ovs_bridge() + # Remove vxlan mesh for compute in computes_available: if host_id == compute['id']: pass else: - config_dic['host_threads'][compute['id']].insert_task("del-vxlan",vxlan_interface_name) + controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name) + config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name) def get_vxlan_interface(local_uuid): @@ -1446,6 +1578,11 @@ def http_post_server_id(tenant_id): print if server_start == 'no': content['status'] = 'INACTIVE' + dhcp_nets_id = [] + for net in http_content['server']['networks']: + if net['type'] == 'instance:ovs': + dhcp_nets_id.append(get_network_id(net['net_id'])) + ports_to_free=[] new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free) if new_instance_result < 0: @@ -1466,9 +1603,8 @@ def http_post_server_id(tenant_id): print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c - - #look for dhcp ip address - r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": new_instance}) + #look for dhcp ip address + r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance}) if r2 >0: for iface in c2: if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]: @@ -1481,14 +1617,23 @@ def http_post_server_id(tenant_id): server_net = get_network_id(iface['net_id']) if server_net["network"].get('provider:physical', "")[:3] == 'OVS': vlan = str(server_net['network']['provider:vlan']) - config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan) + dhcp_enable = bool(server_net['network']['enable_dhcp']) + if dhcp_enable: + dhcp_firt_ip = str(server_net['network']['dhcp_first_ip']) + dhcp_last_ip = str(server_net['network']['dhcp_last_ip']) + dhcp_cidr = str(server_net['network']['cidr']) + vm_dhcp_ip = c2[0]["ip_address"] + config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan) + + set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac']) + launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr) + #Start server - server['uuid'] = new_instance server_start = server.get('start', 'yes') if server_start != 'no': - server['paused'] = True if server_start == 'paused' else False + server['paused'] = True if server_start == 'paused' else False server['action'] = {"start":None} server['status'] = "CREATING" #Program task @@ -1650,8 +1795,12 @@ def http_server_action(server_id, tenant_id, action): print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' + c # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan) for net in net_ovs_list: + mac = str(net[3]) + vm_ip = str(net[2]) vlan = str(net[1]) net_id = net[0] + delete_dhcp_ovs_bridge(vlan, net_id) + delete_mac_dhcp(vm_ip, vlan, mac) config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id) return format_out(data) @@ -1705,7 +1854,7 @@ def http_get_networks(): print "http_get_networks error %d %s" % (result, content) bottle.abort(-result, content) else: - convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp') ) + convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp')) delete_nulls(content) change_keys_http2db(content, http2db_network, reverse=True) data={'networks' : content} @@ -1730,7 +1879,7 @@ def get_network_id(network_id): print "http_get_networks_id network '%s' not found" % network_id bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id) else: - convert_boolean(content, ('shared', 'admin_state_up', 'enale_dhcp') ) + convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp')) change_keys_http2db(content, http2db_network, reverse=True) #get ports result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',), @@ -1762,6 +1911,11 @@ def http_post_networks(): #check valid params net_provider = network.get('provider') net_type = network.get('type') + net_type = network.get('type') + net_enable_dhcp = network.get('enable_dhcp') + if net_enable_dhcp: + net_cidr = network.get('cidr') + net_vlan = network.get("vlan") net_bind_net = network.get("bind_net") net_bind_type= network.get("bind_type") @@ -1867,12 +2021,15 @@ def http_post_networks(): network['provider'] = net_provider network['type'] = net_type network['vlan'] = net_vlan + + if 'enable_dhcp' in network and network['enable_dhcp']: + check_dhcp_data_integrity(network) + result, content = my.db.new_row('nets', network, True, True) if result >= 0: if bridge_net!=None: bridge_net[3] = content - if config_dic.get("dhcp_server") and config_dic['network_type'] == 'bridge': if network["name"] in config_dic["dhcp_server"].get("nets", () ): config_dic["dhcp_nets"].append(content) @@ -1887,6 +2044,24 @@ def http_post_networks(): return +def check_dhcp_data_integrity(network): + """ + Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value + :param network: list with user nets paramters + :return: + """ + control_iface = [] + + if "cidr" in network: + cidr = network["cidr"] + + ips = IPNetwork(cidr) + if "dhcp_first_ip" not in network: + network["dhcp_first_ip"] = str(ips[2]) + if "dhcp_last_ip" not in network: + network["dhcp_last_ip"] = str(ips[-2]) + + @bottle.route(url_base + '/networks/', method='PUT') def http_put_network_id(network_id): '''update a network_id into the database.''' @@ -2284,3 +2459,4 @@ def http_delete_port_id(port_id): bottle.abort(-result, content) return + diff --git a/openvimd.cfg b/openvimd.cfg index 545b739..47238d3 100644 --- a/openvimd.cfg +++ b/openvimd.cfg @@ -76,6 +76,10 @@ network_vlan_range_end: 4000 # - ovs : (by default) Use a vlxan mesh between computes to handle the network overlay. # - bridge: Use pre-populated linux bridges with L2 conectivity at compte nodes. network_type : ovs +ovs_controller_ip : localhost # dhcp controller IP address, must be change in order to +ovs_controller_user : "osm_dhcp" # User for the dchp controller for OVS networks +ovs_controller_file_path : "/var/lib/openvim" # Path for dhcp daemon configuration, by default '/var/lib/openvim' + #host bridge interfaces for networks # Apply only for 'network_type: bridge' diff --git a/openvimd.py b/openvimd.py index eb6c2f0..c1a57d9 100755 --- a/openvimd.py +++ b/openvimd.py @@ -71,6 +71,8 @@ def load_configuration(configuration_file): 'log_level_of': 'ERROR', 'bridge_ifaces': {}, 'network_type': 'ovs', + 'ovs_controller_user': 'osm_dhcp', + 'ovs_controller_file_path': '/var/lib/', } try: #First load configuration from configuration file @@ -220,7 +222,7 @@ if __name__=="__main__": ( 'development_bridge' not in config_dic or config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None) ): logger.error("'%s' is not a valid 'development_bridge', not one of the 'bridge_ifaces'", config_file) exit(-1) - + if config_dic['mode'] != 'normal': print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' print "!! Warning, openvimd in TEST mode '%s'" % config_dic['mode'] diff --git a/scripts/configure-dhcp-server-UBUNTU16.0.4.sh b/scripts/configure-dhcp-server-UBUNTU16.0.4.sh new file mode 100755 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/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/vim_db.py b/vim_db.py index f2e448d..cdc3dcc 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 @@ -1389,8 +1390,21 @@ class vim_db(): #insert resources nb_bridge_ifaces = nb_cores = nb_ifaces = nb_numas = 0 #insert bridged_ifaces + for iface in bridgedifaces: #generate and insert a iface uuid + if 'enable_dhcp' in iface and iface['enable_dhcp']: + dhcp_first_ip = iface["dhcp_first_ip"] + del iface["dhcp_first_ip"] + dhcp_last_ip = iface["dhcp_last_ip"] + del iface["dhcp_last_ip"] + dhcp_cidr = iface["cidr"] + del iface["cidr"] + del iface["enable_dhcp"] + used_dhcp_ips = self._get_dhcp_ip_used_list(iface["net_id"]) + iface["ip_address"] = self.get_free_ip_from_range(dhcp_first_ip, dhcp_last_ip, + dhcp_cidr, used_dhcp_ips) + iface['uuid'] = str(myUuid.uuid1()) # create_uuid cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'ports')" % (iface['uuid'], uuid) self.logger.debug(cmd) @@ -1497,6 +1511,58 @@ class vim_db(): r,c = self.format_error(e, "new_instance", cmd) if r!=-HTTP_Request_Timeout or retry_==1: return r,c + def get_free_ip_from_range(self, first_ip, last_ip, cidr, ip_used_list): + """ + Calculate a free IP from a range given + :param first_ip: First dhcp ip range + :param last_ip: Last dhcp ip range + :param cidr: net cidr + :param ip_used_list: contain all used ips to avoid ip collisions + :return: + """ + + ip_tools = IPNetwork(cidr) + cidr_len = ip_tools.prefixlen + ips = IPNetwork(first_ip + '/' + str(cidr_len)) + ip_used_list.append(str(ips[0])) # first ip + ip_used_list.append(str(ips[1])) # gw ip + ip_used_list.append(str(ips[-1])) # broadcast ip + for vm_ip in ips: + if str(vm_ip) not in ip_used_list: + return vm_ip + + return None + + def _get_dhcp_ip_used_list(self, net_id): + """ + REtreive from DB all ips already used by the dhcp server for a given net + :param net_id: + :return: + """ + WHERE={'type': 'instance:ovs', 'net_id': net_id} + for retry_ in range(0, 2): + cmd = "" + self.cur = self.con.cursor(mdb.cursors.DictCursor) + select_ = "SELECT uuid, ip_address FROM ports " + + if WHERE is None or len(WHERE) == 0: + where_ = "" + else: + where_ = "WHERE " + " AND ".join( + map(lambda x: str(x) + (" is Null" if WHERE[x] is None else "='" + str(WHERE[x]) + "'"), + WHERE.keys())) + limit_ = "LIMIT 100" + cmd = " ".join((select_, where_, limit_)) + self.logger.debug(cmd) + self.cur.execute(cmd) + ports = self.cur.fetchall() + ip_address_list = [] + for port in ports: + ip_address_list.append(port['ip_address']) + + return ip_address_list + + def delete_instance(self, instance_id, tenant_id, net_dataplane_list, ports_to_free, net_ovs_list, logcause="requested by http"): for retry_ in range(0,2): cmd="" @@ -1520,7 +1586,7 @@ class vim_db(): net_dataplane_list.append(net[0]) # get ovs manangement nets - cmd = "SELECT DISTINCT net_id, vlan FROM ports WHERE instance_id='{}' AND net_id is not Null AND "\ + cmd = "SELECT DISTINCT net_id, vlan, ip_address, mac FROM ports WHERE instance_id='{}' AND net_id is not Null AND "\ "type='instance:ovs'".format(instance_id) self.logger.debug(cmd) self.cur.execute(cmd) diff --git a/vim_schema.py b/vim_schema.py index 5bf555d..6d508a6 100644 --- a/vim_schema.py +++ b/vim_schema.py @@ -118,6 +118,10 @@ config_schema = { "log_level_db": log_level_schema, "log_level_of": log_level_schema, "network_type": {"type": "string", "enum": ["ovs", "bridge"]}, + "ovs_controller_file_path": path_schema, + "ovs_controller_user": nameshort_schema, + + "ovs_controller_ip": nameshort_schema }, "patternProperties": { "of_*" : {"type": ["string", "integer", "boolean"]} @@ -626,8 +630,8 @@ network_update_schema = { "provider:physical":net_bind_schema, "cidr":cidr_schema, "enable_dhcp": {"type":"boolean"}, - "dhcp_first_ip": ip_schema, - "dhcp_last_ip": ip_schema, + # "dhcp_first_ip": ip_schema, + # "dhcp_last_ip": ip_schema, "bind_net":name_schema, #can be name, or uuid "bind_type":{"oneOf":[{"type":"null"},{"type":"string", "pattern":"^vlan:[0-9]{1,4}$"}]} }, -- 2.25.1