| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 1 | /** | 
 | 2 |  * | 
 | 3 |  * Provision ironic nodes | 
 | 4 |  * | 
 | 5 |  * Expected parameters: | 
 | 6 |  *   STACK_NAME                 Infrastructure stack name | 
 | 7 |  *   STACK_TYPE                 Deploy OpenStack/AWS [heat/aws], use 'physical' if no stack should be started | 
 | 8 |  * | 
 | 9 |  *   AWS_STACK_REGION           CloudFormation AWS region | 
 | 10 |  *   AWS_API_CREDENTIALS        AWS Access key ID with  AWS secret access key | 
 | 11 |  *   AWS_SSH_KEY                AWS key pair name (used for SSH access) | 
 | 12 |  * | 
 | 13 |  *   HEAT_STACK_ZONE            Heat stack availability zone | 
 | 14 |  *   OPENSTACK_API_URL          OpenStack API address | 
 | 15 |  *   OPENSTACK_API_CREDENTIALS  Credentials to the OpenStack API | 
 | 16 |  *   OPENSTACK_API_PROJECT      OpenStack project to connect to | 
 | 17 |  *   OPENSTACK_API_CLIENT       Versions of OpenStack python clients | 
 | 18 |  *   OPENSTACK_API_VERSION      Version of the OpenStack API (2/3) | 
 | 19 |  | 
 | 20 |  *   SALT_MASTER_CREDENTIALS    Credentials to the Salt API | 
 | 21 |  *                              required for STACK_TYPE=physical | 
 | 22 |  *   SALT_MASTER_URL            URL of Salt master | 
 | 23 |  | 
 | 24 |  * Ironic settings: | 
 | 25 |  *   IRONIC_AUTHORIZATION_PROFILE:    Name of profile with authorization info | 
 | 26 |  *   IRONIC_DEPLOY_NODES:             Space separated list of ironic node name to deploy | 
 | 27 |                                       'all' - trigger deployment of all nodes | 
 | 28 |  *   IRONIC_DEPLOY_PROFILE:           Name of profile to apply to nodes during deployment | 
 | 29 |  *   IRONIC_DEPLOY_PARTITION_PROFILE: Name of partition profile to apply | 
 | 30 |  *   IRONIC_DEPLOY_TIMEOUT:           Timeout in minutes to wait for deploy | 
 | 31 |  * | 
 | 32 |  **/ | 
 | 33 |  | 
 | 34 | common = new com.mirantis.mk.Common() | 
 | 35 | git = new com.mirantis.mk.Git() | 
 | 36 | openstack = new com.mirantis.mk.Openstack() | 
 | 37 | aws = new com.mirantis.mk.Aws() | 
 | 38 | orchestrate = new com.mirantis.mk.Orchestrate() | 
 | 39 | salt = new com.mirantis.mk.Salt() | 
 | 40 | test = new com.mirantis.mk.Test() | 
| chnyda | 625f4b4 | 2017-10-11 14:10:31 +0200 | [diff] [blame] | 41 | def python = new com.mirantis.mk.Python() | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 42 |  | 
| Jakub Josef | 88aaf83 | 2018-01-18 16:18:28 +0100 | [diff] [blame] | 43 | def pepperEnv | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 44 | def venv | 
 | 45 | def outputs = [:] | 
 | 46 |  | 
 | 47 | def ipRegex = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" | 
 | 48 |  | 
| chnyda | 625f4b4 | 2017-10-11 14:10:31 +0200 | [diff] [blame] | 49 | def waitIronicDeployment(pepperEnv, node_names, target, auth_profile, deploy_timeout=60) { | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 50 |     def failed_nodes = [] | 
 | 51 |     timeout (time:  deploy_timeout.toInteger(), unit: 'MINUTES'){ | 
 | 52 |         while (node_names.size() != 0) { | 
 | 53 |             common.infoMsg("Waiting for nodes: " + node_names.join(", ") + " to be deployed.") | 
| chnyda | 625f4b4 | 2017-10-11 14:10:31 +0200 | [diff] [blame] | 54 |             res = salt.runSaltProcessStep(pepperEnv, target, 'ironicng.list_nodes', ["profile=${auth_profile}"], null, false) | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 55 |             for (n in res['return'][0].values()[0]['nodes']){ | 
 | 56 |                 if (n['name'] in node_names) { | 
 | 57 |                     if (n['provision_state'] == 'active'){ | 
 | 58 |                         common.successMsg("Node " + n['name'] + " deployment succeed.") | 
 | 59 |                         node_names.remove(n['name']) | 
 | 60 |                         continue | 
 | 61 |                     } else if (n['provision_state'] == 'deploy failed'){ | 
 | 62 |                         common.warningMsg("Node " + n['name'] + " deployment failed.") | 
 | 63 |                         node_names.remove(n['name']) | 
 | 64 |                         failed_nodes.add(n['name']) | 
 | 65 |                         continue | 
 | 66 |                     } | 
 | 67 |                 } | 
 | 68 |             } | 
 | 69 |             sleep(5) | 
 | 70 |         } | 
 | 71 |     } | 
 | 72 |     return failed_nodes | 
 | 73 | } | 
 | 74 |  | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 75 | timeout(time: 12, unit: 'HOURS') { | 
 | 76 |     node("python") { | 
 | 77 |         try { | 
 | 78 |             // Set build-specific variables | 
| Jakub Josef | 88aaf83 | 2018-01-18 16:18:28 +0100 | [diff] [blame] | 79 |             def workspace = common.getWorkspace() | 
 | 80 |             venv = "${workspace}/venv" | 
 | 81 |             venvPepper = "${workspace}/venvPepper" | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 82 |  | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 83 |             def required_params = ['IRONIC_AUTHORIZATION_PROFILE', 'IRONIC_DEPLOY_NODES'] | 
 | 84 |             def missed_params = [] | 
 | 85 |             for (param in required_params) { | 
 | 86 |                 if (env[param] == '' ) { | 
 | 87 |                     missed_params.add(param) | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 88 |                 } | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 89 |             } | 
 | 90 |             if (missed_params){ | 
 | 91 |                 common.errorMsg(missed_params.join(', ') + " should be set.") | 
 | 92 |             } | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 93 |  | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 94 |             if (IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES != 'all'){ | 
 | 95 |                 common.errorMsg("IRONIC_DEPLOY_PROFILE should be set when deploying specific nodes.") | 
 | 96 |             } | 
 | 97 |  | 
 | 98 |             if (SALT_MASTER_URL == '' && STACK_NAME == ''){ | 
 | 99 |                 common.errorMsg("Any of SALT_MASTER_URL or STACK_NAME should be defined.") | 
 | 100 |             } | 
 | 101 |  | 
 | 102 |             if (SALT_MASTER_URL == '' && STACK_NAME != '') { | 
 | 103 |                 // Get SALT_MASTER_URL machines | 
 | 104 |                 stage ('Getting SALT_MASTER_URL') { | 
 | 105 |  | 
 | 106 |                     outputs.put('stack_type', STACK_TYPE) | 
 | 107 |  | 
 | 108 |                     if (STACK_TYPE == 'heat') { | 
 | 109 |                         // value defaults | 
 | 110 |                         envParams = [ | 
 | 111 |                             'cluster_zone': HEAT_STACK_ZONE, | 
 | 112 |                             'cluster_public_net': HEAT_STACK_PUBLIC_NET | 
 | 113 |                         ] | 
 | 114 |  | 
 | 115 |                         // create openstack env | 
 | 116 |                         openstack.setupOpenstackVirtualenv(venv, OPENSTACK_API_CLIENT) | 
| Jakub Josef | 88aaf83 | 2018-01-18 16:18:28 +0100 | [diff] [blame] | 117 |                         openstackCloud = openstack.createOpenstackEnv(venv, | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 118 |                             OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, | 
 | 119 |                             OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN, | 
 | 120 |                             OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN, | 
 | 121 |                             OPENSTACK_API_VERSION) | 
 | 122 |                         openstack.getKeystoneToken(openstackCloud, venv) | 
 | 123 |  | 
 | 124 |  | 
 | 125 |                         // get SALT_MASTER_URL | 
 | 126 |                         saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, STACK_NAME, 'salt_master_ip', venv) | 
 | 127 |  | 
 | 128 |                     } else if (STACK_TYPE == 'aws') { | 
 | 129 |  | 
 | 130 |                         // setup environment | 
 | 131 |                         aws.setupVirtualEnv(venv) | 
 | 132 |  | 
 | 133 |                         // set aws_env_vars | 
 | 134 |                         aws_env_vars = aws.getEnvVars(AWS_API_CREDENTIALS, AWS_STACK_REGION) | 
 | 135 |  | 
 | 136 |                         // get outputs | 
 | 137 |                         saltMasterHost = aws.getOutputs(venv, aws_env_vars, STACK_NAME, 'SaltMasterIP') | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 138 |                     } | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 139 |  | 
 | 140 |                     if (SALT_MASTER_URL == ''){ | 
 | 141 |                         // check that saltMasterHost is valid | 
 | 142 |                         if (!saltMasterHost || !saltMasterHost.matches(ipRegex)) { | 
 | 143 |                             common.errorMsg("saltMasterHost is not a valid ip, value is: ${saltMasterHost}") | 
 | 144 |                             throw new Exception("saltMasterHost is not a valid ip") | 
 | 145 |                         } | 
 | 146 |                         currentBuild.description = "${STACK_NAME} ${saltMasterHost}" | 
 | 147 |                         SALT_MASTER_URL = "http://${saltMasterHost}:6969" | 
 | 148 |                     } else { | 
 | 149 |                         currentBuild.description = "${STACK_NAME}" | 
 | 150 |                     } | 
 | 151 |                 } | 
 | 152 |             } | 
 | 153 |  | 
 | 154 |             outputs.put('salt_api', SALT_MASTER_URL) | 
 | 155 |  | 
 | 156 |             python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS) | 
 | 157 |  | 
 | 158 |  | 
 | 159 |  | 
 | 160 |             def nodes_to_deploy=[] | 
 | 161 |  | 
 | 162 |             stage('Trigger deployment on nodes') { | 
 | 163 |                 if (IRONIC_DEPLOY_PARTITION_PROFILE == '' && IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES == 'all'){ | 
 | 164 |                     common.infoMsg("Trigger ironic.deploy") | 
 | 165 |                     salt.enforceState(pepperEnv, RUN_TARGET, ['ironic.deploy'], true) | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 166 |                 } else { | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 167 |                     if (IRONIC_DEPLOY_NODES == 'all'){ | 
 | 168 |                          res = salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.list_nodes', ["profile=${IRONIC_AUTHORIZATION_PROFILE}"], null, true) | 
 | 169 |                          // We trigger deployment on single salt minion | 
 | 170 |                          for (n in res['return'][0].values()[0]['nodes']){ | 
 | 171 |                             nodes_to_deploy.add(n['name']) | 
 | 172 |                          } | 
 | 173 |                     } else { | 
 | 174 |                         nodes_to_deploy = IRONIC_DEPLOY_NODES.tokenize(',') | 
 | 175 |                     } | 
 | 176 |  | 
 | 177 |                     def cmd_params = ["profile=${IRONIC_AUTHORIZATION_PROFILE}", "deployment_profile=${IRONIC_DEPLOY_PROFILE}"] | 
 | 178 |  | 
 | 179 |                     if (IRONIC_DEPLOY_PARTITION_PROFILE){ | 
 | 180 |                         cmd_params.add("partition_profile=${IRONIC_DEPLOY_PARTITION_PROFILE}") | 
 | 181 |                     } | 
 | 182 |  | 
 | 183 |                     for (n in nodes_to_deploy){ | 
 | 184 |                         common.infoMsg("Trigger deployment of ${n}") | 
 | 185 |                       salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.deploy_node', ["${n}"] + cmd_params, null, true) | 
 | 186 |                     } | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 187 |                 } | 
 | 188 |             } | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 189 |  | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 190 |             stage('Waiting for deployment is done.') { | 
 | 191 |                 def failed_nodes = waitIronicDeployment(pepperEnv, nodes_to_deploy, RUN_TARGET, IRONIC_AUTHORIZATION_PROFILE, IRONIC_DEPLOY_TIMEOUT) | 
 | 192 |                 if (failed_nodes){ | 
 | 193 |                     common.errorMsg("Some nodes: " + failed_nodes.join(", ") + " are failed to deploy") | 
 | 194 |                     currentBuild.result = 'FAILURE' | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 195 |                 } else { | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 196 |                     common.successMsg("All nodes are deployed successfully.") | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 197 |                 } | 
 | 198 |             } | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 199 |  | 
| Jakub Josef | a63f986 | 2018-01-11 17:58:38 +0100 | [diff] [blame] | 200 |             outputsPretty = common.prettify(outputs) | 
 | 201 |             print(outputsPretty) | 
 | 202 |             writeFile(file: 'outputs.json', text: outputsPretty) | 
 | 203 |             archiveArtifacts(artifacts: 'outputs.json') | 
 | 204 |         } catch (Throwable e) { | 
 | 205 |             currentBuild.result = 'FAILURE' | 
 | 206 |             throw e | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 207 |         } | 
| Vasyl Saienko | e4a8415 | 2017-09-06 12:05:38 +0300 | [diff] [blame] | 208 |     } | 
 | 209 | } |