From 6878e3f50618cf9a6dfe8d365951cb8bc9a98a6a Mon Sep 17 00:00:00 2001 From: mirabal Date: Mon, 5 Jun 2017 09:19:26 -0500 Subject: [PATCH] Provider network and dnsmaq conf improvment When dhcp server is launch, now dns servers and default routes can be progated to a vm. A qrouter is created inside a namespace allowing connections with a provider network and a openvim tenant network. Change-Id: If2becc010b2886493396c9f6b363980a846a04da Signed-off-by: mirabal --- database_utils/migrate_vim_db.sh | 21 +- osm_openvim/host_thread.py | 467 ++++++++++++++++++++++++++++--- osm_openvim/httpserver.py | 52 +++- osm_openvim/ovim.py | 77 ++++- osm_openvim/vim_db.py | 5 + osm_openvim/vim_schema.py | 18 +- test/networks/net-example5.yaml | 20 ++ 7 files changed, 593 insertions(+), 67 deletions(-) create mode 100644 test/networks/net-example5.yaml diff --git a/database_utils/migrate_vim_db.sh b/database_utils/migrate_vim_db.sh index aded5a0..1a1bf95 100755 --- a/database_utils/migrate_vim_db.sh +++ b/database_utils/migrate_vim_db.sh @@ -33,7 +33,7 @@ DBPORT="3306" DBNAME="vim_db" QUIET_MODE="" #TODO update it with the last database version -LAST_DB_VERSION=20 +LAST_DB_VERSION=21 # Detect paths MYSQL=$(which mysql) @@ -187,7 +187,8 @@ fi #[ $OPENVIM_VER_NUM -ge 5010 ] && DATABASE_TARGET_VER_NUM=17 #0.5.10 => 17 #[ $OPENVIM_VER_NUM -ge 5013 ] && DATABASE_TARGET_VER_NUM=18 #0.5.13 => 18 #[ $OPENVIM_VER_NUM -ge 5015 ] && DATABASE_TARGET_VER_NUM=19 #0.5.15 => 19 -#[ $OPENVIM_VER_NUM -ge 5017 ] && DATABASE_TARGET_VER_NUM20 #0.5.17 => 20 +#[ $OPENVIM_VER_NUM -ge 5017 ] && DATABASE_TARGET_VER_NUM=20 #0.5.17 => 20 +#[ $OPENVIM_VER_NUM -ge 5018 ] && DATABASE_TARGET_VER_NUM=21 #0.5.18 => 21 # TODO ... put next versions here function upgrade_to_1(){ @@ -703,6 +704,22 @@ function downgrade_from_20(){ echo "DELETE FROM schema_version WHERE version_int = '20';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 } +function upgrade_to_21(){ + echo " Add 'routes', 'links' and 'dns' to 'nets'" + echo "ALTER TABLE nets ADD COLUMN dns VARCHAR(255) NULL AFTER gateway_ip, + ADD COLUMN links TEXT(2000) NULL AFTER dns, + ADD COLUMN routes TEXT(2000) NULL AFTER links;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) VALUES (21, '0.21', '0.5.18', 'Add routes, links and dns to inets', '2017-06-21');"\ + | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} + +function downgrade_from_21(){ + echo " Delete 'routes', 'links' and 'dns' to 'nets'" + echo "ALTER TABLE nets DROP COLUMN dns, DROP COLUMN links, DROP COLUMN routes;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 + echo "DELETE FROM schema_version WHERE version_int = '21';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1 +} + + #TODO ... put funtions here # echo "db version = "${DATABASE_VER_NUM} diff --git a/osm_openvim/host_thread.py b/osm_openvim/host_thread.py index b8f051c..6fe8331 100644 --- a/osm_openvim/host_thread.py +++ b/osm_openvim/host_thread.py @@ -813,16 +813,16 @@ class host_thread(threading.Thread): if not self.is_dhcp_port_free(vlan, net_uuid): return True try: - net_namespace = 'ovim-' + str(vlan) - dhcp_path = os.path.join(dhcp_path, net_namespace) + dhcp_namespace = str(vlan) + '-dnsmasq' + dhcp_path = os.path.join(dhcp_path, dhcp_namespace) pid_file = os.path.join(dhcp_path, 'dnsmasq.pid') - command = 'sudo ip netns exec ' + net_namespace + ' cat ' + pid_file + command = 'sudo ip netns exec ' + dhcp_namespace + ' cat ' + pid_file self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip netns exec ' + net_namespace + ' kill -9 ' + content + command = 'sudo ip netns exec ' + dhcp_namespace + ' kill -9 ' + content self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -944,7 +944,7 @@ class host_thread(threading.Thread): return True try: port_name = 'ovim-' + str(vlan) - command = 'sudo ip link set dev veth0-' + str(vlan) + ' down' + command = 'sudo ip link set dev ovim-' + str(vlan) + ' down' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) # content = stdout.read() @@ -965,6 +965,59 @@ class host_thread(threading.Thread): self.ssh_connect() return False + def remove_link_bridge_to_ovs(self, vlan, link): + """ + Delete a linux provider net connection to tenatn net + :param vlan: vlan port id + :param link: link name + :return: True if success + """ + + if self.test: + return True + try: + br_tap_name = str(vlan) + '-vethBO' + br_ovs_name = str(vlan) + '-vethOB' + + # Delete ovs veth pair + command = 'sudo ip link set dev {} down'.format(br_ovs_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ovs-vsctl del-port br-int {}'.format(br_ovs_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # Delete br veth pair + command = 'sudo ip link set dev {} down'.format(br_tap_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # Delete br veth interface form bridge + command = 'sudo brctl delif {} {}'.format(link, br_tap_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # Delete br veth pair + command = 'sudo ip link set dev {} down'.format(link) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if len(content) == 0: + return True + else: + return False + except paramiko.ssh_exception.SSHException as e: + self.logger.error("delete_linux_bridge ssh Exception: " + str(e)) + if "SSH session not active" in str(e): + self.ssh_connect() + return False + def create_ovs_bridge_port(self, vlan): """ Generate a linux bridge and attache the port to a OVS bridge @@ -1030,7 +1083,7 @@ class host_thread(threading.Thread): self.ssh_connect() return False - def set_mac_dhcp_server(self, ip, mac, vlan, netmask, dhcp_path): + def set_mac_dhcp_server(self, ip, mac, vlan, netmask, first_ip, 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 @@ -1044,21 +1097,43 @@ class host_thread(threading.Thread): if self.test: return True - net_namespace = 'ovim-' + str(vlan) - dhcp_path = os.path.join(dhcp_path, net_namespace) - dhcp_hostsdir = os.path.join(dhcp_path, net_namespace) + dhcp_namespace = str(vlan) + '-dnsmasq' + dhcp_path = os.path.join(dhcp_path, dhcp_namespace) + dhcp_hostsdir = os.path.join(dhcp_path, dhcp_namespace) if not ip: return False try: + + ns_interface = str(vlan) + '-vethDO' + command = 'sudo ip netns exec ' + dhcp_namespace + ' cat /sys/class/net/{}/address'.format(ns_interface) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + iface_listen_mac = stdout.read() + + if iface_listen_mac > 0: + command = 'sudo ip netns exec ' + dhcp_namespace + ' cat {} | grep {}'.format(dhcp_hostsdir, dhcp_hostsdir) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + if content > 0: + ip_data = iface_listen_mac.upper().replace('\n', '') + ',' + first_ip + dhcp_hostsdir = os.path.join(dhcp_path, dhcp_namespace) + + command = 'sudo ip netns exec ' + dhcp_namespace + ' sudo bash -ec "echo ' + ip_data + ' >> ' + dhcp_hostsdir + '"' + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + ip_data = mac.upper() + ',' + ip - command = 'sudo ip netns exec ' + net_namespace + ' touch ' + dhcp_hostsdir + command = 'sudo ip netns exec ' + dhcp_namespace + ' touch ' + dhcp_hostsdir self.logger.debug("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 + '"' + command = 'sudo ip netns exec ' + dhcp_namespace + ' sudo bash -ec "echo ' + ip_data + ' >> ' + dhcp_hostsdir + '"' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) @@ -1088,16 +1163,16 @@ class host_thread(threading.Thread): if self.test: return False try: - net_namespace = 'ovim-' + str(vlan) - dhcp_path = os.path.join(dhcp_path, net_namespace) - dhcp_hostsdir = os.path.join(dhcp_path, net_namespace) + dhcp_namespace = str(vlan) + '-dnsmasq' + dhcp_path = os.path.join(dhcp_path, dhcp_namespace) + dhcp_hostsdir = os.path.join(dhcp_path, dhcp_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 + command = 'sudo ip netns exec ' + dhcp_namespace + ' sudo sed -i \'/' + ip_data + '/d\' ' + dhcp_hostsdir self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() @@ -1113,7 +1188,7 @@ class host_thread(threading.Thread): self.ssh_connect() return False - def launch_dhcp_server(self, vlan, ip_range, netmask, dhcp_path, gateway): + def launch_dhcp_server(self, vlan, ip_range, netmask, dhcp_path, gateway, dns_list=None, routes=None): """ Generate a linux bridge and attache the port to a OVS bridge :param self: @@ -1122,30 +1197,34 @@ class host_thread(threading.Thread): :param netmask: network netmask :param dhcp_path: dhcp conf file path that live in namespace side :param gateway: Gateway address for dhcp net + :param dns_list: dns list for dhcp server + :param routes: routes list for dhcp server :return: True if success """ if self.test: return True try: - interface = 'tap-' + str(vlan) - net_namespace = 'ovim-' + str(vlan) - dhcp_path = os.path.join(dhcp_path, net_namespace) + ns_interface = str(vlan) + '-vethDO' + dhcp_namespace = str(vlan) + '-dnsmasq' + dhcp_path = os.path.join(dhcp_path, dhcp_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 + command = 'sudo ip netns exec ' + dhcp_namespace + ' mkdir -p ' + dhcp_path self.logger.debug("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 + command = 'sudo ip netns exec ' + dhcp_namespace + ' cat ' + pid_path self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() + # check if pid is runing pid_status_path = content if content: @@ -1153,11 +1232,34 @@ class host_thread(threading.Thread): self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() + + gateway_option = ' --dhcp-option=3,' + gateway + + dhcp_route_option = '' + if routes: + dhcp_route_option = ' --dhcp-option=121' + for key, value in routes.iteritems(): + if 'default' == key: + gateway_option = ' --dhcp-option=3,' + value + else: + dhcp_route_option += ',' + key + ',' + value + dns_data = '' + if dns_list: + dns_data = ' --dhcp-option=6' + for dns in dns_list: + dns_data += ',' + dns + 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 ' + gateway + command = 'sudo ip netns exec ' + dhcp_namespace + ' /usr/sbin/dnsmasq --strict-order --except-interface=lo ' \ + '--interface=' + ns_interface + \ + ' --bind-interfaces --dhcp-hostsdir=' + dhcp_path + \ + ' --dhcp-range ' + dhcp_range + \ + ' --pid-file=' + pid_file + \ + ' --dhcp-leasefile=' + leases_path + \ + ' --listen-address ' + ip_range[0] + \ + gateway_option + \ + dhcp_route_option + \ + dns_data self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) @@ -1183,21 +1285,35 @@ class host_thread(threading.Thread): if self.test: return True try: - net_namespace = 'ovim-' + str(vlan) - command = 'sudo ovs-vsctl del-port br-int ovs-tap-' + str(vlan) + br_veth_name = str(vlan) + '-vethDO' + ovs_veth_name = str(vlan) + '-vethOD' + dhcp_namespace = str(vlan) + '-dnsmasq' + + command = 'sudo ovs-vsctl del-port br-int ' + ovs_veth_name self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + str(vlan) + ' down' + command = 'sudo ip netns exec ' + dhcp_namespace + ' ip link set dev ' + br_veth_name + ' down' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip link set dev ovs-tap-' + str(vlan) + ' down' + command = 'sudo ip link set dev ' + dhcp_namespace + ' down' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() + + command = 'sudo brctl delbr ' + dhcp_namespace + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns del ' + dhcp_namespace + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + except paramiko.ssh_exception.SSHException as e: self.logger.error("delete_dhcp_interfaces ssh Exception: " + str(e)) if "SSH session not active" in str(e): @@ -1216,50 +1332,50 @@ class host_thread(threading.Thread): if self.test: return True try: - net_namespace = 'ovim-' + str(vlan) - namespace_interface = 'tap-' + str(vlan) + ovs_veth_name = str(vlan) + '-vethOD' + ns_veth = str(vlan) + '-vethDO' + dhcp_namespace = str(vlan) + '-dnsmasq' - command = 'sudo ip netns add ' + net_namespace + command = 'sudo ip netns add ' + dhcp_namespace self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip link add tap-' + str(vlan) + ' type veth peer name ovs-tap-' + str(vlan) + command = 'sudo ip link add ' + ns_veth + ' type veth peer name ' + ovs_veth_name self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ovs-vsctl add-port br-int ovs-tap-' + str(vlan) + ' tag=' + str(vlan) + command = 'sudo ip link set ' + ns_veth + ' netns ' + dhcp_namespace self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip link set tap-' + str(vlan) + ' netns ' + net_namespace + command = 'sudo ip netns exec ' + dhcp_namespace + ' ip link set dev ' + ns_veth + ' up' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + str(vlan) + ' up' + command = 'sudo ovs-vsctl add-port br-int ' + ovs_veth_name + ' tag=' + str(vlan) self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip link set dev ovs-tap-' + str(vlan) + ' up' + command = 'sudo ip link set dev ' + ovs_veth_name + ' up' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev lo up' + command = 'sudo ip netns exec ' + dhcp_namespace + ' ip link set dev lo up' self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - command = 'sudo ip netns exec ' + net_namespace + ' ' + ' ifconfig ' + namespace_interface \ + command = 'sudo ip netns exec ' + dhcp_namespace + ' ' + ' ifconfig ' + ns_veth \ + ' ' + ip_listen_address + ' netmask ' + netmask self.logger.debug("command: " + command) (_, stdout, _) = self.ssh_conn.exec_command(command) content = stdout.read() - if len(content) == 0: return True else: @@ -1270,6 +1386,266 @@ class host_thread(threading.Thread): self.ssh_connect() return False + def delete_qrouter_connection(self, vlan, link): + """ + Delete qrouter Namesapce with all veth interfaces need it + :param vlan: + :param link: + :return: + """ + + ns_qouter = str(vlan) + '-qrouter' + qrouter_ovs_veth = str(vlan) + '-vethOQ' + qrouter_ns_veth = str(vlan) + '-vethQO' + + qrouter_br_veth = str(vlan) + '-vethBQ' + qrouter_ns_router_veth = str(vlan) + '-vethQB' + + # delete ovs veth to ovs br-int + command = 'sudo ovs-vsctl del-port br-int {}'.format(qrouter_ovs_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # down ns veth + command = 'sudo ip netns exec {} ip link set dev {} down'.format(ns_qouter, qrouter_ns_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # down ovs veth interface + command = 'sudo ip link set dev {} down'.format(qrouter_br_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # down br veth interface + command = 'sudo ip link set dev {} down'.format(qrouter_ovs_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # down br veth interface + command = 'sudo ip link set dev {} down'.format(qrouter_ns_router_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # down br veth interface + command = 'sudo brctl delif {} {}'.format(link, qrouter_br_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + + # delete NS + command = 'sudo ip netns del ' + ns_qouter + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + def create_qrouter_ovs_connection(self, vlan, gateway, dhcp_cidr): + """ + Create qrouter Namesapce with all veth interfaces need it between NS and OVS + :param vlan: + :param gateway: + :return: + """ + + ns_qouter = str(vlan) + '-qrouter' + qrouter_ovs_veth = str(vlan) + '-vethOQ' + qrouter_ns_veth = str(vlan) + '-vethQO' + + # Create NS + command = 'sudo ip netns add ' + ns_qouter + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # Create pait veth + command = 'sudo ip link add {} type veth peer name {}'.format(qrouter_ns_veth, qrouter_ovs_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ovs veth interface + command = 'sudo ip link set dev {} up'.format(qrouter_ovs_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # add ovs veth to ovs br-int + command = 'sudo ovs-vsctl add-port br-int {} tag={}'.format(qrouter_ovs_veth, vlan) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # add veth to ns + command = 'sudo ip link set {} netns {}'.format(qrouter_ns_veth, ns_qouter) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ns loopback + command = 'sudo ip netns exec {} ip link set dev lo up'.format(ns_qouter) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ns veth + command = 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter, qrouter_ns_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + from netaddr import IPNetwork + ip_tools = IPNetwork(dhcp_cidr) + cidr_len = ip_tools.prefixlen + + # set gw to ns veth + command = 'sudo ip netns exec {} ip address add {}/{} dev {}'.format(ns_qouter, gateway, cidr_len, qrouter_ns_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + def add_ns_routes(self, vlan, routes): + + for key, value in routes.iteritems(): + ns_qouter = str(vlan) + '-qrouter' + qrouter_ns_router_veth = str(vlan) + '-vethQB' + # up ns veth + if key == 'default': + command = 'sudo ip netns exec {} ip route add {} via {} '.format(ns_qouter, key, value) + else: + command = 'sudo ip netns exec {} ip route add {} via {} dev {}'.format(ns_qouter, key, value, + qrouter_ns_router_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + def create_qrouter_br_connection(self, vlan, cidr, link): + """ + Create veth interfaces between user bridge (link) and OVS + :param vlan: + :param link: + :return: + """ + + ns_qouter = str(vlan) + '-qrouter' + qrouter_ns_router_veth = str(vlan) + '-vethQB' + qrouter_br_veth = str(vlan) + '-vethBQ' + + # Create pait veth + command = 'sudo ip link add {} type veth peer name {}'.format(qrouter_br_veth, qrouter_ns_router_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ovs veth interface + command = 'sudo ip link set dev {} up'.format(qrouter_br_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # add veth to ns + command = 'sudo ip link set {} netns {}'.format(qrouter_ns_router_veth, ns_qouter) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ns veth + command = 'sudo ip netns exec {} ip link set dev {} up'.format(ns_qouter, qrouter_ns_router_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip netns exec {} ip address add {} dev {}'.format(ns_qouter, link['nat'], qrouter_ns_router_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo brctl show | grep {}'.format(link['iface']) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if content > '': + # up ns veth + command = 'sudo brctl addif {} {}'.format(link['iface'], qrouter_br_veth) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + # up ns veth + command = 'sudo ip netns exec {} iptables -t nat -A POSTROUTING -o {} -s {} -d {} -j MASQUERADE' \ + .format(ns_qouter, qrouter_ns_router_veth, link['nat'], cidr) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + + else: + self.logger.error('Bridge {} given by user not exist'.format(qrouter_br_veth)) + + + + def create_link_bridge_to_ovs(self, vlan, link): + """ + Create interfaces to connect a linux bridge with tenant net + :param vlan: segmentation id + :return: True if success + """ + if self.test: + return True + try: + + br_tap_name = str(vlan) + '-vethBO' + br_ovs_name = str(vlan) + '-vethOB' + + # is a bridge or a interface + command = 'sudo brctl show | grep {}'.format(link) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if content > '': + command = 'sudo ip link add {} type veth peer name {}'.format(br_tap_name, br_ovs_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link set dev {} up'.format(br_tap_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ip link set dev {} up'.format(br_ovs_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo ovs-vsctl add-port br-int {} tag={}'.format(br_ovs_name, str(vlan)) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + command = 'sudo brctl addif ' + link + ' {}'.format(br_tap_name) + self.logger.debug("command: " + command) + (_, stdout, _) = self.ssh_conn.exec_command(command) + content = stdout.read() + + if len(content) == 0: + return True + else: + return False + else: + self.logger.error('Link is not present, please check {}'.format(link)) + return False + except paramiko.ssh_exception.SSHException as e: + self.logger.error("create_dhcp_interfaces ssh Exception: " + str(e)) + if "SSH session not active" in str(e): + self.ssh_connect() + return False def create_ovs_vxlan_tunnel(self, vxlan_interface, remote_ip): """ @@ -2344,8 +2720,8 @@ def create_server(server, db, db_lock, only_of_ports): #Get the brifge name db_lock.acquire() result, content = db.get_table(FROM='nets', - SELECT=('name', 'type', 'vlan', 'provider', 'enable_dhcp', - 'dhcp_first_ip', 'dhcp_last_ip', 'cidr'), + SELECT=('name', 'type', 'vlan', 'provider', 'enable_dhcp','dhcp_first_ip', + 'dhcp_last_ip', 'cidr', 'gateway_ip', 'dns', 'links', 'routes'), WHERE={'uuid': control_iface['net_id']}) db_lock.release() if result < 0: @@ -2370,6 +2746,13 @@ def create_server(server, db, db_lock, only_of_ports): control_iface["dhcp_first_ip"] = network["dhcp_first_ip"] control_iface["dhcp_last_ip"] = network["dhcp_last_ip"] control_iface["cidr"] = network["cidr"] + + if network.get("dns"): + control_iface["dns"] = yaml.safe_load(network.get("dns")) + if network.get("links"): + control_iface["links"] = yaml.safe_load(network.get("links")) + if network.get("routes"): + control_iface["routes"] = yaml.safe_load(network.get("routes")) 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/osm_openvim/httpserver.py b/osm_openvim/httpserver.py index 7126e21..e95c820 100644 --- a/osm_openvim/httpserver.py +++ b/osm_openvim/httpserver.py @@ -157,15 +157,25 @@ http2db_network={'id':'uuid','provider:vlan':'vlan', 'provider:physical': 'provi http2db_ofc = {'id': 'uuid'} http2db_port={'id':'uuid', 'network_id':'net_id', 'mac_address':'mac', 'device_owner':'type','device_id':'instance_id','binding:switch_port':'switch_port','binding:vlan':'vlan', 'bandwidth':'Mbps'} + def remove_extra_items(data, schema): + import re + deleted=[] if type(data) is tuple or type(data) is list: for d in data: a= remove_extra_items(d, schema['items']) if a is not None: deleted.append(a) elif type(data) is dict: + for k in data.keys(): - if 'properties' not in schema or k not in schema['properties'].keys(): + if 'patternProperties' in schema and k not in schema['properties'].keys(): + reg_ex_list = schema['patternProperties'].keys() + for reg_ex in reg_ex_list: + if not re.match(reg_ex, k): + del data[k] + deleted.append(k) + elif 'properties' not in schema or k not in schema['properties'].keys(): # or k not in schema['patternProperties'].keys(): del data[k] deleted.append(k) else: @@ -174,7 +184,8 @@ def remove_extra_items(data, schema): if len(deleted) == 0: return None elif len(deleted) == 1: return deleted[0] else: return deleted - + + def delete_nulls(var): if type(var) is dict: for k in var.keys(): @@ -648,7 +659,7 @@ def http_post_hosts(): # create bridge create_dhcp_ovs_bridge() config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge") - # check if more host exist + # create vlxan bwt OVS controller and computes create_vxlan_mesh(content['uuid']) # return host data @@ -674,8 +685,8 @@ def delete_dhcp_ovs_bridge(vlan, net_uuid): http_controller = config_dic['http_threads'][threading.current_thread().name] dhcp_controller = http_controller.ovim.get_dhcp_controller() - dhcp_controller.delete_dhcp_port(vlan, net_uuid) dhcp_controller.delete_dhcp_server(vlan, net_uuid, dhcp_path) + dhcp_controller.delete_dhcp_port(vlan, net_uuid) def create_dhcp_ovs_bridge(): @@ -713,7 +724,7 @@ def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac): http_controller = config_dic['http_threads'][threading.current_thread().name] dhcp_controller = http_controller.ovim.get_dhcp_controller() - dhcp_controller.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path) + dhcp_controller.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, first_ip, dhcp_path) def delete_mac_dhcp(vm_ip, vlan, mac): @@ -1614,7 +1625,7 @@ def http_post_server_id(tenant_id): if net['type'] == 'instance:ovs': dhcp_nets_id.append(get_network_id(net['net_id'])) - ports_to_free=[] + ports_to_free = [] new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free) if new_instance_result < 0: print "Error http_post_servers() :", new_instance_result, new_instance @@ -1623,6 +1634,8 @@ def http_post_server_id(tenant_id): print print "inserted at DB" print + + for port in ports_to_free: r,c = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port ) if r < 0: @@ -1652,14 +1665,24 @@ def http_post_server_id(tenant_id): dhcp_enable = bool(server_net['network']['enable_dhcp']) vm_dhcp_ip = c2[0]["ip_address"] config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan) + + dns = yaml.safe_load(server_net['network'].get("dns")) + routes = yaml.safe_load(server_net['network'].get("routes")) + links = yaml.safe_load(server_net['network'].get("links")) 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']) gateway = str(server_net['network']['gateway_ip']) - set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac']) + http_controller = config_dic['http_threads'][threading.current_thread().name] - http_controller.ovim.launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, gateway) + http_controller.ovim.launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, + dhcp_cidr, gateway, dns, routes) + set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac']) + + if links: + http_controller.ovim.launch_link_bridge_to_ovs(vlan, gateway, dhcp_cidr, links, routes) + #Start server server['uuid'] = new_instance @@ -1679,6 +1702,7 @@ def http_post_server_id(tenant_id): bottle.abort(HTTP_Bad_Request, content) return + def http_server_action(server_id, tenant_id, action): '''Perform actions over a server as resume, reboot, terminate, ...''' my = config_dic['http_threads'][ threading.current_thread().name ] @@ -1830,14 +1854,22 @@ 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) + # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan, vm_ip, mac) + 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) + + net_data = my.ovim.show_network(net_id) + if net_data.get('links'): + links = yaml.load(net_data.get('links')) + my.ovim.delete_link_bridge_to_ovs(vlan, links) + config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id) if warn_text: data["result"] += warn_text @@ -1956,7 +1988,7 @@ def http_post_networks(): try: # parse input data - http_content = format_in(network_new_schema ) + http_content = format_in(network_new_schema) r = remove_extra_items(http_content, network_new_schema) if r is not None: print "http_post_networks: Warning: remove extra items ", r diff --git a/osm_openvim/ovim.py b/osm_openvim/ovim.py index 6472cc1..2fca38f 100755 --- a/osm_openvim/ovim.py +++ b/osm_openvim/ovim.py @@ -28,6 +28,7 @@ Two thread will be launched, with normal and administrative permissions. """ import threading +import yaml import vim_db import logging # import imp @@ -42,9 +43,9 @@ import openflow_conn __author__ = "Alfonso Tierno, Leonardo Mirabal" __date__ = "$06-Feb-2017 12:07:15$" -__version__ = "0.5.17-r533" +__version__ = "0.5.18-r534" version_date = "Jun 2017" -database_version = 20 #needed database schema version +database_version = 21 #needed database schema version HTTP_Bad_Request = 400 HTTP_Unauthorized = 401 @@ -146,7 +147,7 @@ class ovim(): if "dhcp_last_ip" not in network: network["dhcp_last_ip"] = str(ips[-2]) if "gateway_ip" not in network: - network["gateway_ip"] = str(ips[2]) + network["gateway_ip"] = str(ips[1]) return True else: @@ -285,11 +286,16 @@ class ovim(): if (net_type == 'bridge_data' or net_type == 'bridge_man') and \ net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true": try: - self.launch_dhcp_server(net['vlan'], - net['dhcp_first_ip'], - net['dhcp_last_ip'], - net['cidr'], - net['gateway_ip']) + routes = yaml.safe_load(net.get('routes')) + dns = yaml.safe_load(net.get('dns')) + self.launch_dhcp_server(net.get('vlan'), + net.get('dhcp_first_ip'), + net.get('dhcp_last_ip'), + net.get('cidr'), + net.get('gateway_ip'), + dns, + routes) + self.launch_link_bridge_to_ovs(net['vlan'], net.get('links'), net.get('routes')) except Exception as e: self.logger.error("Fail at launching dhcp server for net_id='%s' net_name='%s': %s", net["uuid"], net["name"], str(e)) @@ -640,6 +646,13 @@ class ovim(): dhcp_integrity = True if 'enable_dhcp' in network and network['enable_dhcp']: dhcp_integrity = self._check_dhcp_data_integrity(network) + + if network.get('links'): + network['links'] = yaml.safe_dump(network['links'], default_flow_style=True, width=256) + if network.get('dns'): + network['dns'] = yaml.safe_dump(network['dns'], default_flow_style=True, width=256) + if network.get('routes'): + network['routes'] = yaml.safe_dump(network['routes'], default_flow_style=True, width=256) result, content = self.db.new_row('nets', network, True, True) @@ -1392,7 +1405,7 @@ class ovim(): return dhcp_host - def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway): + def launch_dhcp_server(self, vlan, first_ip, last_ip, cidr, gateway, dns, routes): """ Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes :param vlan: vlan identifier @@ -1409,9 +1422,49 @@ class ovim(): dhcp_path = self.config['ovs_controller_file_path'] controller_host = self.get_dhcp_controller() - controller_host.create_linux_bridge(vlan) - controller_host.create_dhcp_interfaces(vlan, gateway, dhcp_netmask) - controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway) + # TODO leo check if is need ti to create an ovim-vlan bridge, looks like not + # controller_host.create_linux_bridge(vlan) + controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask) + dhcp_path = self.config['ovs_controller_file_path'] + controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path, gateway, dns, routes) + + def launch_link_bridge_to_ovs(self, vlan, gateway, dhcp_cidr, links=None, routes=None): + """ + Launch creating of connections (veth) between user bridge (link) and OVS + :param vlan: + :param gateway: + :param links: + :return: + """ + + if links: + controller_host = self.get_dhcp_controller() + for link in links: + if 'iface' in link and 'nat' not in link: + controller_host.create_link_bridge_to_ovs(vlan, link['iface']) + elif 'nat' in link: + controller_host.create_qrouter_ovs_connection(vlan, gateway, dhcp_cidr) + controller_host.create_qrouter_br_connection(vlan, dhcp_cidr, link) + + if len(routes): + controller_host.add_ns_routes(vlan, routes) + + def delete_link_bridge_to_ovs(self, vlan, links=None): + """ + Delete connections (veth) between user bridge (link) and OVS + :param vlan: + :param links: + :return: + """ + if links: + controller_host = self.get_dhcp_controller() + + for link in links: + if 'iface' in link and 'nat' not in link: + controller_host.remove_link_bridge_to_ovs(vlan, link['iface']) + elif 'nat' in link: + controller_host.delete_qrouter_connection(vlan, link['iface']) + if __name__ == "__main__": diff --git a/osm_openvim/vim_db.py b/osm_openvim/vim_db.py index 5fc7c91..0c2d9f1 100644 --- a/osm_openvim/vim_db.py +++ b/osm_openvim/vim_db.py @@ -1428,6 +1428,9 @@ class vim_db(): 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) + del iface['links'] + del iface['dns'] + del iface['routes'] iface['uuid'] = str(myUuid.uuid1()) # create_uuid cmd = "INSERT INTO uuids (uuid, root_uuid, used_at) VALUES ('%s','%s', 'ports')" % (iface['uuid'], uuid) @@ -1443,6 +1446,7 @@ class vim_db(): else: iface['mac'] = iface['mac_address'] del iface['mac_address'] + #iface['mac']=iface.pop('mac_address', None) #for leaving mac generation to libvirt keys = ",".join(iface.keys()) values = ",".join( map(lambda x: "Null" if x is None else "'"+str(x)+"'", iface.values() ) ) @@ -1552,6 +1556,7 @@ class vim_db(): ip_used_list.append(str(ips[1])) # gw ip ip_used_list.append(str(ips[-1])) # broadcast ip + ip_used_list.append(first_ip) for vm_ip in ips: if str(vm_ip) not in ip_used_list and IPAddress(first_ip) <= IPAddress(vm_ip) <= IPAddress(last_ip): diff --git a/osm_openvim/vim_schema.py b/osm_openvim/vim_schema.py index 1925f05..4689a89 100644 --- a/osm_openvim/vim_schema.py +++ b/osm_openvim/vim_schema.py @@ -603,10 +603,26 @@ network_new_schema = { "enable_dhcp": {"type": "boolean"}, "dhcp_first_ip": ip_schema, "dhcp_last_ip": ip_schema, + "dns": {"type": "array", "items": [ip_schema]}, + "links": {"type": "array", "items": {"type": "object", "properties": { + "nat": cidr_schema, + "iface": name_schema, + "vlan": vlan_schema}, + "required": ["iface"], + "additionalProperties": False + }, + + }, + "routes": {"type": "object", "properties": {"default": ip_schema}, "patternProperties": { + "^([0-9]{1,3}.){3}[0-9]{1,3}/[0-9]{1,2}$": ip_schema, + }, + "additionalProperties": False + }, + "bind_net": name_schema, # can be name, or uuid "bind_type": {"oneOf": [{"type": "null"}, {"type": "string", "pattern": "^vlan:[0-9]{1,4}$"}]} }, - "required": ["name"] + "required": ["name"] } }, "required": ["network"], diff --git a/test/networks/net-example5.yaml b/test/networks/net-example5.yaml new file mode 100644 index 0000000..8dc865c --- /dev/null +++ b/test/networks/net-example5.yaml @@ -0,0 +1,20 @@ +network: + name: mgmt + type: bridge_man + shared: True + cidr: 192.168.11.0/24 + enable_dhcp: True + dhcp_first_ip: 192.168.11.16 + dhcp_last_ip: 192.168.11.200 + dns: + - 8.8.8.8 + - 8.8.4.4 + links: + - nat: 10.250.0.3/24 + iface: bridge-osm # Needs to be create in advance by the user + - nat: 10.90.80.5/24 + iface: bridge-internet # Needs to be create in advance by the user + - iface: bridge-private # Needs to be create in advance by the user + routes: + default: 192.168.11.1 # route will be propagate via dhcp server, GW can not be in the dhcp range + 10.90.80.0/24: 192.168.11.20 # route will be propagate via dhcp server -- 2.25.1