X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=httpserver.py;h=4062ef1743754ecf285a931bff3852c2645d1191;hb=e9317ff999987c09369b9eab6bcd044ced8f1048;hp=9f2673711bb756958b702751a72afce867bc3449;hpb=0d8afbe7ad79b031da61c710911aee9a7cf54cbb;p=osm%2Fopenvim.git diff --git a/httpserver.py b/httpserver.py index 9f26737..4062ef1 100644 --- a/httpserver.py +++ b/httpserver.py @@ -26,8 +26,8 @@ This is the thread for the http server North API. Two thread will be launched, with normal and administrative permissions. ''' -__author__="Alfonso Tierno" -__date__ ="$10-jul-2014 12:07:15$" +__author__ = "Alfonso Tierno, Leonardo Mirabal" +__date__ = "$10-jul-2014 12:07:15$" import bottle import urlparse @@ -37,7 +37,9 @@ import threading import datetime import hashlib import os -import RADclass +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 from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \ @@ -50,6 +52,8 @@ from vim_schema import host_new_schema, host_edit_schema, tenant_new_schema, \ global my global url_base global config_dic +global RADclass_module +RADclass=None #RADclass module is charged only if not in test mode url_base="/openvim" @@ -489,8 +493,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_) @@ -503,7 +511,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): @@ -535,9 +543,14 @@ def http_post_hosts(): ip_name=host['ip_name'] user=host['user'] password=host.get('password', None) + if not RADclass_module: + try: + RADclass_module = imp.find_module("RADclass") + except (IOError, ImportError) as e: + raise ImportError("Cannot import RADclass.py Openvim not properly installed" +str(e)) #fill rad info - rad = RADclass.RADclass() + rad = RADclass_module.RADclass() (return_status, code) = rad.obtain_RAD(user, password, ip_name) #return @@ -620,6 +633,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: @@ -630,6 +650,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''' @@ -651,12 +845,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: @@ -674,9 +877,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} @@ -1371,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: @@ -1391,23 +1603,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 @@ -1544,9 +1770,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: @@ -1565,7 +1793,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) @@ -1618,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} @@ -1626,8 +1862,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) @@ -1639,7 +1879,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',), @@ -1648,7 +1888,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(): @@ -1671,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") @@ -1731,8 +1976,7 @@ def http_post_networks(): net_type='bridge_man' if net_provider != None: - if net_provider[:7]=='bridge:': - #check it is one of the pre-provisioned bridges + if net_provider[:7] == 'bridge:': bridge_net_name = net_provider[7:] for brnet in config_dic['bridge_nets']: if brnet[0]==bridge_net_name: # free @@ -1745,7 +1989,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 @@ -1764,22 +2008,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 @@ -1793,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.''' @@ -2111,7 +2380,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': @@ -2190,3 +2459,4 @@ def http_delete_port_id(port_id): bottle.abort(-result, content) return +