Merge branch 'v1.1'
[osm/openvim.git] / httpserver.py
index 08ea403..1237fb4 100644 (file)
@@ -26,7 +26,7 @@ This is the thread for the http server North API.
 Two thread will be launched, with normal and administrative permissions.
 '''
 
-__author__="Alfonso Tierno, Gerardo Garcia"
+__author__="Alfonso Tierno, Gerardo Garcia, Leonardo Mirabal"
 __date__ ="$10-jul-2014 12:07:15$"
 
 import bottle
@@ -38,6 +38,7 @@ import datetime
 import hashlib
 import os
 import imp
+from netaddr import IPNetwork, IPAddress, all_matching_cidrs
 #import only if needed because not needed in test mode. To allow an easier installation   import RADclass
 from jsonschema import validate as js_v, exceptions as js_e
 import host_thread as ht
@@ -497,8 +498,12 @@ def enable_cors():
 
 @bottle.route(url_base + '/hosts', method='GET')
 def http_get_hosts():
-    select_,where_,limit_ = filter_query_string(bottle.request.query, http2db_host,
-            ('id','name','description','status','admin_state_up') )
+    return format_out(get_hosts())
+
+
+def get_hosts():
+    select_, where_, limit_ = filter_query_string(bottle.request.query, http2db_host,
+                                                  ('id', 'name', 'description', 'status', 'admin_state_up', 'ip_name'))
     
     myself = config_dic['http_threads'][ threading.current_thread().name ]
     result, content = myself.db.get_table(FROM='hosts', SELECT=select_, WHERE=where_, LIMIT=limit_)
@@ -511,7 +516,7 @@ def http_get_hosts():
         for row in content:
             row['links'] = ( {'href': myself.url_preffix + '/hosts/' + str(row['id']), 'rel': 'bookmark'}, )
         data={'hosts' : content}
-        return format_out(data)
+        return data
 
 @bottle.route(url_base + '/hosts/<host_id>', method='GET')
 def http_get_host_id(host_id):
@@ -633,6 +638,13 @@ def http_post_hosts():
             thread.start()
             config_dic['host_threads'][ content['uuid'] ] = thread
 
+            if config_dic['network_type'] == 'ovs':
+                # create bridge
+                create_dhcp_ovs_bridge()
+                config_dic['host_threads'][content['uuid']].insert_task("new-ovsbridge")
+                # check if more host exist
+                create_vxlan_mesh(content['uuid'])
+
         #return host data
         change_keys_http2db(content, http2db_host, reverse=True)
         if len(warning_text)>0:
@@ -643,6 +655,180 @@ def http_post_hosts():
         bottle.abort(HTTP_Bad_Request, content)
         return
 
+
+def get_dhcp_controller():
+    """
+    Create an host_thread object for manage openvim controller and not create a thread for itself
+    :return: dhcp_host openvim controller object
+    """
+
+    if 'openvim_controller' in config_dic['host_threads']:
+        return config_dic['host_threads']['openvim_controller']
+
+    bridge_ifaces = []
+    controller_ip = config_dic['ovs_controller_ip']
+    ovs_controller_user = config_dic['ovs_controller_user']
+
+    host_test_mode = True if config_dic['mode'] == 'test' or config_dic['mode'] == "OF only" else False
+    host_develop_mode = True if config_dic['mode'] == 'development' else False
+
+    dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip, db=config_dic['db'],
+                               db_lock=config_dic['db_lock'], test=host_test_mode,
+                               image_path=config_dic['image_path'], version=config_dic['version'],
+                               host_id='openvim_controller', develop_mode=host_develop_mode,
+                               develop_bridge_iface=bridge_ifaces)
+
+    config_dic['host_threads']['openvim_controller'] = dhcp_host
+    dhcp_host.ssh_connect()
+    return dhcp_host
+
+
+def delete_dhcp_ovs_bridge(vlan, net_uuid):
+    """
+    Delete bridges and port created during dhcp launching at openvim controller
+    :param vlan: net vlan id
+    :param net_uuid: network identifier
+    :return:
+    """
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.delete_dhcp_port(vlan, net_uuid)
+    controller_host.delete_dhcp_server(vlan, net_uuid, dhcp_path)
+
+
+def create_dhcp_ovs_bridge():
+    """
+    Initialize bridge to allocate the dhcp server at openvim controller
+    :return:
+    """
+    controller_host = get_dhcp_controller()
+    controller_host.create_ovs_bridge()
+
+
+def set_mac_dhcp(vm_ip, vlan, first_ip, last_ip, cidr, mac):
+    """"
+    Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
+    :param vm_ip: IP address asigned to a VM
+    :param vlan: Segmentation id
+    :param first_ip: First dhcp range ip
+    :param last_ip: Last dhcp range ip
+    :param cidr: net cidr
+    :param mac: VM vnic mac to be macthed with the IP received
+    """
+    if not vm_ip:
+        return
+    ip_tools = IPNetwork(cidr)
+    cidr_len = ip_tools.prefixlen
+    dhcp_netmask = str(ip_tools.netmask)
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    new_cidr = [first_ip + '/' + str(cidr_len)]
+    if not len(all_matching_cidrs(vm_ip, new_cidr)):
+        vm_ip = None
+
+    controller_host = get_dhcp_controller()
+    controller_host.set_mac_dhcp_server(vm_ip, mac, vlan, dhcp_netmask, dhcp_path)
+
+
+def delete_mac_dhcp(vm_ip, vlan, mac):
+    """
+    Delete into dhcp conf file the ip  assigned to a specific MAC address
+    :param vm_ip: IP address asigned to a VM
+    :param vlan: Segmentation id
+    :param mac:  VM vnic mac to be macthed with the IP received
+    :return:
+    """
+
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.delete_mac_dhcp_server(vm_ip, mac, vlan, dhcp_path)
+
+
+def launch_dhcp_server(vlan, first_ip, last_ip, cidr):
+    """
+    Launch a dhcpserver base on dnsmasq attached to the net base on vlan id across the the openvim computes
+    :param vlan: vlan identifier
+    :param first_ip: First dhcp range ip
+    :param last_ip: Last dhcp range ip
+    :param cidr: net cidr
+    :return:
+    """
+    ip_tools = IPNetwork(cidr)
+    dhcp_netmask = str(ip_tools.netmask)
+    ip_range = [first_ip, last_ip]
+    dhcp_path = config_dic['ovs_controller_file_path']
+
+    controller_host = get_dhcp_controller()
+    controller_host.create_linux_bridge(vlan)
+    controller_host.create_dhcp_interfaces(vlan, first_ip, dhcp_netmask)
+    controller_host.launch_dhcp_server(vlan, ip_range, dhcp_netmask, dhcp_path)
+
+
+def create_vxlan_mesh(host_id):
+    """
+    Create vxlan mesh across all openvimc controller and computes.
+    :param host_id: host identifier
+    :param host_id: host identifier
+    :return:
+    """
+    dhcp_compute_name = get_vxlan_interface("dhcp")
+    existing_hosts = get_hosts()
+    if len(existing_hosts['hosts']) > 0:
+        # vlxan mesh creation between openvim controller and computes
+        computes_available = existing_hosts['hosts']
+        controller_host = get_dhcp_controller()
+        for compute in computes_available:
+            vxlan_interface_name = get_vxlan_interface(compute['id'][:8])
+            config_dic['host_threads'][compute['id']].insert_task("new-vxlan", dhcp_compute_name, controller_host.host)
+            controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute['ip_name'])
+
+        # vlxan mesh creation between openvim computes
+        for count, compute_owner in enumerate(computes_available):
+            for compute in computes_available:
+                if compute_owner['id'] == compute['id']:
+                    pass
+                else:
+                    vxlan_interface_name = get_vxlan_interface(compute_owner['id'][:8])
+                    controller_host.create_ovs_vxlan_tunnel(vxlan_interface_name, compute_owner['ip_name'])
+                    config_dic['host_threads'][compute['id']].insert_task("new-vxlan",
+                                                                          vxlan_interface_name,
+                                                                          compute_owner['ip_name'])
+
+
+def delete_vxlan_mesh(host_id):
+    """
+    Create a task for remove a specific compute of the vlxan mesh
+    :param host_id: host id to be deleted.
+    """
+    existing_hosts = get_hosts()
+    computes_available = existing_hosts['hosts']
+    #
+    vxlan_interface_name = get_vxlan_interface(host_id[:8])
+    controller_host = get_dhcp_controller()
+    controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
+    # remove bridge from openvim controller if no more computes exist
+    if len(existing_hosts):
+        controller_host.delete_ovs_bridge()
+    # Remove vxlan mesh
+    for compute in computes_available:
+        if host_id == compute['id']:
+            pass
+        else:
+            controller_host.delete_ovs_vxlan_tunnel(vxlan_interface_name)
+            config_dic['host_threads'][compute['id']].insert_task("del-vxlan", vxlan_interface_name)
+
+
+def get_vxlan_interface(local_uuid):
+    """
+    Genearte a vxlan interface name
+    :param local_uuid: host id
+    :return: vlxan-8digits
+    """
+    return 'vxlan-' + local_uuid[:8]
+
+
 @bottle.route(url_base + '/hosts/<host_id>', method='PUT')
 def http_put_host_id(host_id):
     '''modify a host into the database. All resources are got and inserted'''
@@ -664,12 +850,21 @@ def http_put_host_id(host_id):
         change_keys_http2db(content, http2db_host, reverse=True)
         data={'host' : content}
 
+        if config_dic['network_type'] == 'ovs':
+            delete_vxlan_mesh(host_id)
+            config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
+
         #reload thread
         config_dic['host_threads'][host_id].name = content.get('name',content['ip_name'])
         config_dic['host_threads'][host_id].user = content['user']
         config_dic['host_threads'][host_id].host = content['ip_name']
         config_dic['host_threads'][host_id].insert_task("reload")
 
+        if config_dic['network_type'] == 'ovs':
+            # create mesh with new host data
+            config_dic['host_threads'][host_id].insert_task("new-ovsbridge")
+            create_vxlan_mesh(host_id)
+
         #print data
         return format_out(data)
     else:
@@ -687,9 +882,13 @@ def http_delete_host_id(host_id):
     result, content = my.db.delete_row('hosts', host_id)
     if result == 0:
         bottle.abort(HTTP_Not_Found, content)
-    elif result >0:
-        #terminate thread
+    elif result > 0:
+        if config_dic['network_type'] == 'ovs':
+            delete_vxlan_mesh(host_id)
+        # terminate thread
         if host_id in config_dic['host_threads']:
+            if config_dic['network_type'] == 'ovs':
+                config_dic['host_threads'][host_id].insert_task("del-ovsbridge")
             config_dic['host_threads'][host_id].insert_task("exit")
         #return data
         data={'result' : content}
@@ -1411,6 +1610,11 @@ def http_post_server_id(tenant_id):
         print
         if server_start == 'no':
             content['status'] = 'INACTIVE'
+        dhcp_nets_id = []
+        for net in http_content['server']['networks']:
+            if net['type'] == 'instance:ovs':
+                dhcp_nets_id.append(get_network_id(net['net_id']))
+
         ports_to_free=[]
         new_instance_result, new_instance = my.db.new_instance(content, nets, ports_to_free)
         if new_instance_result < 0:
@@ -1431,23 +1635,37 @@ def http_post_server_id(tenant_id):
                 print ':http_post_servers ERROR UPDATING NETS !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c
 
             
-            
-        #look for dhcp ip address 
-        r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": new_instance})
-        if r2 >0 and config_dic.get("dhcp_server"):
+        #look for dhcp ip address
+        r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "ip_address", "net_id"], WHERE={"instance_id": new_instance})
+        if r2 >0:
             for iface in c2:
-                if iface["net_id"] in config_dic["dhcp_nets"]:
+                if config_dic.get("dhcp_server") and iface["net_id"] in config_dic["dhcp_nets"]:
                     #print "dhcp insert add task"
                     r,c = config_dic['dhcp_thread'].insert_task("add", iface["mac"])
                     if r < 0:
-                        print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c 
-        
-    #Start server
-        
+                        print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c
+
+                #ensure compute contain the bridge for ovs networks:
+                server_net = get_network_id(iface['net_id'])
+                if server_net["network"].get('provider:physical', "")[:3] == 'OVS':
+                    vlan = str(server_net['network']['provider:vlan'])
+                    dhcp_enable = bool(server_net['network']['enable_dhcp'])
+                    if dhcp_enable:
+                        dhcp_firt_ip = str(server_net['network']['dhcp_first_ip'])
+                        dhcp_last_ip = str(server_net['network']['dhcp_last_ip'])
+                        dhcp_cidr = str(server_net['network']['cidr'])
+                        vm_dhcp_ip = c2[0]["ip_address"]
+                        config_dic['host_threads'][server['host_id']].insert_task("create-ovs-bridge-port", vlan)
+
+                        set_mac_dhcp(vm_dhcp_ip, vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr, c2[0]['mac'])
+                        launch_dhcp_server(vlan, dhcp_firt_ip, dhcp_last_ip, dhcp_cidr)
+
+        #Start server
         server['uuid'] = new_instance
-        #server_start = server.get('start', 'yes')
+        server_start = server.get('start', 'yes')
+
         if server_start != 'no':
-            server['paused'] = True if server_start == 'paused' else False 
+            server['paused'] = True if server_start == 'paused' else False
             server['action'] = {"start":None}
             server['status'] = "CREATING"
             #Program task
@@ -1585,9 +1803,11 @@ def http_server_action(server_id, tenant_id, action):
     if new_status != None and new_status == 'DELETING':
         nets=[]
         ports_to_free=[]
-        #look for dhcp ip address 
+
+        net_ovs_list = []
+        #look for dhcp ip address
         r2, c2 = my.db.get_table(FROM="ports", SELECT=["mac", "net_id"], WHERE={"instance_id": server_id})
-        r,c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, "requested by http")
+        r, c = my.db.delete_instance(server_id, tenant_id, nets, ports_to_free, net_ovs_list, "requested by http")
         for port in ports_to_free:
             r1,c1 = config_dic['host_threads'][ server['host_id'] ].insert_task( 'restore-iface',*port )
             if r1 < 0:
@@ -1606,7 +1826,15 @@ def http_server_action(server_id, tenant_id, action):
                     #print "dhcp insert del task"
                     if r < 0:
                         print ':http_post_servers ERROR UPDATING dhcp_server !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!' +  c 
-
+        # delete ovs-port and linux bridge, contains a list of tuple (net_id,vlan)
+        for net in net_ovs_list:
+            mac = str(net[3])
+            vm_ip = str(net[2])
+            vlan = str(net[1])
+            net_id = net[0]
+            delete_dhcp_ovs_bridge(vlan, net_id)
+            delete_mac_dhcp(vm_ip, vlan, mac)
+            config_dic['host_threads'][server['host_id']].insert_task('del-ovs-port', vlan, net_id)
     return format_out(data)
 
 
@@ -1659,7 +1887,7 @@ def http_get_networks():
         print "http_get_networks error %d %s" % (result, content)
         bottle.abort(-result, content)
     else:
-        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp') )
+        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
         delete_nulls(content)      
         change_keys_http2db(content, http2db_network, reverse=True)  
         data={'networks' : content}
@@ -1667,8 +1895,12 @@ def http_get_networks():
 
 @bottle.route(url_base + '/networks/<network_id>', method='GET')
 def http_get_network_id(network_id):
-    my = config_dic['http_threads'][ threading.current_thread().name ]
-    #obtain data
+        data = get_network_id(network_id)
+        return format_out(data)
+
+def get_network_id(network_id):
+    my = config_dic['http_threads'][threading.current_thread().name]
+    # obtain data
     where_ = bottle.request.query
     where_['uuid'] = network_id
     result, content = my.db.get_table(FROM='nets', WHERE=where_, LIMIT=100)
@@ -1680,7 +1912,7 @@ def http_get_network_id(network_id):
         print "http_get_networks_id network '%s' not found" % network_id
         bottle.abort(HTTP_Not_Found, 'network %s not found' % network_id)
     else:
-        convert_boolean(content, ('shared', 'admin_state_up', 'enale_dhcp') )
+        convert_boolean(content, ('shared', 'admin_state_up', 'enable_dhcp'))
         change_keys_http2db(content, http2db_network, reverse=True)        
         #get ports
         result, ports = my.db.get_table(FROM='ports', SELECT=('uuid as port_id',), 
@@ -1689,7 +1921,7 @@ def http_get_network_id(network_id):
             content[0]['ports'] = ports
         delete_nulls(content[0])      
         data={'network' : content[0]}
-        return format_out(data)
+        return data
 
 @bottle.route(url_base + '/networks', method='POST')
 def http_post_networks():
@@ -1712,6 +1944,10 @@ def http_post_networks():
     #check valid params
     net_provider = network.get('provider')
     net_type =     network.get('type')
+    net_enable_dhcp = network.get('enable_dhcp')
+    if net_enable_dhcp:
+        net_cidr = network.get('cidr')
+
     net_vlan =     network.get("vlan")
     net_bind_net = network.get("bind_net")
     net_bind_type= network.get("bind_type")
@@ -1786,7 +2022,7 @@ def http_post_networks():
 #            if bridge_net==None:     
 #                bottle.abort(HTTP_Bad_Request, "invalid 'provider:physical', bridge '%s' is not one of the provisioned 'bridge_ifaces' in the configuration file" % bridge_net_name)
 #                return
-    elif net_type=='bridge_data' or net_type=='bridge_man':
+    elif config_dic['network_type'] == 'bridge' and ( net_type =='bridge_data' or net_type ==  'bridge_man' ):
         #look for a free precreated nets
         for brnet in config_dic['bridge_nets']:
             if brnet[3]==None: # free
@@ -1805,22 +2041,29 @@ def http_post_networks():
             print "using net", bridge_net
             net_provider = "bridge:"+bridge_net[0]
             net_vlan = bridge_net[1]
-    if net_vlan==None and (net_type=="data" or net_type=="ptp"):
+    elif net_type == 'bridge_data' or net_type == 'bridge_man' and config_dic['network_type'] == 'ovs':
+        net_provider = 'OVS'
+    if not net_vlan and (net_type == "data" or net_type == "ptp" or net_provider == "OVS"):
         net_vlan = my.db.get_free_net_vlan()
         if net_vlan < 0:
             bottle.abort(HTTP_Internal_Server_Error, "Error getting an available vlan")
             return
-    
+    if net_provider == 'OVS':
+        net_provider = 'OVS' + ":" + str(net_vlan)
+
     network['provider'] = net_provider
     network['type']     = net_type
     network['vlan']     = net_vlan
+
+    if 'enable_dhcp' in network and network['enable_dhcp']:
+        check_dhcp_data_integrity(network)
+
     result, content = my.db.new_row('nets', network, True, True)
     
     if result >= 0:
         if bridge_net!=None:
             bridge_net[3] = content
-        
-        if config_dic.get("dhcp_server"):
+        if config_dic.get("dhcp_server") and config_dic['network_type'] == 'bridge':
             if network["name"] in config_dic["dhcp_server"].get("nets", () ):
                 config_dic["dhcp_nets"].append(content)
                 print "dhcp_server: add new net", content
@@ -1834,6 +2077,24 @@ def http_post_networks():
         return
 
 
+def check_dhcp_data_integrity(network):
+    """
+    Check if all dhcp parameter for anet are valid, if not will be calculated from cidr value
+    :param network: list with user nets paramters
+    :return:
+    """
+    control_iface = []
+
+    if "cidr" in network:
+        cidr = network["cidr"]
+
+        ips = IPNetwork(cidr)
+        if "dhcp_first_ip" not in network:
+            network["dhcp_first_ip"] = str(ips[2])
+        if "dhcp_last_ip" not in network:
+            network["dhcp_last_ip"] = str(ips[-2])
+
+
 @bottle.route(url_base + '/networks/<network_id>', method='PUT')
 def http_put_network_id(network_id):
     '''update a network_id into the database.'''
@@ -2152,7 +2413,7 @@ def http_put_port_id(port_id):
             
             if new_net is not None: nets.append(new_net) #put first the new net, so that new openflow rules are created before removing the old ones 
             if old_net is not None: nets.append(old_net)
-            if port['type'] == 'instance:bridge':
+            if port['type'] == 'instance:bridge' or port['type'] == 'instance:ovs':
                 bottle.abort(HTTP_Forbidden, "bridge interfaces cannot be attached to a different net")
                 return
             elif port['type'] == 'external':