/**
 *
 * Provision ironic nodes
 *
 * Expected parameters:
 *   STACK_NAME                 Infrastructure stack name
 *   STACK_TYPE                 Deploy OpenStack/AWS [heat/aws], use 'physical' if no stack should be started
 *
 *   AWS_STACK_REGION           CloudFormation AWS region
 *   AWS_API_CREDENTIALS        AWS Access key ID with  AWS secret access key
 *   AWS_SSH_KEY                AWS key pair name (used for SSH access)
 *
 *   HEAT_STACK_ZONE            Heat stack availability zone
 *   OPENSTACK_API_URL          OpenStack API address
 *   OPENSTACK_API_CREDENTIALS  Credentials to the OpenStack API
 *   OPENSTACK_API_PROJECT      OpenStack project to connect to
 *   OPENSTACK_API_CLIENT       Versions of OpenStack python clients
 *   OPENSTACK_API_VERSION      Version of the OpenStack API (2/3)

 *   SALT_MASTER_CREDENTIALS    Credentials to the Salt API
 *                              required for STACK_TYPE=physical
 *   SALT_MASTER_URL            URL of Salt master

 * Ironic settings:
 *   IRONIC_AUTHORIZATION_PROFILE:    Name of profile with authorization info
 *   IRONIC_DEPLOY_NODES:             Space separated list of ironic node name to deploy
                                      'all' - trigger deployment of all nodes
 *   IRONIC_DEPLOY_PROFILE:           Name of profile to apply to nodes during deployment
 *   IRONIC_DEPLOY_PARTITION_PROFILE: Name of partition profile to apply
 *   IRONIC_DEPLOY_TIMEOUT:           Timeout in minutes to wait for deploy
 *
 **/

common = new com.mirantis.mk.Common()
git = new com.mirantis.mk.Git()
openstack = new com.mirantis.mk.Openstack()
aws = new com.mirantis.mk.Aws()
orchestrate = new com.mirantis.mk.Orchestrate()
salt = new com.mirantis.mk.Salt()
test = new com.mirantis.mk.Test()
def python = new com.mirantis.mk.Python()

def pepperEnv
def venv
def outputs = [:]

def ipRegex = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"

def waitIronicDeployment(pepperEnv, node_names, target, auth_profile, deploy_timeout=60) {
    def failed_nodes = []
    timeout (time:  deploy_timeout.toInteger(), unit: 'MINUTES'){
        while (node_names.size() != 0) {
            common.infoMsg("Waiting for nodes: " + node_names.join(", ") + " to be deployed.")
            res = salt.runSaltProcessStep(pepperEnv, target, 'ironicng.list_nodes', ["profile=${auth_profile}"], null, false)
            for (n in res['return'][0].values()[0]['nodes']){
                if (n['name'] in node_names) {
                    if (n['provision_state'] == 'active'){
                        common.successMsg("Node " + n['name'] + " deployment succeed.")
                        node_names.remove(n['name'])
                        continue
                    } else if (n['provision_state'] == 'deploy failed'){
                        common.warningMsg("Node " + n['name'] + " deployment failed.")
                        node_names.remove(n['name'])
                        failed_nodes.add(n['name'])
                        continue
                    }
                }
            }
            sleep(5)
        }
    }
    return failed_nodes
}

timeout(time: 12, unit: 'HOURS') {
    node("python") {
        try {
            // Set build-specific variables
            def workspace = common.getWorkspace()
            venv = "${workspace}/venv"
            venvPepper = "${workspace}/venvPepper"

            def required_params = ['IRONIC_AUTHORIZATION_PROFILE', 'IRONIC_DEPLOY_NODES']
            def missed_params = []
            for (param in required_params) {
                if (env[param] == '' ) {
                    missed_params.add(param)
                }
            }
            if (missed_params){
                common.errorMsg(missed_params.join(', ') + " should be set.")
            }

            if (IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES != 'all'){
                common.errorMsg("IRONIC_DEPLOY_PROFILE should be set when deploying specific nodes.")
            }

            if (SALT_MASTER_URL == '' && STACK_NAME == ''){
                common.errorMsg("Any of SALT_MASTER_URL or STACK_NAME should be defined.")
            }

            if (SALT_MASTER_URL == '' && STACK_NAME != '') {
                // Get SALT_MASTER_URL machines
                stage ('Getting SALT_MASTER_URL') {

                    outputs.put('stack_type', STACK_TYPE)

                    if (STACK_TYPE == 'heat') {
                        // value defaults
                        envParams = [
                            'cluster_zone': HEAT_STACK_ZONE,
                            'cluster_public_net': HEAT_STACK_PUBLIC_NET
                        ]

                        // create openstack env
                        openstack.setupOpenstackVirtualenv(venv, OPENSTACK_API_CLIENT)
                        openstackCloud = openstack.createOpenstackEnv(venv,
                            OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
                            OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
                            OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
                            OPENSTACK_API_VERSION)
                        openstack.getKeystoneToken(openstackCloud, venv)


                        // get SALT_MASTER_URL
                        saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, STACK_NAME, 'salt_master_ip', venv)

                    } else if (STACK_TYPE == 'aws') {

                        // setup environment
                        aws.setupVirtualEnv(venv)

                        // set aws_env_vars
                        aws_env_vars = aws.getEnvVars(AWS_API_CREDENTIALS, AWS_STACK_REGION)

                        // get outputs
                        saltMasterHost = aws.getOutputs(venv, aws_env_vars, STACK_NAME, 'SaltMasterIP')
                    }

                    if (SALT_MASTER_URL == ''){
                        // check that saltMasterHost is valid
                        if (!saltMasterHost || !saltMasterHost.matches(ipRegex)) {
                            common.errorMsg("saltMasterHost is not a valid ip, value is: ${saltMasterHost}")
                            throw new Exception("saltMasterHost is not a valid ip")
                        }
                        currentBuild.description = "${STACK_NAME} ${saltMasterHost}"
                        SALT_MASTER_URL = "http://${saltMasterHost}:6969"
                    } else {
                        currentBuild.description = "${STACK_NAME}"
                    }
                }
            }

            outputs.put('salt_api', SALT_MASTER_URL)

            python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)



            def nodes_to_deploy=[]

            stage('Trigger deployment on nodes') {
                if (IRONIC_DEPLOY_PARTITION_PROFILE == '' && IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES == 'all'){
                    common.infoMsg("Trigger ironic.deploy")
                    salt.enforceState(pepperEnv, RUN_TARGET, ['ironic.deploy'], true)
                } else {
                    if (IRONIC_DEPLOY_NODES == 'all'){
                         res = salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.list_nodes', ["profile=${IRONIC_AUTHORIZATION_PROFILE}"], null, true)
                         // We trigger deployment on single salt minion
                         for (n in res['return'][0].values()[0]['nodes']){
                            nodes_to_deploy.add(n['name'])
                         }
                    } else {
                        nodes_to_deploy = IRONIC_DEPLOY_NODES.tokenize(',')
                    }

                    def cmd_params = ["profile=${IRONIC_AUTHORIZATION_PROFILE}", "deployment_profile=${IRONIC_DEPLOY_PROFILE}"]

                    if (IRONIC_DEPLOY_PARTITION_PROFILE){
                        cmd_params.add("partition_profile=${IRONIC_DEPLOY_PARTITION_PROFILE}")
                    }

                    for (n in nodes_to_deploy){
                        common.infoMsg("Trigger deployment of ${n}")
                      salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.deploy_node', ["${n}"] + cmd_params, null, true)
                    }
                }
            }

            stage('Waiting for deployment is done.') {
                def failed_nodes = waitIronicDeployment(pepperEnv, nodes_to_deploy, RUN_TARGET, IRONIC_AUTHORIZATION_PROFILE, IRONIC_DEPLOY_TIMEOUT)
                if (failed_nodes){
                    common.errorMsg("Some nodes: " + failed_nodes.join(", ") + " are failed to deploy")
                    currentBuild.result = 'FAILURE'
                } else {
                    common.successMsg("All nodes are deployed successfully.")
                }
            }

            outputsPretty = common.prettify(outputs)
            print(outputsPretty)
            writeFile(file: 'outputs.json', text: outputsPretty)
            archiveArtifacts(artifacts: 'outputs.json')
        } catch (Throwable e) {
            currentBuild.result = 'FAILURE'
            throw e
        }
    }
}
