Empty disk support added to openvim
[osm/openvim.git] / osm_openvim / host_thread.py
index 0cf2691..00d253e 100644 (file)
@@ -34,20 +34,21 @@ import threading
 import time
 import Queue
 import paramiko
-from jsonschema import validate as js_v, exceptions as js_e
-#import libvirt
+# import subprocess
+# import libvirt
 import imp
-from vim_schema import localinfo_schema, hostinfo_schema
 import random
 import os
 import logging
+from jsonschema import validate as js_v, exceptions as js_e
+from vim_schema import localinfo_schema, hostinfo_schema
 
 
 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, logger_name=None, debug=None):
+                 develop_bridge_iface, password=None, keyfile = None, logger_name=None, debug=None):
         '''Init a thread.
         Arguments:
             'id' number of thead
@@ -62,6 +63,8 @@ class host_thread(threading.Thread):
         self.db = db
         self.db_lock = db_lock
         self.test = test
+        self.password = password
+        self.keyfile =  keyfile
         self.localinfo_dirty = False
 
         if not test and not host_thread.lvirt_module:
@@ -82,6 +85,7 @@ class host_thread(threading.Thread):
         self.develop_mode = develop_mode
         self.develop_bridge_iface = develop_bridge_iface
         self.image_path = image_path
+        self.empty_image_path = image_path
         self.host_id = host_id
         self.version = version
         
@@ -97,33 +101,38 @@ class host_thread(threading.Thread):
         self.queueLock = threading.Lock()
         self.taskQueue = Queue.Queue(2000)
         self.ssh_conn = None
+        self.lvirt_conn_uri = "qemu+ssh://{user}@{host}/system?no_tty=1&no_verify=1".format(
+            user=self.user, host=self.host)
+        if keyfile:
+            self.lvirt_conn_uri += "&keyfile=" + keyfile
 
     def ssh_connect(self):
         try:
-            #Connect SSH
+            # Connect SSH
             self.ssh_conn = paramiko.SSHClient()
             self.ssh_conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
             self.ssh_conn.load_system_host_keys()
-            self.ssh_conn.connect(self.host, username=self.user, timeout=10) #, None)
+            self.ssh_conn.connect(self.host, username=self.user, password=self.password, key_filename=self.keyfile,
+                                  timeout=10) #, None)
         except paramiko.ssh_exception.SSHException as e:
             text = e.args[0]
             self.logger.error("ssh_connect ssh Exception: " + text)
-        
+
     def load_localinfo(self):
         if not self.test:
             try:
-                #Connect SSH
+                # Connect SSH
                 self.ssh_connect()
-    
+
                 command = 'mkdir -p ' +  self.image_path
-                #print self.name, ': command:', command
+                # print self.name, ': command:', command
                 (_, stdout, stderr) = self.ssh_conn.exec_command(command)
                 content = stderr.read()
                 if len(content) > 0:
                     self.logger.error("command: '%s' stderr: '%s'", command, content)
 
                 command = 'cat ' +  self.image_path + '/.openvim.yaml'
-                #print self.name, ': command:', command
+                # print self.name, ': command:', command
                 (_, stdout, stderr) = self.ssh_conn.exec_command(command)
                 content = stdout.read()
                 if len(content) == 0:
@@ -136,7 +145,7 @@ class host_thread(threading.Thread):
                     self.localinfo['server_files'] = {}
                 self.logger.debug("localinfo load from host")
                 return
-    
+
             except paramiko.ssh_exception.SSHException as e:
                 text = e.args[0]
                 self.logger.error("load_localinfo ssh Exception: " + text)
@@ -214,13 +223,14 @@ class host_thread(threading.Thread):
             tries-=1
             
             try:
-                command = 'cat > ' +  self.image_path + '/.openvim.yaml'
+                command = 'cat > ' + self.image_path + '/.openvim.yaml'
                 self.logger.debug("command:" + command)
                 (stdin, _, _) = self.ssh_conn.exec_command(command)
                 yaml.safe_dump(self.localinfo, stdin, explicit_start=True, indent=4, default_flow_style=False, tags=False, encoding='utf-8', allow_unicode=True)
+
                 self.localinfo_dirty = False
                 break #while tries
-    
+
             except paramiko.ssh_exception.SSHException as e:
                 text = e.args[0]
                 self.logger.error("save_localinfo ssh Exception: " + text)
@@ -427,8 +437,7 @@ class host_thread(threading.Thread):
         if topo == None and 'metadata' in dev_list[0]:
             topo = dev_list[0]['metadata'].get('topology', None)
     #name
-        name = server.get('name','') + "_" + server['uuid']
-        name = name[:58]  #qemu impose a length  limit of 59 chars or not start. Using 58
+        name = server.get('name', '')[:28] + "_" + server['uuid'][:28] #qemu impose a length  limit of 59 chars or not start. Using 58
         text += self.inc_tab() + "<name>" + name+ "</name>"
     #uuid
         text += self.tab() + "<uuid>" + server['uuid'] + "</uuid>" 
@@ -511,7 +520,7 @@ class host_thread(threading.Thread):
         if topo == "oneSocket:hyperthreading":
             if vcpus % 2 != 0:
                 return -1, 'Cannot expose hyperthreading with an odd number of vcpus'
-            text += self.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='2' /> </cpu>" % vcpus/2
+            text += self.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='2' /> </cpu>" % (vcpus/2)
         elif windows_os or topo == "oneSocket":
             text += self.tab() + "<cpu mode='host-model'> <topology sockets='1' cores='%d' threads='1' /> </cpu>" % vcpus
         else:
@@ -567,7 +576,7 @@ class host_thread(threading.Thread):
                 #else:
                 #    return -1, 'Unknown disk type ' + v['type']
                 vpci = dev.get('vpci',None)
-                if vpci == None:
+                if vpci == None and 'metadata' in dev:
                     vpci = dev['metadata'].get('vpci',None)
                 text += self.pci2xml(vpci)
                
@@ -628,7 +637,7 @@ class host_thread(threading.Thread):
             elif content[0]['provider'][0:3] == "OVS":
                 vlan = content[0]['provider'].replace('OVS:', '')
                 text += self.tab() + "<interface type='bridge'>" + \
-                        self.inc_tab() + "<source bridge='ovim-" + vlan + "'/>"
+                        self.inc_tab() + "<source bridge='ovim-" + str(vlan) + "'/>"
             else:
                 return -1, 'Unknown Bridge net provider ' + content[0]['provider']
             if model!=None:
@@ -751,7 +760,7 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            port_name = 'ovim-' + vlan
+            port_name = 'ovim-' + str(vlan)
             command = 'sudo ovs-vsctl del-port br-int ' + port_name
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
@@ -779,7 +788,7 @@ class host_thread(threading.Thread):
         if not self.is_dhcp_port_free(vlan, net_uuid):
             return True
         try:
-            net_namespace = 'ovim-' + vlan
+            net_namespace = 'ovim-' + str(vlan)
             dhcp_path = os.path.join(dhcp_path, net_namespace)
             pid_file = os.path.join(dhcp_path, 'dnsmasq.pid')
 
@@ -813,7 +822,7 @@ class host_thread(threading.Thread):
         self.db_lock.acquire()
         result, content = self.db.get_table(
             FROM='ports',
-            WHERE={'p.type': 'instance:ovs', 'p.net_id': net_uuid}
+            WHERE={'type': 'instance:ovs', 'net_id': net_uuid}
         )
         self.db_lock.release()
 
@@ -852,8 +861,8 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            port_name = 'ovim-' + vlan
-            command = 'sudo ovs-vsctl add-port br-int ' + port_name + ' tag=' + vlan
+            port_name = 'ovim-' + str(vlan)
+            command = 'sudo ovs-vsctl add-port br-int ' + port_name + ' tag=' + str(vlan)
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
@@ -909,8 +918,8 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            port_name = 'ovim-' + vlan
-            command = 'sudo ip link set dev veth0-' + vlan + ' down'
+            port_name = 'ovim-' + str(vlan)
+            command = 'sudo ip link set dev veth0-' + str(vlan) + ' down'
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             # content = stdout.read()
@@ -952,7 +961,7 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            port_name = 'ovim-' + vlan
+            port_name = 'ovim-' + str(vlan)
             command = 'sudo brctl show | grep ' + port_name
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
@@ -1010,7 +1019,7 @@ class host_thread(threading.Thread):
         if self.test:
             return True
 
-        net_namespace = 'ovim-' + vlan
+        net_namespace = 'ovim-' + str(vlan)
         dhcp_path = os.path.join(dhcp_path, net_namespace)
         dhcp_hostsdir = os.path.join(dhcp_path, net_namespace)
 
@@ -1054,7 +1063,7 @@ class host_thread(threading.Thread):
         if self.test:
             return False
         try:
-            net_namespace = 'ovim-' + vlan
+            net_namespace = 'ovim-' + str(vlan)
             dhcp_path = os.path.join(dhcp_path, net_namespace)
             dhcp_hostsdir = os.path.join(dhcp_path, net_namespace)
 
@@ -1094,8 +1103,8 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            interface = 'tap-' + vlan
-            net_namespace = 'ovim-' + vlan
+            interface = 'tap-' + str(vlan)
+            net_namespace = 'ovim-' + str(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')
@@ -1149,18 +1158,18 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            net_namespace = 'ovim-' + vlan
-            command = 'sudo ovs-vsctl del-port br-int ovs-tap-' + vlan
+            net_namespace = 'ovim-' + str(vlan)
+            command = 'sudo ovs-vsctl del-port br-int ovs-tap-' + str(vlan)
             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-' + vlan + ' down'
+            command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + str(vlan) + ' down'
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
 
-            command = 'sudo ip link set dev ovs-tap-' + vlan + ' down'
+            command = 'sudo ip link set dev ovs-tap-' + str(vlan) + ' down'
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
@@ -1170,11 +1179,11 @@ class host_thread(threading.Thread):
                 self.ssh_connect()
             return False
 
-    def create_dhcp_interfaces(self, vlan, ip, netmask):
+    def create_dhcp_interfaces(self, vlan, ip_listen_address, 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 ip_listen_address: Listen Ip address for the dhcp service, the tap interface living in namesapce side
         :param netmask: dhcp net CIDR
         :return: True if success
         """
@@ -1182,41 +1191,46 @@ class host_thread(threading.Thread):
         if self.test:
             return True
         try:
-            net_namespace = 'ovim-' + vlan
-            namespace_interface = 'tap-' + vlan
+            net_namespace = 'ovim-' + str(vlan)
+            namespace_interface = 'tap-' + str(vlan)
 
             command = 'sudo ip netns add ' + net_namespace
             self.logger.debug("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
+            command = 'sudo ip link add tap-' + str(vlan) + ' type veth peer name ovs-tap-' + str(vlan)
+            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)
             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-' + vlan + ' tag=' + vlan
+            command = 'sudo ip link set tap-' + str(vlan) + ' netns ' + net_namespace
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
 
-            command = 'sudo ip link set tap-' + vlan + ' netns ' + net_namespace
+            command = 'sudo ip netns exec ' + net_namespace + ' ip link set dev tap-' + str(vlan) + ' 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-' + vlan + ' up'
+            command = 'sudo ip link set dev ovs-tap-' + str(vlan) + ' up'
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
 
-            command = 'sudo ip link set dev ovs-tap-' + vlan + ' up'
+            command = 'sudo ip netns exec ' + net_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 \
-                      + ' ' + ip + ' netmask ' + netmask
+                      + ' ' + ip_listen_address + ' netmask ' + netmask
             self.logger.debug("command: " + command)
             (_, stdout, _) = self.ssh_conn.exec_command(command)
             content = stdout.read()
@@ -1345,6 +1359,24 @@ class host_thread(threading.Thread):
         else:
             self.logger.error("qemu_change_backing error: " + content)
             return -1
+
+    def qemu_create_empty_disk(self, dev):
+
+        if not dev and 'source' not in dev and 'file format' not in dev and 'image_size' not in dev:
+            self.logger.error("qemu_create_empty_disk error: missing image parameter")
+            return -1
+
+        empty_disk_path = dev['source file']
+
+        command = 'qemu-img create -f qcow2 ' + empty_disk_path + ' ' + str(dev['image_size']) + 'G'
+        self.logger.debug("command: " + command)
+        (_, _, stderr) = self.ssh_conn.exec_command(command)
+        content = stderr.read()
+        if len(content) == 0:
+            return 0
+        else:
+            self.logger.error("qemu_create_empty_disk error: " + content)
+            return -1
     
     def get_notused_filename(self, proposed_name, suffix=''):
         '''Look for a non existing file_name in the host
@@ -1516,31 +1548,45 @@ class host_thread(threading.Thread):
             devices = [  {"type":"disk", "image_id":server['image_id'], "vpci":server_metadata.get('vpci', None) } ] 
             if 'extended' in server_data and server_data['extended']!=None and "devices" in server_data['extended']:
                 devices += server_data['extended']['devices']
-
+            empty_path = None
             for dev in devices:
-                if dev['image_id'] == None:
+                image_id = dev.get('image_id')
+                if not image_id:
+                    import uuid
+                    uuid_empty = str(uuid.uuid4())
+                    empty_path = self.empty_image_path + uuid_empty + '.qcow2' # local path for empty disk
+
+                    dev['source file'] = empty_path
+                    dev['file format'] = 'qcow2'
+                    self.qemu_create_empty_disk(dev)
+                    server_host_files[uuid_empty] = {'source file': empty_path,
+                                                     'file format': dev['file format']}
+
                     continue
-                
-                self.db_lock.acquire()
-                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']
-                    self.logger.error("launch_server " + error_text)
-                    return -1, error_text
-                if content[0]['metadata'] is not None:
-                    dev['metadata'] = json.loads(content[0]['metadata'])
                 else:
-                    dev['metadata'] = {}
-                
-                if dev['image_id'] in server_host_files:
-                    dev['source file'] = server_host_files[ dev['image_id'] ] ['source file'] #local path
-                    dev['file format'] = server_host_files[ dev['image_id'] ] ['file format'] # raw or qcow2
-                    continue
+                    self.db_lock.acquire()
+                    result, content = self.db.get_table(FROM='images', SELECT=('path', 'metadata'),
+                                                        WHERE={'uuid': image_id})
+                    self.db_lock.release()
+                    if result <= 0:
+                        error_text = "ERROR", result, content, "when getting image", dev['image_id']
+                        self.logger.error("launch_server " + error_text)
+                        return -1, error_text
+                    if content[0]['metadata'] is not None:
+                        dev['metadata'] = json.loads(content[0]['metadata'])
+                    else:
+                        dev['metadata'] = {}
+
+                    if image_id in server_host_files:
+                        dev['source file'] = server_host_files[image_id]['source file'] #local path
+                        dev['file format'] = server_host_files[image_id]['file format'] # raw or qcow2
+                        continue
                 
             #2: copy image to host
-                remote_file = content[0]['path']
+                if image_id:
+                    remote_file = content[0]['path']
+                else:
+                    remote_file = empty_path
                 use_incremental_image = use_incremental
                 if dev['metadata'].get("use_incremental") == "no":
                     use_incremental_image = False
@@ -1605,12 +1651,12 @@ class host_thread(threading.Thread):
                             # VIR_DOMAIN_SHUTOFF = 5
                             # VIR_DOMAIN_CRASHED = 6
                             # VIR_DOMAIN_PMSUSPENDED = 7   #TODO suspended
-    
+
         if self.test or len(self.server_status)==0:
-            return            
-        
+            return
+
         try:
-            conn = host_thread.lvirt_module.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+            conn = host_thread.lvirt_module.open(self.lvirt_conn_uri)
             domains=  conn.listAllDomains() 
             domain_dict={}
             for domain in domains:
@@ -1699,7 +1745,7 @@ class host_thread(threading.Thread):
                 self.create_image(None, req)
         else:
             try:
-                conn = host_thread.lvirt_module.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+                conn = host_thread.lvirt_module.open(self.lvirt_conn_uri)
                 try:
                     dom = conn.lookupByUUIDString(server_id)
                 except host_thread.lvirt_module.libvirtError as e:
@@ -1893,7 +1939,7 @@ class host_thread(threading.Thread):
             return 0, None
         try:
             if not lib_conn:
-                conn = host_thread.lvirt_module.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+                conn = host_thread.lvirt_module.open(self.lvirt_conn_uri)
             else:
                 conn = lib_conn
                 
@@ -1901,7 +1947,8 @@ class host_thread(threading.Thread):
             #TODO.Revise  self.server_forceoff(True)
 
             iface = conn.interfaceLookupByMACString(mac)
-            iface.destroy()
+            if iface.isActive():
+                iface.destroy()
             iface.create()
             self.logger.debug("restore_iface '%s' %s", name, mac)
         except host_thread.lvirt_module.libvirtError as e:
@@ -1989,12 +2036,12 @@ class host_thread(threading.Thread):
             xml.append("<interface type='hostdev' managed='yes'>")
             xml.append("  <mac address='" +port['mac']+ "'/>")
             xml.append("  <source>"+ self.pci2xml(port['pci'])+"\n  </source>")
-            xml.append('</interface>')                
+            xml.append('</interface>')
 
             
             try:
                 conn=None
-                conn = host_thread.lvirt_module.open("qemu+ssh://"+self.user+"@"+self.host+"/system")
+                conn = host_thread.lvirt_module.open(self.lvirt_conn_uri)
                 dom = conn.lookupByUUIDString(port["instance_id"])
                 if old_net:
                     text="\n".join(xml)