Allow specifying ssh key file for compute nodes and network controller 66/1866/5
authortierno <alfonso.tiernosepulveda@telefonica.com>
Wed, 24 May 2017 14:54:33 +0000 (16:54 +0200)
committertierno <alfonso.tiernosepulveda@telefonica.com>
Fri, 26 May 2017 08:05:16 +0000 (10:05 +0200)
Having equal host-list output and host-add descriptor
Ignore console input error when launching as a service
Register network error upon dhcp controller error
fix configure-dhcp-server-UBUNTU16.0.4.sh help error

Change-Id: I30b5af3b25d7f2f86a75a4502af10b5e16fd3202
Signed-off-by: tierno <alfonso.tiernosepulveda@telefonica.com>
charm/openvim/layer-openvim/templates/openvimd.cfg
database_utils/migrate_vim_db.sh
openvimd
osm_openvim/dhcp_thread.py
osm_openvim/host_thread.py
osm_openvim/httpserver.py
osm_openvim/openvimd.cfg
osm_openvim/ovim.py
osm_openvim/vim_db.py
osm_openvim/vim_schema.py
scripts/configure-dhcp-server-UBUNTU16.0.4.sh

index 2a51c4d..06c9bc9 100644 (file)
@@ -62,7 +62,7 @@ db_passwd: {{ db.password() }}
 db_name:   {{ db.database() }}
 
 #host paremeters
-image_path: "/opt/VNF/images"        # Folder, same for every host, where the VNF images will be copied
+host_image_path: "/opt/VNF/images"        # Folder, same for every host, where the VNF images will be copied
 
 #testing parameters (used by ./test/test_openvim.py)
 tenant_id: fc7b43b6-6bfa-11e4-84d2-5254006d6777   # Default tenant identifier for testing
index a56ede0..27a5d92 100755 (executable)
@@ -33,7 +33,7 @@ DBPORT="3306"
 DBNAME="vim_db"
 QUIET_MODE=""
 #TODO update it with the last database version
-LAST_DB_VERSION=18
+LAST_DB_VERSION=19
 
 # Detect paths
 MYSQL=$(which mysql)
@@ -186,7 +186,8 @@ fi
 #[ $OPENVIM_VER_NUM -ge 5009 ] && DATABASE_TARGET_VER_NUM=16  #0.5.9   => 16
 #[ $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
-#TODO ... put next versions here
+#[ $OPENVIM_VER_NUM -ge 5015 ] && DATABASE_TARGET_VER_NUM=19  #0.5.15  => 19
+# TODO ... put next versions here
 
 function upgrade_to_1(){
     # echo "    upgrade database from version 0.0 to version 0.1"
@@ -653,7 +654,8 @@ function upgrade_to_18(){
             "DROP INDEX type_vlan;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
     echo "    Fill 'region' with __OVS__/__DATA__ for OVS/openflow provider at nets"
     echo "UPDATE nets set region='__OVS__' where provider like 'OVS%';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
-    echo "UPDATE nets set region='__DATA__' where type='data' or type='ptp';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "UPDATE nets set region='__DATA__' where type='data' or type='ptp';" | $DBCMD || ! echo "ERROR. Aborted!" ||
+         exit -1
     echo "    Create new index region_vlan at nets"
        echo "ALTER TABLE nets ADD UNIQUE INDEX region_vlan (region, vlan);" \
             | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
@@ -670,6 +672,22 @@ function downgrade_from_18(){
     echo "DELETE FROM schema_version WHERE version_int = '18';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
 }
 
+function upgrade_to_19(){
+    echo "    Add 'keyfile' to 'hosts'"
+    echo "ALTER TABLE hosts ADD COLUMN keyfile VARCHAR(255) NULL DEFAULT NULL AFTER password;" \
+            | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "INSERT INTO schema_version (version_int, version, openvim_ver, comments, date) "\
+            "VALUES (19, '0.19', '0.5.15', 'Add keyfile to hosts', '2017-05-23');"\
+         | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+function downgrade_from_19(){
+    echo "    Delete 'keyfile' from 'hosts'"
+    echo "ALTER TABLE hosts DROP COLUMN keyfile;" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+    echo "DELETE FROM schema_version WHERE version_int = '19';" | $DBCMD || ! echo "ERROR. Aborted!" || exit -1
+}
+
+
 #TODO ... put funtions here
 
 # echo "db version = "${DATABASE_VER_NUM}
index 1b3c7db..6fbcfe2 100755 (executable)
--- a/openvimd
+++ b/openvimd
@@ -55,10 +55,10 @@ class LoadConfigurationException(Exception):
 
 
 def load_configuration(configuration_file):
-    default_tokens ={'http_port':9080, 'http_host':'localhost', 
-                     'of_controller_nets_with_same_vlan':True,
-                     'image_path':'/opt/VNF/images',
-                     'network_vlan_range_start':1000,
+    default_tokens ={'http_port': 9080, 'http_host': 'localhost',
+                     'of_controller_nets_with_same_vlan': True,
+                     'host_ssh_keyfile': None,
+                     'network_vlan_range_start': 1000,
                      'network_vlan_range_end': 4096,
                      'log_level': "DEBUG",
                      'log_level_db': "ERROR",
@@ -69,79 +69,98 @@ def load_configuration(configuration_file):
                      'ovs_controller_file_path': '/var/lib/',
             }
     try:
-        #First load configuration from configuration file
-        #Check config file exists
+        # First load configuration from configuration file
+        # Check config file exists
         if not os.path.isfile(configuration_file):
-            return (False, "Configuration file '"+configuration_file+"' does not exists")
+            raise LoadConfigurationException("Configuration file '{}' does not exists".format(configuration_file))
             
-        #Read and parse file
+        # Read and parse file
         (return_status, code) = af.read_file(configuration_file)
         if not return_status:
-            return (return_status, "Error loading configuration file '"+configuration_file+"': "+code)
-        try:
-            config = yaml.load(code)
-        except yaml.YAMLError, exc:
-            error_pos = ""
-            if hasattr(exc, 'problem_mark'):
-                mark = exc.problem_mark
-                error_pos = " at position: (%s:%s)" % (mark.line+1, mark.column+1)
-            return (False, "Error loading configuration file '"+configuration_file+"'"+error_pos+": content format error: Failed to parse yaml format")
-        
-        
-        try:
-            js_v(config, config_schema)
-        except js_e.ValidationError, exc:
-            error_pos = ""
-            if len(exc.path)>0: error_pos=" at '" + ":".join(map(str, exc.path))+"'"
-            return False, "Error loading configuration file '"+configuration_file+"'"+error_pos+": "+exc.message 
-        
-        
-        #Check default values tokens
-        for k,v in default_tokens.items():
-            if k not in config: config[k]=v
-        #Check vlan ranges
+            raise LoadConfigurationException("Error loading configuration file '{}': {}".format(
+                configuration_file, code))
+        config = yaml.load(code)
+        js_v(config, config_schema)
+        # Check default values tokens
+        for k, v in default_tokens.items():
+            if k not in config:
+                config[k] = v
+        # Check vlan ranges
         if config["network_vlan_range_start"]+10 >= config["network_vlan_range_end"]:
-            return False, "Error invalid network_vlan_range less than 10 elements"
-    
-    except Exception,e:
-        return (False, "Error loading configuration file '"+configuration_file+"': "+str(e))
-    return (True, config)
+            raise LoadConfigurationException("Error at configuration file '{}'. Invalid network_vlan_range less than 10 elements".format)
+        return config
+    except yaml.YAMLError as exc:
+        error_pos = ""
+        if hasattr(exc, 'problem_mark'):
+            mark = exc.problem_mark
+            error_pos = " at position: ({}:{})".format(mark.line + 1, mark.column + 1)
+        raise LoadConfigurationException("Error loading configuration file '{}'{}: {}\n"
+                                         "Use a valid yaml format. Indentation matters, "
+                                         "and tabs characters are not valid".format(
+                                             configuration_file, error_pos, exc))
+    except js_e.ValidationError as exc:
+        error_pos = ""
+        if len(exc.path) > 0:
+            error_pos = " at '{}'".format(":".join(map(str, exc.path)))
+        raise LoadConfigurationException("Error loading configuration file '{}'{}: {}".format(
+            configuration_file, error_pos, exc))
+
+    # except Exception as e:
+    #     raise LoadConfigurationException("Error loading configuration file '{}': {}".format(configuration_file, e))
+
 
 def usage():
-    print "Usage: ", sys.argv[0], "[options]"
-    print "      -v|--version: prints current version"
-    print "      -c|--config FILE: loads the configuration file (default: osm_openvim/openvimd.cfg)"
-    print "      -h|--help: shows this help"
-    print "      -p|--port PORT: changes port number and overrides the port number in the configuration file (default: 908)"
-    print "      -P|--adminport PORT: changes admin port number and overrides the port number in the configuration file (default: not listen)"
-    print "      --dbname NAME: changes db_name and overrides the db_name in the configuration file"
-    #print( "      --log-socket-host HOST: send logs to this host")
-    #print( "      --log-socket-port PORT: send logs using this port (default: 9022)")
-    print( "      --log-file FILE: send logs to this file")
+    print ("Usage: ", sys.argv[0], "[options]")
+    print ("      -v|--version: prints current version")
+    print ("      -c|--config FILE: loads the configuration file (default: osm_openvim/openvimd.cfg)")
+    print ("      -h|--help: shows this help")
+    print ("      -p|--port PORT: changes port number and overrides the port number in the configuration file "
+           "(default: 908)")
+    print ("      -P|--adminport PORT: changes admin port number and overrides the port number in the configuration "
+           "file (default: not listen)")
+    print ("      --dbname NAME: changes db_name and overrides the db_name in the configuration file")
+    # print( "      --log-socket-host HOST: send logs to this host")
+    # print( "      --log-socket-port PORT: send logs using this port (default: 9022)")
+    print ("      --log-file FILE: send logs to this file")
     return
 
 
-if __name__=="__main__":
+def set_logging_file(log_file):
+    try:
+        file_handler = logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
+        file_handler.setFormatter(log_formatter_simple)
+        logger.addHandler(file_handler)
+        # logger.debug("moving logs to '%s'", global_config["log_file"])
+        # remove initial stream handler
+        logging.root.removeHandler(logging.root.handlers[0])
+        print ("logging on '{}'".format(log_file))
+    except IOError as e:
+        raise LoadConfigurationException(
+            "Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, e))
+
+
+if __name__ == "__main__":
     hostname = socket.gethostname()
-    #streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
-    log_formatter_complete = logging.Formatter(
-        '%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s severity:%(levelname)s logger:%(name)s log:%(message)s'.format(host=hostname),
-        datefmt='%Y-%m-%dT%H:%M:%S',
-    )
-    log_format_simple =  "%(asctime)s %(levelname)s  %(name)s %(filename)s:%(lineno)s %(message)s"
+    # streamformat = "%(levelname)s (%(module)s:%(lineno)d) %(message)s"
+    log_formatter_complete = logging.Formatter('%(asctime)s.%(msecs)03d00Z[{host}@openmanod] %(filename)s:%(lineno)s '
+                                               'severity:%(levelname)s logger:%(name)s log:%(message)s'.format(
+                                                    host=hostname),
+                                datefmt='%Y-%m-%dT%H:%M:%S')
+    log_format_simple = "%(asctime)s %(levelname)s  %(name)s %(filename)s:%(lineno)s %(message)s"
     log_formatter_simple = logging.Formatter(log_format_simple, datefmt='%Y-%m-%dT%H:%M:%S')
     logging.basicConfig(format=log_format_simple, level=logging.DEBUG)
     logger = logging.getLogger('openvim')
     logger.setLevel(logging.DEBUG)
     try:
-        opts, args = getopt.getopt(sys.argv[1:], "hvc:p:P:", ["config=", "help", "version", "port=", "adminport=", "log-file=", "dbname="])
-    except getopt.GetoptError, err:
+        opts, args = getopt.getopt(sys.argv[1:], "hvc:p:P:",
+                                   ["config=", "help", "version", "port=", "adminport=", "log-file=", "dbname="])
+    except getopt.GetoptError as err:
         # print help information and exit:
-        logger.error("%s. Type -h for help", err) # will print something like "option -a not recognized"
-        #usage()
-        sys.exit(-2)
+        logger.error("%s. Type -h for help", err)  # will print something like "option -a not recognized"
+        # usage()
+        sys.exit(2)
 
-    port=None
+    port = None
     port_admin = None
     config_file = 'osm_openvim/openvimd.cfg'
     log_file = None
@@ -149,8 +168,8 @@ if __name__=="__main__":
 
     for o, a in opts:
         if o in ("-v", "--version"):
-            print "openvimd version", ovim.ovim.get_version(), ovim.ovim.get_version_date()
-            print "(c) Copyright Telefonica"
+            print ("openvimd version", ovim.ovim.get_version(), ovim.ovim.get_version_date())
+            print ("(c) Copyright Telefonica")
             sys.exit(0)
         elif o in ("-h", "--help"):
             usage()
@@ -168,34 +187,27 @@ if __name__=="__main__":
         else:
             assert False, "Unhandled option"
 
-    
     engine = None
     http_thread = None
     http_thread_admin = None
 
     try:
-        #Load configuration file
-        r, config_dic = load_configuration(config_file)
-        #print config_dic
-        if not r:
-            logger.error(config_dic)
-            config_dic={}
-            exit(-1)
         if log_file:
-            try:
-                file_handler= logging.handlers.RotatingFileHandler(log_file, maxBytes=100e6, backupCount=9, delay=0)
-                file_handler.setFormatter(log_formatter_simple)
-                logger.addHandler(file_handler)
-                #logger.debug("moving logs to '%s'", global_config["log_file"])
-                #remove initial stream handler
-                logging.root.removeHandler(logging.root.handlers[0])
-                print ("logging on '{}'".format(log_file))
-            except IOError as e:
-                raise LoadConfigurationException("Cannot open logging file '{}': {}. Check folder exist and permissions".format(log_file, str(e)) ) 
+            set_logging_file(log_file)
+        # Load configuration file
+        config_dic = load_configuration(config_file)
+        if config_dic.get("dhcp_server"):
+            if config_dic["dhcp_server"].get("key"):
+                config_dic["dhcp_server"]["keyfile"] = config_dic["dhcp_server"].pop("key")
+        if config_dic.get("image_path"):
+            config_dic["host_image_path"] = config_dic.pop("image_path")
+        elif not config_dic.get("host_image_path"):
+            config_dic["host_image_path"] = '/opt/VNF/images'   # default value
+        # print config_dic
 
         logger.setLevel(getattr(logging, config_dic['log_level']))
         logger.critical("Starting openvim server command: '%s'", sys.argv[0])
-        #override parameters obtained by command line
+        # override parameters obtained by command line
         if port: 
             config_dic['http_port'] = port
         if port_admin:
@@ -203,21 +215,24 @@ if __name__=="__main__":
         if db_name: 
             config_dic['db_name'] = db_name
         
-        #check mode
+        # check mode
         if 'mode' not in config_dic:
             config_dic['mode'] = 'normal'
-            #allow backward compatibility of test_mode option
+            # allow backward compatibility of test_mode option
             if 'test_mode' in config_dic and config_dic['test_mode']==True:
                 config_dic['mode'] = 'test' 
         if config_dic['mode'] == 'development' and config_dic['network_type'] == 'bridge' and \
-                ( 'development_bridge' not in config_dic or config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None) ):
-            logger.error("'%s' is not a valid 'development_bridge', not one of the 'bridge_ifaces'", config_file)
-            exit(-1)
+                ('development_bridge' not in config_dic or
+                             config_dic['development_bridge'] not in config_dic.get("bridge_ifaces",None)):
+            error_msg = "'{}' is not a valid 'development_bridge', not one of the 'bridge_ifaces'".format(config_file)
+            print (error_msg)
+            logger.error(error_msg)
+            exit(1)
 
         if config_dic['mode'] != 'normal':
-            print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
-            print "!! Warning, openvimd in TEST mode '%s'" % config_dic['mode']
-            print '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
+            print ('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
+            print ("!! Warning, openvimd in TEST mode '{}'".format(config_dic['mode']))
+            print ('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
         config_dic['version'] = ovim.ovim.get_version()
         config_dic["logger_name"] = "openvim"
 
@@ -225,46 +240,42 @@ if __name__=="__main__":
         engine.start_service()
 
         
-    #Create thread to listen to web requests
-        http_thread = httpserver.httpserver(engine, 'http', config_dic['http_host'], config_dic['http_port'], False, config_dic)
+    # Create thread to listen to web requests
+        http_thread = httpserver.httpserver(engine, 'http', config_dic['http_host'], config_dic['http_port'],
+                                            False, config_dic)
         http_thread.start()
-        
+
         if 'http_admin_port' in config_dic:
             engine2 = ovim.ovim(config_dic)
-            http_thread_admin = httpserver.httpserver(engine2, 'http-admin', config_dic['http_host'], config_dic['http_admin_port'], True)
+            http_thread_admin = httpserver.httpserver(engine2, 'http-admin', config_dic['http_host'],
+                                                      config_dic['http_admin_port'], True)
             http_thread_admin.start()
         else:
             http_thread_admin = None
-        time.sleep(1)      
+        time.sleep(1)
         logger.info('Waiting for http clients')
         print ('openvimd ready')
         print ('====================')
         sys.stdout.flush()
-        
+
         #TODO: Interactive console would be nice here instead of join or sleep
-        
-        r="help" #force print help at the beginning
+
+        r = ""
         while True:
-            if r=='exit':
-                break      
-            elif r!='':
+            if r == 'exit':
+                break
+            elif r != '':
                 print "type 'exit' for terminate"
-            r = raw_input('> ')
+            try:
+                r = raw_input('> ')
+            except EOFError:
+                time.sleep(86400)
 
     except (KeyboardInterrupt, SystemExit):
         pass
-    except SystemExit:
-        pass
-    except getopt.GetoptError as e:
-        logger.critical(str(e)) # will print something like "option -a not recognized"
-        #usage()
-        exit(-1)
-    except LoadConfigurationException as e:
-        logger.critical(str(e))
-        exit(-1)
-    except ovim.ovimException as e:
-        logger.critical(str(e))
-        exit(-1)
+    except (getopt.GetoptError, LoadConfigurationException, ovim.ovimException) as e:
+        logger.critical(str(e))   # will print something like "option -a not recognized"
+        exit(1)
 
     logger.info('Exiting openvimd')
     if engine:
@@ -273,7 +284,5 @@ if __name__=="__main__":
         http_thread.join(1)
     if http_thread_admin:
         http_thread_admin.join(1)
-
-    logger.debug( "bye!")
+    logger.debug("bye!")
     exit()
-
index da7176b..8cf50dd 100644 (file)
@@ -83,7 +83,7 @@ class dhcp_thread(threading.Thread):
             self.ssh_conn.load_system_host_keys()
             self.ssh_conn.connect(self.dhcp_params["host"], port=self.dhcp_params.get("port", 22),
                                   username=self.dhcp_params["user"], password=self.dhcp_params.get("password"),
-                                  key_filename=self.dhcp_params.get("key"), timeout=2)
+                                  key_filename=self.dhcp_params.get("keyfile"), timeout=2)
         except paramiko.ssh_exception.SSHException as e:
             self.logger.error("ssh_connect ssh Exception " + str(e))
         
@@ -222,7 +222,7 @@ class dhcp_thread(threading.Thread):
                     (_, stdout, _) = self.ssh_conn.exec_command(command)
                     content = stdout.read()
                 except paramiko.ssh_exception.SSHException as e:
-                    self.logger.error("get_ip_from_dhcp: ssh_Exception: " + srt(e))
+                    self.logger.error("get_ip_from_dhcp: ssh_Exception: " + str(e))
                     content = None
                     self.ssh_conn = None
                 except Exception as e:
index bf7a6da..1a03c7f 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:
@@ -97,33 +100,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 +144,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)
@@ -1610,12 +1618,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:
@@ -1704,7 +1712,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:
@@ -1898,7 +1906,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
                 
@@ -1995,12 +2003,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)
index c161942..43d11cf 100644 (file)
@@ -55,20 +55,20 @@ global my
 global url_base
 global config_dic
 global RADclass_module
-RADclass=None  #RADclass module is charged only if not in test mode
+RADclass_module=None  #RADclass module is charged only if not in test mode
 
 url_base="/openvim"
 
 HTTP_Bad_Request =          400
-HTTP_Unauthorized =         401 
-HTTP_Not_Found =            404 
+HTTP_Unauthorized =         401
+HTTP_Not_Found =            404
 HTTP_Forbidden =            403
-HTTP_Method_Not_Allowed =   405 
+HTTP_Method_Not_Allowed =   405
 HTTP_Not_Acceptable =       406
 HTTP_Request_Timeout =      408
 HTTP_Conflict =             409
-HTTP_Service_Unavailable =  503 
-HTTP_Internal_Server_Error= 500 
+HTTP_Service_Unavailable =  503
+HTTP_Internal_Server_Error= 500
 
 def md5(fname):
     hash_md5 = hashlib.md5()
@@ -524,6 +524,7 @@ def http_get_host_id(host_id):
 @bottle.route(url_base + '/hosts', method='POST')
 def http_post_hosts():
     '''insert a host into the database. All resources are got and inserted'''
+    global RADclass_module
     my = config_dic['http_threads'][ threading.current_thread().name ]
     #check permissions
     if not my.admin:
@@ -535,17 +536,17 @@ def http_post_hosts():
     if r is not None: print "http_post_host_id: Warning: remove extra items ", r
     change_keys_http2db(http_content['host'], http2db_host)
 
-    host = http_content['host']
-    warning_text=""
-    if 'host-data' in http_content:
-        host.update(http_content['host-data'])
-        ip_name=http_content['host-data']['ip_name']
-        user=http_content['host-data']['user']
-        password=http_content['host-data'].get('password', None)
+    if 'host' in http_content:
+        host = http_content['host']
+        if 'host-data' in http_content:
+            host.update(http_content['host-data'])
     else:
-        ip_name=host['ip_name']
-        user=host['user']
-        password=host.get('password', None)
+        host = http_content['host-data']
+    warning_text = ""
+    ip_name = host['ip_name']
+    user = host['user']
+    password = host.get('password')
+    if host.get('autodiscover'):
         if not RADclass_module:
             try:
                 RADclass_module = imp.find_module("RADclass")
@@ -618,10 +619,13 @@ def http_post_hosts():
             memory=node['memory']['node_size'] / (1024*1024*1024)
             #memory=get_next_2pow(node['memory']['hugepage_nr'])
             host['numas'].append( {'numa_socket': node['id'], 'hugepages': node['memory']['hugepage_nr'], 'memory':memory, 'interfaces': interfaces, 'cores': cores } )
-    print json.dumps(host, indent=4)
-    #return
-    #
-    #insert in data base
+    # print json.dumps(host, indent=4)
+    # insert in data base
+    if "created_at" in host:
+        del host["created_at"]
+    for numa in host.get("numas", ()):
+        if "hugepages_consumed" in numa:
+            del numa["hugepages_consumed"]
     result, content = my.db.new_host(host)
     if result >= 0:
         if content['admin_state_up']:
@@ -629,10 +633,13 @@ def http_post_hosts():
             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
             host_develop_bridge_iface = config_dic.get('development_bridge', None)
-            thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name, 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=content['uuid'],
-                develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface   )
+            thread = ht.host_thread(name=host.get('name',ip_name), user=user, host=ip_name,
+                                    password=host.get('password'),
+                                    keyfile=host.get('keyfile', config_dic["host_ssh_keyfile"]),
+                                    db=config_dic['db'], db_lock=config_dic['db_lock'],
+                                    test=host_test_mode, image_path=config_dic['host_image_path'],
+                                    version=config_dic['version'], host_id=content['uuid'],
+                                    develop_mode=host_develop_mode, develop_bridge_iface=host_develop_bridge_iface)
             thread.start()
             config_dic['host_threads'][ content['uuid'] ] = thread
 
index 732512b..030de87 100644 (file)
@@ -21,8 +21,8 @@
 
 
 
-#Miscellaneous
-#Option to test openvim without the needed infrastructure, possible values are
+# Miscellaneous
+# Option to test openvim without the needed infrastructure, possible values are
 #    "normal"      by default, Openflow controller (OFC), switch and real host are needed
 #    "test"        Used for testing http API and database without connecting to host or to OFC
 #    "host only"   Used when neither OFC nor OF switch are provided. 
 #                  The same 'development_bridge' (see below) is used for all dataplane networks
 mode: test
 
-#Openflow controller information
+
+# Default openflow controller information
 of_controller:      floodlight                   # Type of controller to be used.
                                                  # Valid controllers are 'opendaylight', 'floodlight' or <custom>
-#of_controller_module: module                    # Only needed for <custom>.  Python module that implement
+# of_controller_module: module                     # Only needed for <custom>.  Python module that implement
                                                  # this controller. By default a file with the name  <custom>.py is used 
-#of_<other>:           value                     # Other parameters required by <custom> controller. Consumed by __init__
+# of_<other>:           value                      # Other parameters required by <custom> controller. Consumed by __init__
 of_user:            user credentials             # User credentials for the controller if needed
 of_password:        passwd credentials           # Password credentials for the controller if needed
 of_controller_ip:   127.0.0.1                    # IP address where the Openflow controller is listening
 of_controller_port: 7070                         # TCP port where the Openflow controller is listening (REST API server)
 of_controller_dpid: '00:01:02:03:04:05:06:07'    # Openflow Switch identifier (put here the right number)
-
-#This option is used for those openflow switch that cannot deliver one packet to several output with different vlan tags
-#When set to true, it fails when trying to attach different vlan tagged ports to the same net
+# This option is used for those openflow switch that cannot deliver one packet to several output with different vlan tags
+# When set to true, it fails when trying to attach different vlan tagged ports to the same net
 of_controller_nets_with_same_vlan: false         # (by default, true)
 
-#Server parameters
+
+# Server parameters
 http_host:       0.0.0.0             # IP address where openvim is listening (by default, localhost)
 http_port:       9080                # General port where openvim is listening (by default, 9080)
 http_admin_port: 9085                # Admin port where openvim is listening (when missing, no administration server is launched)
 
-#database parameters
-db_host:   localhost                   # by default localhost
+# Database parameters
+db_host:   localhost                 # by default localhost
 db_user:   vim                       # DB user
 db_passwd: vimpw                     # DB password
 db_name:   vim_db                    # Name of the VIM DB
 
-#host paremeters
-image_path: "/opt/VNF/images"        # Folder, same for every host, where the VNF images will be copied
 
-#testing parameters (used by ./test/test_openvim.py)
+# Common compute node parameters
+host_image_path:  /opt/VNF/images        # Folder, same for every host, where the VNF images will be copied
+# host_ssh_keyfile: /path/to/ssh-key-file  # Default ssh_kye to use for connecting to compute nodes
+
+
+# Deprecated: testing parameters (used by ./test/test_openvim.py)
 tenant_id: fc7b43b6-6bfa-11e4-84d2-5254006d6777   # Default tenant identifier for testing
 
-#VLAN ranges used for the dataplane networks (ptp, data)
-#When a network is created an unused value in this range is used
+
+# Underlay network: VLAN ranges used for underlay dataplane networks
+# When a network is created an unused value in this range is used
 network_vlan_range_start: 3000
 network_vlan_range_end:   4000
 
+
 # Overlay network implementation. Options are:
 # - ovs :   (by default) Use a vlxan mesh between computes to handle the network overlay.
 # - bridge: Use pre-populated linux bridges with L2 conectivity at compte nodes.
 network_type : ovs
-ovs_controller_ip   :   localhost                   # dhcp controller IP address, must be change in order to
-ovs_controller_user :   "osm_dhcp"                  # User for the dchp controller for OVS networks
-ovs_controller_file_path  :   "/var/lib/openvim"    # Path for dhcp daemon configuration, by default '/var/lib/openvim'
+ovs_controller_ip:        localhost               # IP address of the controller OVS network host
+ovs_controller_user:      osm_user                # User for controller OVS network host
+# ovs_controller_password: osm_passwd               # password for controller OVS network host
+# ovs_controller_keyfile:   /path/to/ssh-key-file   # ssh-access-key file to connect host
+ovs_controller_file_path: /var/lib/openvim        # Path for dhcp daemon configuration, by default '/var/lib/openvim'
 
 
-#host bridge interfaces for networks
-# Apply only for 'network_type: bridge'
+# Host bridge interfaces for networks. It applies only for 'network_type: bridge'
 # Indicates the bridges at compute nodes to be used for the overlay networks
 # Bridge networks need to be pre-provisioned on each host and Openvim uses those pre-provisioned bridge networks.
 # Openvim assumes that the following bridge interfaces have been created on each host, appropriately associated to a physical port.
@@ -105,34 +112,34 @@ ovs_controller_file_path  :   "/var/lib/openvim"    # Path for dhcp daemon confi
 #   virbrMan9:  [2009, 1]
 #   virbrMan10: [2010, 1]
 
-#Used only when 'mode' is at development'. Indicates which 'bridge_ifaces' is used for dataplane networks
+# Used only when 'mode' is at development'. Indicates which 'bridge_ifaces' is used for dataplane networks
 #development_bridge: virbrMan10
 
-#DHCP SERVER PARAMETERS. 
-#In case some of the previous 'bridge_ifaces' are connected to an EXTERNAL dhcp server, provide 
+# DHCP SERVER PARAMETERS.
+# In case some of the previous 'bridge_ifaces' are connected to an EXTERNAL dhcp server, provide
 #   the server parameters to allow openvim getting the allocated IP addresses of virtual machines
 #   connected to the indicated 'bridge_ifaces' and or 'nets'. Openvim will connect to the dhcp server by ssh.
-#DHCP server must contain a shell script "get_dhcp_lease.sh" included in the path, that accepts a mac address as 
+# DHCP server must contain a shell script "get_dhcp_lease.sh" included in the path, that accepts a mac address as
 #   parameter and return empty or the allocated IP address. See an example at the end of the file 
 #   ./openvim/dhcp_thread.py 
-#COMMENT all lines in case you do not have a DHCP server in 'normal', 'development'  or 'host only' modes.
+# COMMENT all lines in case you do not have a DHCP server in 'normal', 'development'  or 'host only' modes.
 #   For 'test' or 'OF only' modes you can leave then uncommented, because in these modes fake IP 
 #   address are generated instead of connecting with a real DHCP server.
-dhcp_server:
-   host:     host-ip-or-name  
-   #port:     22               #ssh port, by default 22
-   provider: isc-dhcp-server  #dhcp-server type
-   user:     user
-   #provide password, or key if needed
-   password: passwd           
-   #key:     ssh-access-key
-   #list of the previous bridge interfaces attached to this dhcp server
-   bridge_ifaces:   [ virbrMan1, virbrMan2 ] 
-   #list of the networks attached to this dhcp server
-   nets: [default]
-
-
-#logging parameters       # DEBUG, INFO, WARNING, ERROR, CRITICAL
+#dhcp_server:
+#   host:     host-ip-or-name
+#   #port:     22               #ssh port, by default 22
+#   provider: isc-dhcp-server  #dhcp-server type
+#   user:     user
+#   #provide password, or key if needed
+#   password: passwd
+#   #keyfile:     ssh-access-key
+#   #list of the previous bridge interfaces attached to this dhcp server
+#   bridge_ifaces:   [ virbrMan1, virbrMan2 ]
+#   #list of the networks attached to this dhcp server
+#   nets: [default]
+
+
+# Logging parameters       # DEBUG, INFO, WARNING, ERROR, CRITICAL
 log_level:       DEBUG
 log_level_db:    DEBUG
 log_level_of:    DEBUG
index 3131e8a..dd5b315 100755 (executable)
@@ -42,9 +42,9 @@ import openflow_conn
 
 __author__ = "Alfonso Tierno, Leonardo Mirabal"
 __date__ = "$06-Feb-2017 12:07:15$"
-__version__ = "0.5.14-r530"
+__version__ = "0.5.15-r531"
 version_date = "May 2017"
-database_version = 18      #needed database schema version
+database_version = 19      #needed database schema version
 
 HTTP_Bad_Request =          400
 HTTP_Unauthorized =         401
@@ -244,16 +244,18 @@ class ovim():
         host_develop_bridge_iface = self.config.get('development_bridge', None)
 
         # get host list from data base before starting threads
-        r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid'), FROM='hosts', WHERE={'status': 'ok'})
+        r, hosts = self.db.get_table(SELECT=('name', 'ip_name', 'user', 'uuid', 'password', 'keyfile'),
+                                     FROM='hosts', WHERE={'status': 'ok'})
         if r < 0:
             raise ovimException("Cannot get hosts from database {}".format(hosts))
 
         self.config['host_threads'] = {}
         for host in hosts:
-            host['image_path'] = '/opt/VNF/images/openvim'
             thread = ht.host_thread(name=host['name'], user=host['user'], host=host['ip_name'], db=self.config["db"],
+                                    password=host['password'],
+                                    keyfile=host.get('keyfile', self.config["host_ssh_keyfile"]),
                                     db_lock=self.config["db_lock"], test=host_test_mode,
-                                    image_path=self.config['image_path'],
+                                    image_path=self.config['host_image_path'],
                                     version=self.config['version'], host_id=host['uuid'],
                                     develop_mode=host_develop_mode,
                                     develop_bridge_iface=host_develop_bridge_iface,
@@ -270,13 +272,20 @@ class ovim():
 
         for net in content:
             net_type = net['type']
-            if (net_type == 'bridge_data' or net_type == 'bridge_man') \
-                    and net["provider"][:4] == 'OVS:' and net["enable_dhcp"] == "true":
+            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'])
+                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))
+                    self.db.update_rows("nets", {"status": "ERROR",
+                                                 "last_error": "Fail at launching dhcp server: " + str(e)},
+                                        {"uuid": net["uuid"]})
 
     def _start_of_db_tasks(self):
         """
@@ -432,9 +441,13 @@ class ovim():
         if 'dhcp_thread' in self.config:
             threads['dhcp'] = (self.config['dhcp_thread'])
 
-        for thread in threads.values():
+        for thread_id, thread in threads.items():
+            if thread_id == 'openvim_controller':
+                continue
             thread.insert_task("exit")
-        for thread in threads.values():
+        for thread_id, thread in threads.items():
+            if thread_id == 'openvim_controller':
+                continue
             thread.join()
 
     def get_networks(self, columns=None, db_filter={}, limit=None):
@@ -1346,19 +1359,21 @@ class ovim():
 
         bridge_ifaces = []
         controller_ip = self.config['ovs_controller_ip']
-        ovs_controller_user = self.config['ovs_controller_user']
+        ovs_controller_user = self.config.get('ovs_controller_user')
 
         host_test_mode = True if self.config['mode'] == 'test' or self.config['mode'] == "OF only" else False
         host_develop_mode = True if self.config['mode'] == 'development' else False
 
         dhcp_host = ht.host_thread(name='openvim_controller', user=ovs_controller_user, host=controller_ip,
+                                   password=self.config.get('ovs_controller_password'),
+                                   keyfile=self.config.get('ovs_controller_keyfile'),
                                    db=self.config["db"], db_lock=self.config["db_lock"], test=host_test_mode,
-                                   image_path=self.config['image_path'], version=self.config['version'],
+                                   image_path=self.config['host_image_path'], version=self.config['version'],
                                    host_id='openvim_controller', develop_mode=host_develop_mode,
                                    develop_bridge_iface=bridge_ifaces,
                                    logger_name=self.logger_name + ".host.controller",
                                    debug=self.config.get('log_level_host'))
-        dhcp_host.start()
+        dhcp_host.start()
         self.config['host_threads']['openvim_controller'] = dhcp_host
         if not host_test_mode:
             dhcp_host.ssh_connect()
index 5d7fb16..31d4a0f 100644 (file)
@@ -472,16 +472,19 @@ class vim_db():
                 with self.con:
                     self.cur = self.con.cursor(mdb.cursors.DictCursor)
                     #get HOST
-                    cmd = "SELECT uuid, user, name, ip_name, description, ranking, admin_state_up, DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s') as created_at \
-                        FROM hosts WHERE " + where_filter
+                    cmd = "SELECT uuid, user, password, keyfile, name, ip_name, description, ranking, admin_state_up, "\
+                          "DATE_FORMAT(created_at,'%Y-%m-%dT%H:%i:%s') as created_at "\
+                          "FROM hosts WHERE " + where_filter
                     self.logger.debug(cmd) 
                     self.cur.execute(cmd)
-                    if self.cur.rowcount == 0 : 
+                    if self.cur.rowcount == 0:
                         return 0, "host '" + str(host_id) +"'not found."
                     elif self.cur.rowcount > 1 : 
                         return 0, "host '" + str(host_id) +"' matches more than one result."
                     host = self.cur.fetchone()
                     host_id = host['uuid']
+                    if host.get("password"):
+                        host["password"] = "*****"
                     #get numa
                     cmd = "SELECT id, numa_socket, hugepages, memory, admin_state_up FROM numas WHERE host_id = '" + str(host_id) + "'"
                     self.logger.debug(cmd)
@@ -504,20 +507,20 @@ class vim_db():
                         used = self.cur.fetchone()
                         used_= int(used['hugepages_consumed']) if used != None else 0
                         numa['hugepages_consumed'] = used_
-                        #get ports
-                        #cmd = "CALL GetPortsFromNuma(%s)'" % str(numa['id'])
-                        #self.cur.callproc('GetPortsFromNuma', (numa['id'],) )
-                        #every time a Procedure is launched you need to close and open the cursor 
-                        #under Error 2014: Commands out of sync; you can't run this command now
-                        #self.cur.close()   
-                        #self.cur = self.con.cursor(mdb.cursors.DictCursor)
-                        cmd="SELECT Mbps, pci, status, Mbps_used, instance_id, if(id=root_id,'PF','VF') as type_,\
-                             switch_port, switch_dpid, mac, source_name\
-                             FROM resources_port WHERE numa_id=%d ORDER BY root_id, type_ DESC" %  (numa['id'])
+                        # get ports
+                        # cmd = "CALL GetPortsFromNuma(%s)'" % str(numa['id'])
+                        # self.cur.callproc('GetPortsFromNuma', (numa['id'],) )
+                        # every time a Procedure is launched you need to close and open the cursor
+                        # under Error 2014: Commands out of sync; you can't run this command now
+                        # self.cur.close()
+                        # self.cur = self.con.cursor(mdb.cursors.DictCursor)
+                        cmd = "SELECT Mbps, pci, status, Mbps_used, instance_id, if(id=root_id,'PF','VF') as type_, "\
+                              "switch_port, switch_dpid, switch_mac, mac, source_name "\
+                              "FROM resources_port WHERE numa_id={} ORDER BY root_id, type_ DESC".format(numa['id'])
                         self.logger.debug(cmd)
                         self.cur.execute(cmd)
                         ifaces = self.cur.fetchall()
-                        #The SQL query will ensure to have SRIOV interfaces from a port first
+                        # The SQL query will ensure to have SRIOV interfaces from a port first
                         sriovs=[]
                         Mpbs_consumed = 0
                         numa['interfaces'] = []
@@ -533,6 +536,8 @@ class vim_db():
                                     del iface["switch_dpid"]
                                 if not iface["switch_port"]:
                                     del iface["switch_port"]
+                                if not iface["switch_mac"]:
+                                    del iface["switch_mac"]
                                 if sriovs:
                                     iface["sriovs"] = sriovs
                                 if Mpbs_consumed:
@@ -544,6 +549,7 @@ class vim_db():
                             else: #VF, SRIOV
                                 del iface["switch_port"]
                                 del iface["switch_dpid"]
+                                del iface["switch_mac"]
                                 del iface["type_"]
                                 del iface["Mbps"]
                                 sriovs.append(iface)
index 69d6a0c..c48d48e 100644 (file)
@@ -31,7 +31,7 @@ __date__ ="$10-jul-2014 12:07:15$"
 # SCHEMAS to validate input data
 #
 
-path_schema={"type":"string", "pattern":"^(\.){0,2}(/[^/\"':{}\(\)]+)+$"}
+path_schema = {"type": "string", "maxLength": 255, "pattern": "^(\.){0,2}(/[^/\"':{}\(\)]+)+$"}
 http_schema={"type":"string", "pattern":"^https?://[^'\"=]+$"}
 port_schema={"type":"integer","minimum":1,"maximun":65534}
 ip_schema={"type":"string","pattern":"^([0-9]{1,3}.){3}[0-9]{1,3}$"}
@@ -55,9 +55,9 @@ yes_no_schema={"type":"string", "enum":["yes", "no"]}
 log_level_schema={"type":"string", "enum":["DEBUG", "INFO", "WARNING","ERROR","CRITICAL"]}
 
 config_schema = {
-    "title":"main configuration information schema",
+    "title": "main configuration information schema",
     "$schema": "http://json-schema.org/draft-04/schema#",
-    "type":"object",
+    "type": "object",
     "properties":{
         "http_port": port_schema,
         "http_admin_port": port_schema,
@@ -65,7 +65,7 @@ config_schema = {
         "http_url_prefix": path_schema, # it does not work yet; it's supposed to be the base path to be used by bottle, but it must be explicitly declared
         "db_host": nameshort_schema,
         "db_user": nameshort_schema,
-        "db_passwd": {"type":"string"},
+        "db_passwd": {"type": "string"},
         "db_name": nameshort_schema,
         "of_controller_ip": ip_schema,
         "of_controller_port": port_schema,
@@ -75,18 +75,20 @@ config_schema = {
         "of_controller_module": {"type":"string"},
         "of_user": nameshort_schema,
         "of_password": nameshort_schema,
-        "test_mode": {"type": "boolean"}, #leave for backward compatibility
+        "test_mode": {"type": "boolean"}, # leave for backward compatibility
         "mode": {"type":"string", "enum":["normal", "host only", "OF only", "development", "test"] },
         "development_bridge": {"type":"string"},
         "tenant_id": {"type" : "string"},
-        "image_path": path_schema,
+        "image_path": path_schema,      # leave for backward compatibility
+        "host_image_path": path_schema,
+        "host_ssh_keyfile": path_schema,
         "network_vlan_range_start": vlan_schema,
         "network_vlan_range_end": vlan_schema,
         "bridge_ifaces": {
             "type": "object",
             "patternProperties": {
                 "." : {
-                    "type": "array", 
+                    "type": "array",
                     "items": integer0_schema,
                     "minItems":2,
                     "maxItems":2,
@@ -97,18 +99,19 @@ config_schema = {
         "dhcp_server": {
             "type": "object",
             "properties": {
-                "host" : name_schema,
-                "port" : port_schema,
-                "provider" : {"type": "string", "enum": ["isc-dhcp-server"]},
-                "user" : nameshort_schema,
-                "password" : {"type": "string"},
-                "key" : {"type": "string"},
-                "bridge_ifaces" :{
-                    "type": "array", 
+                "host": name_schema,
+                "port": port_schema,
+                "provider": {"type": "string", "enum": ["isc-dhcp-server"]},
+                "user": nameshort_schema,
+                "password": {"type": "string"},
+                "key": path_schema,         # for backward compatibility, use keyfile instead
+                "keyfile": path_schema,
+                "bridge_ifaces": {
+                    "type": "array",
                     "items": nameshort_schema,
                 },
-                "nets" :{
-                    "type": "array", 
+                "nets"{
+                    "type": "array",
                     "items": name_schema,
                 },
             },
@@ -119,9 +122,10 @@ config_schema = {
         "log_level_of": log_level_schema,
         "network_type": {"type": "string", "enum": ["ovs", "bridge"]},
         "ovs_controller_file_path": path_schema,
+        "ovs_controller_ip": nameshort_schema,
         "ovs_controller_user": nameshort_schema,
-
-        "ovs_controller_ip": nameshort_schema
+        "ovs_controller_password": {"type": "string"},
+        "ovs_controller_keyfile": path_schema,
     },
     "patternProperties": {
         "of_*" : {"type": ["string", "integer", "boolean"]}
@@ -249,90 +253,97 @@ extended_schema={
 host_data_schema={
     "title":"hosts manual insertion information schema",
     "type":"object", 
-    "properties":{                  
-        "ip_name":nameshort_schema,
+    "properties":{
+        "id": id_schema,
+        "admin_state_up": {"type": "boolean"},
+        "created_at": {"type": "string"},        # ignored, just for compatibility with host-list
+        "ip_name": nameshort_schema,
         "name": name_schema,
-        "description":description_schema,
-        "user":nameshort_schema,
-        "password":nameshort_schema,
-        "features":description_schema,
-        "ranking":integer0_schema,
-        "devices":{
+        "description": description_schema,
+        "user": nameshort_schema,
+        "password": nameshort_schema,
+        "keyfile": path_schema,
+        "features": description_schema,
+        "ranking": integer0_schema,
+        "autodiscover": {"type": "boolean"},    # try to discover host parameters instead of providing in this schema
+        "devices": {
             "type": "array", 
-            "items":{
+            "items": {
                 "type": "object",
-                "properties":{
-                    "type":{"type":"string", "enum":["usb","disk"]},
-                    "vpci":pci_schema
+                "properties": {
+                    "type": {"type": "string", "enum": ["usb", "disk"]},
+                    "vpci": pci_schema
                 },
                 "additionalProperties": False,
                 "required": ["type"]
             }
         },
-        "numas":{
+        "numas": {
             "type": "array",
-            "minItems":1,
-            "items":{
+            "minItems": 1,
+            "items": {
                 "type": "object",
-                "properties":{
-                    "admin_state_up":{"type":"boolean"},
-                    "hugepages":integer0_schema,
+                "properties": {
+                    "admin_state_up": {"type": "boolean"},
+                    "hugepages": integer0_schema,
+                    "hugepages_consumed": integer0_schema,  # ignored, just for compatibility with host-list
+                    "numa_socket": integer0_schema,
+                    "memory": integer1_schema,
                     "cores":{
                         "type": "array",
-                        "minItems":2,
-                        "items":{
+                        "minItems": 2,
+                        "items": {
                             "type": "object",
-                            "properties":{
-                                "core_id":integer0_schema,
-                                "thread_id":integer0_schema,
-                                "status": {"type":"string", "enum":["noteligible"]}
+                            "properties": {
+                                "core_id": integer0_schema,
+                                "thread_id": integer0_schema,
+                                "status": {"type": "string", "enum": ["noteligible"]}
                             },
                             "additionalProperties": False,
-                            "required": ["core_id","thread_id"]
+                            "required": ["core_id", "thread_id"]
                         }
                     },
-                    "interfaces":{
+                    "interfaces": {
                         "type": "array",
-                        "minItems":1,
-                        "items":{
+                        "minItems": 1,
+                        "items": {
                             "type": "object",
-                            "properties":{
-                                "source_name":nameshort_schema,
-                                "mac":mac_schema,
-                                "Mbps":integer0_schema,
-                                "pci":pci_schema,
-                                "sriovs":{
+                            "properties": {
+                                "source_name": nameshort_schema,
+                                "mac": mac_schema,
+                                "Mbps": integer0_schema,
+                                "pci": pci_schema,
+                                "sriovs": {
                                     "type": "array",
                                     "minItems":1,
-                                    "items":{
+                                    "items": {
                                         "type": "object",
-                                        "properties":{
-                                            "source_name":{"oneOf":[integer0_schema, nameshort_schema]},
-                                            "mac":mac_schema,
-                                            "vlan":integer0_schema, 
-                                            "pci":pci_schema,
+                                        "properties": {
+                                            "source_name": {"oneOf": [integer0_schema, nameshort_schema]},
+                                            "mac": mac_schema,
+                                            "vlan": integer0_schema,  # ignored, just for backward compatibility
+                                            "pci": pci_schema,
                                         },
                                         "additionalProperties": False,
-                                        "required": ["source_name","mac","pci"]
+                                        "required": ["source_name", "mac", "pci"]
                                     }
                                 },
                                 "switch_port": nameshort_schema,
                                 "switch_dpid": nameshort_schema,
+                                "switch_mac": mac_schema,
                             },
                             "additionalProperties": False,
-                            "required": ["source_name","mac","Mbps","pci"]
+                            "required": ["source_name", "mac", "Mbps", "pci"]
                         }
                     },
-                    "numa_socket":integer0_schema,
-                    "memory":integer1_schema
                 },
                 "additionalProperties": False,
-                "required": ["cores","numa_socket"]
+                "required": ["cores", "numa_socket"]
             }
         }
     },
     "additionalProperties": False,
-    "required": ["ranking", "numas","ip_name","user"]
+    "required": ["name", "ip_name"]
 }
 
 host_edit_schema={
@@ -388,19 +399,7 @@ host_new_schema = {
     "$schema": "http://json-schema.org/draft-04/schema#",
     "type":"object",
     "properties":{
-        "host":{
-            "type":"object",
-            "properties":{
-                "id":id_schema,
-                "ip_name":nameshort_schema,
-                "name": name_schema,
-                "description":description_schema,
-                "user":nameshort_schema,
-                "password":nameshort_schema,
-                "admin_state_up":{"type":"boolean"},
-            },
-            "required": ["name","ip_name","user"]
-        },
+        "host": host_data_schema,
         "host-data":host_data_schema
     },
     "required": ["host"],
index 9eb675e..15a9913 100755 (executable)
@@ -113,7 +113,7 @@ shift $((OPTIND-1))
 
 if [ $# -lt 1 ]
 then
-  usage
+  _usage
   exit
 fi