blob: 05e5313dab607a6b40a23214fef8d12df9fd23a0 [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
chnyda625f4b42017-10-11 14:10:31 +020043def pepperEnv = "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
75
76node("python") {
77 try {
78 // Set build-specific variables
79 venv = "${env.WORKSPACE}/venv"
80
81 def required_params = ['IRONIC_AUTHORIZATION_PROFILE', 'IRONIC_DEPLOY_NODES']
82 def missed_params = []
83 for (param in required_params) {
84 if (env[param] == '' ) {
85 missed_params.add(param)
86 }
87 }
88 if (missed_params){
89 common.errorMsg(missed_params.join(', ') + " should be set.")
90 }
91
92 if (IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES != 'all'){
93 common.errorMsg("IRONIC_DEPLOY_PROFILE should be set when deploying specific nodes.")
94 }
95
96 if (SALT_MASTER_URL == '' && STACK_NAME == ''){
97 common.errorMsg("Any of SALT_MASTER_URL or STACK_NAME should be defined.")
98 }
99
100 if (SALT_MASTER_URL == '' && STACK_NAME != '') {
101 // Get SALT_MASTER_URL machines
102 stage ('Getting SALT_MASTER_URL') {
103
104 outputs.put('stack_type', STACK_TYPE)
105
106 if (STACK_TYPE == 'heat') {
107 // value defaults
108 envParams = [
109 'cluster_zone': HEAT_STACK_ZONE,
110 'cluster_public_net': HEAT_STACK_PUBLIC_NET
111 ]
112
113 // create openstack env
114 openstack.setupOpenstackVirtualenv(venv, OPENSTACK_API_CLIENT)
115 openstackCloud = openstack.createOpenstackEnv(
116 OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS,
117 OPENSTACK_API_PROJECT, OPENSTACK_API_PROJECT_DOMAIN,
118 OPENSTACK_API_PROJECT_ID, OPENSTACK_API_USER_DOMAIN,
119 OPENSTACK_API_VERSION)
120 openstack.getKeystoneToken(openstackCloud, venv)
121
122
123 // get SALT_MASTER_URL
124 saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, STACK_NAME, 'salt_master_ip', venv)
125
126 } else if (STACK_TYPE == 'aws') {
127
128 // setup environment
129 aws.setupVirtualEnv(venv)
130
131 // set aws_env_vars
132 aws_env_vars = aws.getEnvVars(AWS_API_CREDENTIALS, AWS_STACK_REGION)
133
134 // get outputs
135 saltMasterHost = aws.getOutputs(venv, aws_env_vars, STACK_NAME, 'SaltMasterIP')
136 }
137
138 if (SALT_MASTER_URL == ''){
139 // check that saltMasterHost is valid
140 if (!saltMasterHost || !saltMasterHost.matches(ipRegex)) {
141 common.errorMsg("saltMasterHost is not a valid ip, value is: ${saltMasterHost}")
142 throw new Exception("saltMasterHost is not a valid ip")
143 }
144 currentBuild.description = "${STACK_NAME} ${saltMasterHost}"
145 SALT_MASTER_URL = "http://${saltMasterHost}:6969"
146 } else {
147 currentBuild.description = "${STACK_NAME}"
148 }
149 }
150 }
151
152 outputs.put('salt_api', SALT_MASTER_URL)
153
Dmitrii Kabanovf31c8962017-10-12 21:00:30 -0700154 python.setupPepperVirtualenv(pepperEnv, SALT_MASTER_URL, SALT_MASTER_CREDENTIALS)
chnyda625f4b42017-10-11 14:10:31 +0200155
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300156
157
158 def nodes_to_deploy=[]
159
160 stage('Trigger deployment on nodes') {
161 if (IRONIC_DEPLOY_PARTITION_PROFILE == '' && IRONIC_DEPLOY_PROFILE == '' && IRONIC_DEPLOY_NODES == 'all'){
162 common.infoMsg("Trigger ironic.deploy")
chnyda625f4b42017-10-11 14:10:31 +0200163 salt.enforceState(pepperEnv, RUN_TARGET, ['ironic.deploy'], true)
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300164 } else {
165 if (IRONIC_DEPLOY_NODES == 'all'){
chnyda625f4b42017-10-11 14:10:31 +0200166 res = salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.list_nodes', ["profile=${IRONIC_AUTHORIZATION_PROFILE}"], null, true)
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300167 // We trigger deployment on single salt minion
168 for (n in res['return'][0].values()[0]['nodes']){
169 nodes_to_deploy.add(n['name'])
170 }
171 } else {
172 nodes_to_deploy = IRONIC_DEPLOY_NODES.tokenize(',')
173 }
174
175 def cmd_params = ["profile=${IRONIC_AUTHORIZATION_PROFILE}", "deployment_profile=${IRONIC_DEPLOY_PROFILE}"]
176
177 if (IRONIC_DEPLOY_PARTITION_PROFILE){
178 cmd_params.add("partition_profile=${IRONIC_DEPLOY_PARTITION_PROFILE}")
179 }
180
181 for (n in nodes_to_deploy){
182 common.infoMsg("Trigger deployment of ${n}")
chnyda625f4b42017-10-11 14:10:31 +0200183 salt.runSaltProcessStep(pepperEnv, RUN_TARGET, 'ironicng.deploy_node', ["${n}"] + cmd_params, null, true)
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300184 }
185 }
186 }
187
188 stage('Waiting for deployment is done.') {
chnyda625f4b42017-10-11 14:10:31 +0200189 def failed_nodes = waitIronicDeployment(pepperEnv, nodes_to_deploy, RUN_TARGET, IRONIC_AUTHORIZATION_PROFILE, IRONIC_DEPLOY_TIMEOUT)
Vasyl Saienkoe4a84152017-09-06 12:05:38 +0300190 if (failed_nodes){
191 common.errorMsg("Some nodes: " + failed_nodes.join(", ") + " are failed to deploy")
192 currentBuild.result = 'FAILURE'
193 } else {
194 common.successMsg("All nodes are deployed successfully.")
195 }
196 }
197
198 outputsPretty = common.prettify(outputs)
199 print(outputsPretty)
200 writeFile(file: 'outputs.json', text: outputsPretty)
201 archiveArtifacts(artifacts: 'outputs.json')
202 } catch (Throwable e) {
203 currentBuild.result = 'FAILURE'
204 throw e
205 }
206}