#!/usr/bin/python

##
## Copyright (c) 2010-2017 Intel Corporation
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
##     http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##

from __future__ import print_function

import os
import stat
import sys
import time
import subprocess
import getopt
import re
import logging
from logging.handlers import RotatingFileHandler
from logging import handlers
from prox_ctrl import prox_ctrl
import ConfigParser

version="18.2.3"
stack = "rapid" #Default string for stack
yaml = "rapid.yaml" #Default string for yaml file
key = "prox" # This is also the default in the yaml file....
flavor = "prox_flavor" # This is also the default in the yaml file....
image = "rapidVM" # This is also the default in the yaml file....
image_file = "rapidVM.qcow2"
dataplane_network = "dataplane-network" # This is also the default in the yaml file....
subnet = "dpdk-subnet" #subnet for dataplane
subnet_cidr="10.10.10.0/24" # cidr for dataplane
internal_network="admin_internal_net"
floating_network="admin_floating_net"
loglevel="DEBUG" # sets log level for writing to file
runtime=10 # time in seconds for 1 test run

def usage():
	print("usage: createrapid [--version] [-v]")
	print("                   [--stack STACK_NAME]")
	print("                   [--yaml YAML_FILE]")
	print("                   [--key KEY_NAME]")
	print("                   [--flavor FLAVOR_NAME]")
	print("                   [--image IMAGE_NAME]")
	print("                   [--image_file IMAGE_FILE]")
	print("                   [--dataplane_network DP_NETWORK]")
	print("                   [--subnet DP_SUBNET]")
	print("                   [--subnet_cidr SUBNET_CIDR]")
	print("                   [--internal_network ADMIN_NETWORK]")
	print("                   [--floating_network ADMIN_NETWORK]")
	print("                   [--log DEBUG|INFO|WARNING|ERROR|CRITICAL")
	print("                   [-h] [--help]")
	print("")
	print("Command-line interface to createrapid")
	print("")
	print("optional arguments:")
	print("  -v,  --version           	Show program's version number and exit")
	print("  --stack STACK_NAME       	Specify a name for the heat stack. Default is %s."%stack)
	print("  --yaml YAML_FILE         	Specify the yaml file to be used. Default is %s."%yaml)
	print("  --key KEY_NAME           	Specify the key to be used. Default is %s."%key)
	print("  --flavor FLAVOR_NAME     	Specify the flavor to be used. Default is %s."%flavor)
	print("  --image IMAGE_NAME       	Specify the image to be used. Default is %s."%image)
	print("  --image_file IMAGE_FILE  	Specify the image qcow2 file to be used. Default is %s."%image_file)
	print("  --dataplane_network NETWORK 	Specify the network name to be used for the dataplane. Default is %s."%dataplane_network)
	print("  --subnet DP_SUBNET	 	Specify the subnet name to be used for the dataplane. Default is %s."%subnet)
	print("  --subnet_cidr SUBNET_CIDR  	Specify the subnet CIDR to be used for the dataplane. Default is %s."%subnet_cidr)
	print("  --internal_network NETWORK 	Specify the network name to be used for the control plane. Default is %s."%internal_network)
	print("  --floating_network NETWORK 	Specify the external floating ip network name. Default is %s. NO if no floating ip used."%floating_network)
	print("  --log				Specify logging level for log file output, screen output level is hard coded")
	print("  -h, --help               	Show help message and exit.")
	print("")
	print("To delete the rapid stack, type the following command")
	print("   openstack stack delete --yes --wait %s"%stack)
	print("Note that %s is the default stack name. Replace with STACK_NAME if needed"%stack)

try:
	opts, args = getopt.getopt(sys.argv[1:], "vh", ["version","help", "yaml=","stack=","key=","flavor=","image=","image_file=","dataplane_network=","subnet=","subnet_cidr=","internal_network=","floating_network=","log="])
except getopt.GetoptError as err:
	print("===========================================")
	print(str(err))
	print("===========================================")
	usage()
	sys.exit(2)
if args:
	usage()
	sys.exit(2)
for opt, arg in opts:
	if opt in ("-h", "--help"):
		usage()
		sys.exit()
	if opt in ("-v", "--version"):
		print("Rapid Automated Performance Indication for Dataplane "+version)
		sys.exit()
	if opt in ("--stack"):
		stack = arg
		print ("Using '"+stack+"' as name for the stack")
	elif opt in ("--yaml"):
		yaml = arg
		print ("Using stack: "+yaml)
	elif opt in ("--key"):
		key = arg
		print ("Using key: "+key)
	elif opt in ("--flavor"):
		flavor = arg
		print ("Using flavor: "+flavor)
	elif opt in ("--image"):
		image = arg
		print ("Using image: "+image)
	elif opt in ("--image_file"):
		image_file = arg
		print ("Using qcow2 file: "+image_file)
	elif opt in ("--dataplane_network"):
		dataplane_network = arg
		print ("Using dataplane network: "+ dataplane_network)
	elif opt in ("--subnet"):
		subnet = arg
		print ("Using dataplane subnet: "+ subnet)
	elif opt in ("--subnet_cidr"):
		subnet_cidr = arg
		print ("Using dataplane subnet: "+ subnet_cidr)
	elif opt in ("--internal_network"):
		internal_network = arg
		print ("Using control plane network: "+ internal_network)
	elif opt in ("--floating_network"):
		floating_network = arg
		print ("Using floating ip network: "+ floating_network)
	elif opt in ("--log"):
		loglevel = arg
		print ("Log level: "+ loglevel)


# create formatters
screen_formatter = logging.Formatter("%(message)s")
file_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")

# get a top-level logger,
# set its log level,
# BUT PREVENT IT from propagating messages to the root logger
#
log = logging.getLogger()
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
    raise ValueError('Invalid log level: %s' % loglevel)
log.setLevel(numeric_level)
log.propagate = 0

# create a console handler
# and set its log level to the command-line option 
# 
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(screen_formatter)

# create a file handler
# and set its log level to DEBUG
#
log_file = 'CREATE' +stack +'.log'
file_handler = logging.handlers.RotatingFileHandler(log_file, backupCount=10)
#file_handler = log.handlers.TimedRotatingFileHandler(log_file, 'D', 1, 5)
file_handler.setLevel(numeric_level)
file_handler.setFormatter(file_formatter)

# add handlers to the logger
#
log.addHandler(file_handler)
log.addHandler(console_handler)

# Check if log exists and should therefore be rolled
needRoll = os.path.isfile(log_file)


# This is a stale log, so roll it
if needRoll:    
    # Add timestamp
    log.debug('\n---------\nLog closed on %s.\n---------\n' % time.asctime())

    # Roll over on application start
    log.handlers[0].doRollover()

# Add timestamp
log.debug('\n---------\nLog started on %s.\n---------\n' % time.asctime())

log.debug("createrapid.py version: "+version)
# Checking if the control network already exists, if not, stop the script
log.debug("Checking control plane network: "+internal_network)
cmd = 'openstack network show '+internal_network
log.debug (cmd)
cmd = cmd + ' |grep "status " | tr -s " " | cut -d" " -f 4'
NetworkExist = subprocess.check_output(cmd , shell=True).strip()
if NetworkExist == 'ACTIVE':
	log.info("Control plane network ("+internal_network+")  already active")
else:
	log.exception("Control plane network " + internal_network + " not existing")
	raise Exception("Control plane network " + internal_network + " not existing")

# Checking if the floating ip network already exists, if not, stop the script
if floating_network <>'NO':
	log.debug("Checking floating ip network: "+floating_network)
	cmd = 'openstack network show '+floating_network
	log.debug (cmd)
	cmd = cmd + ' |grep "status " | tr -s " " | cut -d" " -f 4'
	NetworkExist = subprocess.check_output(cmd , shell=True).strip()
	if NetworkExist == 'ACTIVE':
		log.info("Floating ip network ("+floating_network+")  already active")
	else:
		log.exception("Floating ip network " + floating_network + " not existing")
		raise Exception("Floating ip network " + floating_network + " not existing")

# Checking if the image already exists, if not create it
log.debug("Checking image: "+image)
cmd = 'openstack image show '+image
log.debug(cmd)
cmd = cmd +' |grep "status " | tr -s " " | cut -d" " -f 4'
ImageExist = subprocess.check_output(cmd , shell=True).strip()
if ImageExist == 'active':
	log.info("Image ("+image+") already available")
else:
	log.info('Creating image ...')
	cmd = 'openstack image create --disk-format qcow2 --container-format bare --public --file ./'+image_file+ ' ' +image
	log.debug(cmd)
	cmd = cmd + ' |grep "status " | tr -s " " | cut -d" " -f 4'
	ImageExist = subprocess.check_output(cmd , shell=True).strip()
	if ImageExist == 'active':
		log.info('Image created and active')
		cmd = 'openstack image set --property hw_vif_multiqueue_enabled="true" ' +image
#		subprocess.check_call(cmd , shell=True)
	else :
		log.exception("Failed to create image")
		raise Exception("Failed to create image")

# Checking if the key already exists, if not create it
log.debug("Checking key: "+key)
cmd = 'openstack keypair show '+key
log.debug (cmd)
cmd = cmd + ' |grep "name " | tr -s " " | cut -d" " -f 4'
KeyExist = subprocess.check_output(cmd , shell=True).strip()
if KeyExist == key:
	log.info("Key ("+key+") already installed")
else:
	log.info('Creating key ...')
	cmd = 'openstack keypair create '+ key + '>' +key+'.pem'
	log.debug(cmd)
	subprocess.check_call(cmd , shell=True)
	cmd = 'chmod 600 ' +key+'.pem'
	subprocess.check_call(cmd , shell=True)
	cmd = 'openstack keypair show '+key
	log.debug(cmd)
	cmd = cmd + ' |grep "name " | tr -s " " | cut -d" " -f 4'
	KeyExist = subprocess.check_output(cmd , shell=True).strip()
	if KeyExist == key:
		log.info("Key created")
	else :
		log.exception("Failed to create key: " + key)
		raise Exception("Failed to create key: " + key)

# Checking if the flavor already exists, if not create it
log.debug("Checking flavor: "+flavor)
cmd = 'openstack flavor show '+flavor
log.debug (cmd)
cmd = cmd + ' |grep "name " | tr -s " " | cut -d" " -f 4'
FlavorExist = subprocess.check_output(cmd , shell=True).strip()
if FlavorExist == flavor:
	log.info("Flavor ("+flavor+") already installed")
else:
	log.info('Creating flavor ...')
	cmd = 'openstack flavor create '+flavor+' --ram 8192 --disk 20 --vcpus 4'
	log.debug(cmd)
	cmd = cmd + ' |grep "name " | tr -s " " | cut -d" " -f 4'
	FlavorExist = subprocess.check_output(cmd , shell=True).strip()
	if FlavorExist == flavor:
		cmd = 'openstack flavor set '+ flavor +' --property hw:mem_page_size="large" --property hw:cpu_policy="dedicated" --property hw:cpu_threads_policy="isolate"'
		log.debug(cmd)
		subprocess.check_call(cmd , shell=True)
		log.info("Flavor created")
	else :
		log.exception("Failed to create flavor: " + flavor)
		raise Exception("Failed to create flavor: " + flavor)

# Checking if the dataplane network already exists, if not create it
log.debug("Checking dataplane network: "+dataplane_network)
cmd = 'openstack network show '+dataplane_network
log.debug (cmd)
cmd = cmd + ' |grep "status " | tr -s " " | cut -d" " -f 4'
NetworkExist = subprocess.check_output(cmd , shell=True).strip()
if NetworkExist == 'ACTIVE':
	log.info("Dataplane network ("+dataplane_network+") already active")
else:
	log.info('Creating dataplane network ...')
	cmd = 'openstack network create '+dataplane_network
	log.debug(cmd)
	cmd = cmd + ' |grep "status " | tr -s " " | cut -d" " -f 4'
	NetworkExist = subprocess.check_output(cmd , shell=True).strip()
	if NetworkExist == 'ACTIVE':
		log.info("Dataplane network created")
	else :
		log.exception("Failed to create dataplane network: " + dataplane_network)
		raise Exception("Failed to create dataplane network: " + dataplane_network)

# Checking if the dataplane subnet already exists, if not create it
log.debug("Checking subnet: "+subnet)
cmd = 'openstack subnet show '+ subnet
log.debug (cmd)
cmd = cmd +' |grep "name " | tr -s " " | cut -d"|" -f 3'
SubnetExist = subprocess.check_output(cmd , shell=True).strip()
if SubnetExist == subnet:
	log.info("Subnet (" +subnet+ ") already exists")
else:
	log.info('Creating subnet ...')
	cmd = 'openstack subnet create --network ' + dataplane_network + ' --subnet-range ' + subnet_cidr +' --gateway none ' + subnet
	log.debug(cmd)
	cmd = cmd + ' |grep "name " | tr -s " " | cut -d"|" -f 3'
	SubnetExist = subprocess.check_output(cmd , shell=True).strip()
	if SubnetExist == subnet:
		log.info("Subnet created")
	else :
		log.exception("Failed to create subnet: " + subnet)
		raise Exception("Failed to create subnet: " + subnet)

# Checking if the stack already exists, if not create it
log.debug("Checking Stack: "+stack)
cmd = 'openstack stack show '+stack
log.debug (cmd)
cmd = cmd+' |grep "stack_status " | tr -s " " | cut -d"|" -f 3'
StackRunning = subprocess.check_output(cmd , shell=True).strip()
if StackRunning == '':
	log.info('Creating Stack ...')
	cmd = 'openstack stack create -t '+ yaml +  ' --parameter flavor="'+flavor  +'" --parameter key="'+ key + '" --parameter image="'+image  + '" --parameter dataplane_network="'+dataplane_network+ '" --parameter internal_network="'+internal_network+'" --parameter floating_network="'+floating_network+'" --wait '+stack 
	log.debug(cmd)
	cmd = cmd + ' |grep "stack_status " | tr -s " " | cut -d"|" -f 3'
	StackRunning = subprocess.check_output(cmd , shell=True).strip()
if StackRunning != 'CREATE_COMPLETE':
	log.exception("Failed to create stack")
	raise Exception("Failed to create stack")

# Obtaining IP & MAC addresses for the VMs created in the stack
log.info("Stack ("+stack+") running")
cmd='openstack stack show -f yaml -c outputs ' + stack
log.debug(cmd)
output = subprocess.check_output(cmd , shell=True).strip()
matchObj = re.search('.*total_number_of_VMs.*?([0-9])', output, re.DOTALL)
total_number_of_VMs = matchObj.group(1)
vmDPIP =[]
vmAdminIP =[]
vmDPmac =[]
config = ConfigParser.RawConfigParser()
for vm in range(1, int(total_number_of_VMs)+1):
	searchString = '.*vm%d_dataplane_ip.*?([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)' % vm
	matchObj = re.search(searchString, output, re.DOTALL)
	vmDPIP.append(matchObj.group(1))
	if floating_network <> 'NO':
		searchString = '.*vm%d_public_ip.*?([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)' % vm
		matchObj = re.search(searchString, output, re.DOTALL)
		vmAdminIP.append(matchObj.group(1))
	else:
		searchString = '.*vm%d_private_ip.*?([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)' % vm
		matchObj = re.search(searchString, output, re.DOTALL)
		vmAdminIP.append(matchObj.group(1))
	searchString = '.*vm%d_dataplane_mac.*?([a-fA-F0-9:]{17})' % vm
	matchObj = re.search(searchString, output, re.DOTALL)
	vmDPmac.append(matchObj.group(1))
	log.info('VM%d: (admin IP: %s), (dataplane IP: %s), (dataplane MAC: %s)' % (vm,vmAdminIP[-1],vmDPIP[-1],vmDPmac[-1]))
	config.add_section('VM%d'%vm)
	config.set('VM%d'%vm, 'admin_ip', vmAdminIP[-1])
	config.set('VM%d'%vm, 'dp_ip', vmDPIP[-1])
	config.set('VM%d'%vm, 'dp_mac', vmDPmac[-1])
config.add_section('OpenStack')
config.set('OpenStack', 'stack', stack)
config.set('OpenStack', 'yaml', yaml)
config.set('OpenStack', 'key', key)
config.set('OpenStack', 'flavor', flavor)
config.set('OpenStack', 'image', image)
config.set('OpenStack', 'image_file', image_file)
config.set('OpenStack', 'dataplane_network', dataplane_network)
config.set('OpenStack', 'subnet', subnet)
config.set('OpenStack', 'subnet_cidr', subnet_cidr)
config.set('OpenStack', 'internal_network', internal_network)
config.set('OpenStack', 'floating_network', floating_network)
config.add_section('rapid')
config.set('rapid', 'loglevel', loglevel)
config.set('rapid', 'version', version)
config.set('rapid', 'total_number_of_VMs', total_number_of_VMs)
config.set('DEFAULT', 'admin_ip', 'none')
# Writing the environment file
with open(stack+'.env', 'wb') as envfile:
    config.write(envfile)