Validate input network params

 * Validate: ipaddr,netmask,gw,ns

 * Autoident

Change-Id: I729457a7eb11d4ad454360149de3c1c61701f051
Prod-related: PROD-29171 (PROD:29171)
diff --git a/config-drive/create_config_drive.py b/config-drive/create_config_drive.py
index 4a0671e..b0a5558 100755
--- a/config-drive/create_config_drive.py
+++ b/config-drive/create_config_drive.py
@@ -8,9 +8,9 @@
 #
 # This script uses OpenStack Metadata Service Network format for configuring networking.
 #
-__author__    = "Dzmitry Stremkouski"
+__author__ = "Dzmitry Stremkouski"
 __copyright__ = "Copyright 2019, Mirantis Inc."
-__license__   = "Apache 2.0"
+__license__ = "Apache 2.0"
 
 import argparse
 from crypt import crypt
@@ -20,7 +20,7 @@
 from subprocess import call as run
 from sys import argv, exit
 from uuid import uuid1 as uuidgen
-from yaml import safe_load
+import ipaddress
 
 
 def crash_with_error(msg, exit_code=1):
@@ -34,9 +34,9 @@
 
 
 def calculate_hostnames(name, hostname):
-
     if len(name.split('.')) > 1:
-        crash_with_error("instance name should be in short format without domain")
+        crash_with_error(
+            "instance name should be in short format without domain")
     else:
         if len(hostname.split('.')) == 1:
             if not name == uuid:
@@ -53,17 +53,46 @@
 def validate_args(args):
     if not args.user_data:
         if args.cloud_user_name or args.cloud_user_pass:
-            crash_with_error("You have not specified user-data file path, but require cloud-user setup, which requires it.")
+            crash_with_error(
+                "You have not specified user-data file path, but require"
+                "cloud-user setup, which requires it.")
 
     if not args.skip_network and not args.network_data:
+        try:
+            ipaddress.ip_address(args.ip)
+        except Exception:
+            raise Exception(
+                "Unable to parse ip addr:{}".format(args.ip))
+        try:
+            ipaddress.ip_network(args.netmask)
+        except Exception:
+            raise Exception(
+                "Unable to parse netmask:{}".format(args.netmask))
+        if args.gateway:
+            try:
+                ipaddress.ip_address(args.gateway_ip)
+            except Exception:
+                raise Exception(
+                    "Unable to parse gateway IP:{}".format(args.netmask))
+        if args.dns_nameservers:
+            for ns in args.dns_nameservers.split(','):
+                try:
+                    ipaddress.ip_address(ns)
+                except Exception:
+                    raise Exception(
+                        "Unable to parse nameserver IP:{}".format(ns))
         if not args.ip or not args.netmask or not args.interface:
-            crash_with_error("You have not specified neither ip nor netmask nor interface nor network_data.json file.\nEither skip network configuration or provide network_data.json file path.")
+            crash_with_error(
+                "You have not specified neither ip nor netmask nor interface "
+                "nor network_data.json file. Either skip network configuration "
+                "or provide network_data.json file path.")
 
     if args.skip_network and args.network_data:
-        crash_with_error("--skip-network and --network-data are mutually exclusive.")
+        crash_with_error(
+            "--skip-network and --network-data are mutually exclusive.")
 
 
-def generate_iso(cfg_file_path, cfg_dir_path, quiet = ''):
+def generate_iso(cfg_file_path, cfg_dir_path, quiet=''):
     xprint("Generating config drive image: %s" % cfg_file_path)
     cmd = ["mkisofs", "-r", "-J", "-V", "config-2", "-input-charset", "utf-8"]
     if quiet:
@@ -97,12 +126,8 @@
     makedirs(mcp_dir_path)
     makedirs(meta_dir_path)
 
-    meta_data = {}
-    meta_data["uuid"] = uuid
-    meta_data["hostname"] = hostname
-    meta_data["name"] = name
+    meta_data = {"uuid": uuid, "hostname": hostname, "name": name}
     network_data = {}
-    gateway_ip = ""
 
     ssh_keys = []
 
@@ -111,7 +136,8 @@
         ssh_keys.append(args.ssh_key)
 
     if args.ssh_keys:
-        xprint("Adding authorized keys file entries to config drive: %s" % str(args.ssh_keys))
+        xprint("Adding authorized keys file entries to config drive: %s" % str(
+            args.ssh_keys))
         with open(args.ssh_keys, 'r') as ssh_keys_file:
             ssh_keys += ssh_keys_file.readlines()
         ssh_keys = [x.strip() for x in ssh_keys]
@@ -130,11 +156,13 @@
         copytree(args.model, model_path)
 
     if args.pipeline_library:
-        xprint("Adding pipeline-library to config drive: %s" % str(args.pipeline_library))
+        xprint("Adding pipeline-library to config drive: %s" % str(
+            args.pipeline_library))
         copytree(args.pipeline_library, pipeline_lib_path)
 
     if args.mk_pipelines:
-        xprint("Adding mk-pipelines to config drive: %s" % str(args.mk_pipelines))
+        xprint(
+            "Adding mk-pipelines to config drive: %s" % str(args.mk_pipelines))
         copytree(args.mk_pipelines, mk_pipelines_path)
 
     if args.gpg_key:
@@ -143,14 +171,16 @@
         copyfile(args.gpg_key, gpg_file_path + '/salt_master_pillar.asc')
 
     if args.vendor_data:
-        xprint("Adding vendor metadata file to config drive: %s" % str(args.vendor_data))
+        xprint("Adding vendor metadata file to config drive: %s" % str(
+            args.vendor_data))
         copyfile(args.vendor_data, vendor_file_path)
 
     with open(meta_file_path, 'w') as meta_file:
         json_dump(meta_data, meta_file)
 
     if args.user_data:
-        xprint("Adding user data file to config drive: %s" % str(args.user_data))
+        xprint(
+            "Adding user data file to config drive: %s" % str(args.user_data))
         if username:
             with open(user_file_path, 'a') as user_file:
                 users_data = "#cloud-config\n"
@@ -160,7 +190,8 @@
                 users_data += "    groups: admin\n"
                 users_data += "    lock_passwd: false\n"
                 if userpass:
-                    users_data += "    passwd: %s\n" % str(crypt(userpass, '$6$'))
+                    users_data += "    passwd: %s\n" % str(
+                        crypt(userpass, '$6$'))
                 if ssh_keys:
                     users_data += "    ssh_authorized_keys:\n"
                     for ssh_key in ssh_keys:
@@ -173,22 +204,30 @@
             copyfile(args.user_data, user_file_path)
 
     if args.network_data:
-        xprint("Adding network metadata file to config drive: %s" % str(args.network_data))
+        xprint("Adding network metadata file to config drive: %s" % str(
+            args.network_data))
         copyfile(args.network_data, net_file_path)
     else:
         if not args.skip_network:
             xprint("Configuring network metadata from specified parameters.")
             network_data["links"] = []
             network_data["networks"] = []
-            network_data["links"].append({"type": "phy", "id": args.interface, "name": args.interface})
-            network_data["networks"].append({"type": "ipv4", "netmask": args.netmask, "link": args.interface, "id": "private-ipv4", "ip_address": args.ip})
+            network_data["links"].append(
+                {"type": "phy", "id": args.interface, "name": args.interface})
+            network_data["networks"].append(
+                {"type": "ipv4", "netmask": args.netmask,
+                 "link": args.interface, "id": "private-ipv4",
+                 "ip_address": args.ip})
             if args.dns_nameservers:
                 network_data["services"] = []
                 for nameserver in args.dns_nameservers.split(','):
-                    network_data["services"].append({"type": "dns", "address": nameserver})
+                    network_data["services"].append(
+                        {"type": "dns", "address": nameserver})
             if args.gateway:
                 network_data["networks"][0]["routes"] = []
-                network_data["networks"][0]["routes"].append({"netmask": "0.0.0.0", "gateway": args.gateway, "network": "0.0.0.0"})
+                network_data["networks"][0]["routes"].append(
+                    {"netmask": "0.0.0.0", "gateway": args.gateway,
+                     "network": "0.0.0.0"})
 
     # Check if network metadata is not skipped
     if len(network_data) > 0:
@@ -203,28 +242,67 @@
 
 if __name__ == '__main__':
     uuid = str(uuidgen())
-    parser = argparse.ArgumentParser(description='Config drive generator for MCP instances.', prog=argv[0], usage='%(prog)s [options]')
-    parser.add_argument('--gpg-key', type=str, help='Upload gpg key for salt master. Specify path to file in asc format.', required=False)
-    parser.add_argument('--name', type=str, default=uuid, help='Specify instance name. Hostname in short format, without domain.', required=False)
-    parser.add_argument('--hostname',  type=str, default=uuid, help='Specify instance hostname. FQDN. Hostname in full format with domain. Shortname would be trated as name.', required=False)
-    parser.add_argument('--skip-network', action='store_true', help='Do not generate network_data for the instance.', required=False)
-    parser.add_argument('--interface', type=str, default='ens3', help='Specify interface for instance to configure.', required=False)
-    parser.add_argument('--ssh-key', type=str, help='Specify ssh public key to upload to cloud image.', required=False)
-    parser.add_argument('--ssh-keys', type=str, help='Upload authorized_keys to cloud image. Specify path to file in authorized_keys format.', required=False)
-    parser.add_argument('--cloud-user-name', type=str, help='Specify cloud user name.', required=False)
-    parser.add_argument('--cloud-user-pass', type=str, help='Specify cloud user password.', required=False)
-    parser.add_argument('--ip', type=str, help='Specify IP address for instance.', required=False)
-    parser.add_argument('--netmask', type=str, help='Specify netmask for instance.', required=False)
-    parser.add_argument('--gateway', type=str, help='Specify gateway address for instance.', required=False)
-    parser.add_argument('--dns-nameservers', type=str, help='Specify DNS nameservers delimited by comma.', required=False)
-    parser.add_argument('--user-data', type=str, help='Specify path to user_data file in yaml format.', required=False)
-    parser.add_argument('--vendor-data', type=str, help='Specify path to vendor_data.json in openstack vendor metadata format.', required=False)
-    parser.add_argument('--network-data', type=str, help='Specify path to network_data.json in openstack network metadata format.', required=False)
-    parser.add_argument('--model', type=str, help='Specify path to cluster model.', required=False)
-    parser.add_argument('--mk-pipelines', type=str, help='Specify path to mk-pipelines folder.', required=False)
-    parser.add_argument('--pipeline-library', type=str, help='Specify path to pipeline-library folder.', required=False)
-    parser.add_argument('--clean-up', action='store_true', help='Clean-up config-drive dir once ISO is created.', required=False)
-    parser.add_argument('--quiet', action='store_true', help='Keep silence. Do not write any output messages to stout.', required=False)
+    parser = argparse.ArgumentParser(
+        description='Config drive generator for MCP instances.', prog=argv[0],
+        usage='%(prog)s [options]')
+    parser.add_argument('--gpg-key', type=str,
+                        help='Upload gpg key for salt master. Specify path to file in asc format.',
+                        required=False)
+    parser.add_argument('--name', type=str, default=uuid,
+                        help='Specify instance name. Hostname in short format, without domain.',
+                        required=False)
+    parser.add_argument('--hostname', type=str, default=uuid,
+                        help='Specify instance hostname. FQDN. Hostname in full format with domain. Shortname would be trated as name.',
+                        required=False)
+    parser.add_argument('--skip-network', action='store_true',
+                        help='Do not generate network_data for the instance.',
+                        required=False)
+    parser.add_argument('--interface', type=str, default='ens3',
+                        help='Specify interface for instance to configure.',
+                        required=False)
+    parser.add_argument('--ssh-key', type=str,
+                        help='Specify ssh public key to upload to cloud image.',
+                        required=False)
+    parser.add_argument('--ssh-keys', type=str,
+                        help='Upload authorized_keys to cloud image. Specify path to file in authorized_keys format.',
+                        required=False)
+    parser.add_argument('--cloud-user-name', type=str,
+                        help='Specify cloud user name.', required=False)
+    parser.add_argument('--cloud-user-pass', type=str,
+                        help='Specify cloud user password.', required=False)
+    parser.add_argument('--ip', type=str,
+                        help='Specify IP address for instance.', required=False)
+    parser.add_argument('--netmask', type=str,
+                        help='Specify netmask for instance.', required=False)
+    parser.add_argument('--gateway', type=str,
+                        help='Specify gateway address for instance.',
+                        required=False)
+    parser.add_argument('--dns-nameservers', type=str,
+                        help='Specify DNS nameservers delimited by comma.',
+                        required=False)
+    parser.add_argument('--user-data', type=str,
+                        help='Specify path to user_data file in yaml format.',
+                        required=False)
+    parser.add_argument('--vendor-data', type=str,
+                        help='Specify path to vendor_data.json in openstack vendor metadata format.',
+                        required=False)
+    parser.add_argument('--network-data', type=str,
+                        help='Specify path to network_data.json in openstack network metadata format.',
+                        required=False)
+    parser.add_argument('--model', type=str,
+                        help='Specify path to cluster model.', required=False)
+    parser.add_argument('--mk-pipelines', type=str,
+                        help='Specify path to mk-pipelines folder.',
+                        required=False)
+    parser.add_argument('--pipeline-library', type=str,
+                        help='Specify path to pipeline-library folder.',
+                        required=False)
+    parser.add_argument('--clean-up', action='store_true',
+                        help='Clean-up config-drive dir once ISO is created.',
+                        required=False)
+    parser.add_argument('--quiet', action='store_true',
+                        help='Keep silence. Do not write any output messages to stout.',
+                        required=False)
     args = parser.parse_args()
 
     if len(argv) < 2: