blob: 3d2717b2b21e6d61107c41c2e64fa96db025114c [file] [log] [blame]
Vasyl Saienkoe4a84152017-09-06 12:05:38 +03001/**
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
34common = new com.mirantis.mk.Common()
35git = new com.mirantis.mk.Git()
36openstack = new com.mirantis.mk.Openstack()
37aws = new com.mirantis.mk.Aws()
38orchestrate = new com.mirantis.mk.Orchestrate()
39salt = new com.mirantis.mk.Salt()
40test = new com.mirantis.mk.Test()
chnyda625f4b42017-10-11 14:10:31 +020041def python = new com.mirantis.mk.Python()
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030042
Jakub Josef88aaf832018-01-18 16:18:28 +010043def pepperEnv
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030044def venv
45def outputs = [:]
46
47def ipRegex = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
48
chnyda625f4b42017-10-11 14:10:31 +020049def waitIronicDeployment(pepperEnv, node_names, target, auth_profile, deploy_timeout=60) {
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030050 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.")
chnyda625f4b42017-10-11 14:10:31 +020054 res = salt.runSaltProcessStep(pepperEnv, target, 'ironicng.list_nodes', ["profile=${auth_profile}"], null, false)
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030055 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 Josefa63f9862018-01-11 17:58:38 +010075timeout(time: 12, unit: 'HOURS') {
76 node("python") {
77 try {
78 // Set build-specific variables
Jakub Josef88aaf832018-01-18 16:18:28 +010079 def workspace = common.getWorkspace()
80 venv = "${workspace}/venv"
81 venvPepper = "${workspace}/venvPepper"
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030082
Jakub Josefa63f9862018-01-11 17:58:38 +010083 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 Saienkoe4a84152017-09-06 12:05:38 +030088 }
Jakub Josefa63f9862018-01-11 17:58:38 +010089 }
90 if (missed_params){
91 common.errorMsg(missed_params.join(', ') + " should be set.")
92 }
Vasyl Saienkoe4a84152017-09-06 12:05:38 +030093
Jakub Josefa63f9862018-01-11 17:58:38 +010094 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 Josef88aaf832018-01-18 16:18:28 +0100117 openstackCloud = openstack.createOpenstackEnv(venv,
Jakub Josefa63f9862018-01-11 17:58:38 +0100118 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 Saienkoe4a84152017-09-06 12:05:38 +0300138 }
Jakub Josefa63f9862018-01-11 17:58:38 +0100139
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 Saienkoe4a84152017-09-06 12:05:38 +0300166 } else {
Jakub Josefa63f9862018-01-11 17:58:38 +0100167 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 Saienkoe4a84152017-09-06 12:05:38 +0300187 }
188 }
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300189
Jakub Josefa63f9862018-01-11 17:58:38 +0100190 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 Saienkoe4a84152017-09-06 12:05:38 +0300195 } else {
Jakub Josefa63f9862018-01-11 17:58:38 +0100196 common.successMsg("All nodes are deployed successfully.")
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300197 }
198 }
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300199
Jakub Josefa63f9862018-01-11 17:58:38 +0100200 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 Saienkoe4a84152017-09-06 12:05:38 +0300207 }
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300208 }
209}