blob: 96b6529fd826870c05617065c6a0a63300d53f3c [file] [log] [blame]
Filip Pytloun0a07f702017-02-24 18:26:18 +01001/**
2 *
3 * Launch heat stack with CI/CD lab infrastructure
4 *
5 * Expected parameters:
6 * HEAT_TEMPLATE_URL URL to git repo with Heat templates
7 * HEAT_TEMPLATE_CREDENTIALS Credentials to the Heat templates repo
8 * HEAT_TEMPLATE_BRANCH Heat templates repo branch
9 * HEAT_STACK_NAME Heat stack name
10 * HEAT_STACK_TEMPLATE Heat stack HOT template
11 * HEAT_STACK_ENVIRONMENT Heat stack environmental parameters
12 * HEAT_STACK_ZONE Heat stack availability zone
13 * HEAT_STACK_PUBLIC_NET Heat stack floating IP pool
14 * HEAT_STACK_DELETE Delete Heat stack when finished (bool)
15 * HEAT_STACK_CLEANUP_JOB Name of job for deleting Heat stack
16 * HEAT_STACK_REUSE Reuse Heat stack (don't create one)
17 *
18 * SALT_MASTER_CREDENTIALS Credentials to the Salt API
Filip Pytloune32fda82017-02-24 18:26:18 +010019 * SALT_MASTER_PORT Port of salt-api, defaults to 8000
Filip Pytloun0a07f702017-02-24 18:26:18 +010020 *
21 * OPENSTACK_API_URL OpenStack API address
22 * OPENSTACK_API_CREDENTIALS Credentials to the OpenStack API
23 * OPENSTACK_API_PROJECT OpenStack project to connect to
24 * OPENSTACK_API_CLIENT Versions of OpenStack python clients
25 * OPENSTACK_API_VERSION Version of the OpenStack API (2/3)
26 *
27 */
28
29git = new com.mirantis.mk.Git()
30openstack = new com.mirantis.mk.Openstack()
31salt = new com.mirantis.mk.Salt()
32orchestrate = new com.mirantis.mk.Orchestrate()
33
Filip Pytloun8fe1f7e2017-03-01 18:45:09 +010034def waitForServices(saltMaster) {
35 retry(30) {
36 out = salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', """/bin/bash -c 'docker service ls | grep -E "0/[0-9]+"' && echo 'Some services are not running'""")
37 for (int a = 0; a < out['return'].size(); a++) {
38 def entry = out['return'].get(a)
39 for (int i = 0; i < entry.size(); i++) {
40 def node = entry.get(i)
41 if (node) {
42 if (node.value =~ /Some services are not running/) {
43 sleep(10)
44 throw new Exception("$node.key: $node.value")
45 } else {
46 print out
47 }
48 }
49 }
50 }
51 }
52}
53
Filip Pytloun0a07f702017-02-24 18:26:18 +010054node {
Filip Pytlounfd6726a2017-02-28 19:31:16 +010055 try {
56 // connection objects
57 def openstackCloud
58 def saltMaster
Filip Pytloun0a07f702017-02-24 18:26:18 +010059
Filip Pytlounfd6726a2017-02-28 19:31:16 +010060 // value defaults
61 def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
62 def openstackEnv = "${env.WORKSPACE}/venv"
Filip Pytloun0a07f702017-02-24 18:26:18 +010063
Filip Pytlounfd6726a2017-02-28 19:31:16 +010064 if (HEAT_STACK_NAME == '') {
65 HEAT_STACK_NAME = BUILD_TAG
Filip Pytloun0a07f702017-02-24 18:26:18 +010066 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010067
Filip Pytlounfd6726a2017-02-28 19:31:16 +010068 //
69 // Bootstrap
70 //
71
72 stage ('Download Heat templates') {
73 git.checkoutGitRepository('template', HEAT_TEMPLATE_URL, HEAT_TEMPLATE_BRANCH, HEAT_TEMPLATE_CREDENTIALS)
Filip Pytloune32fda82017-02-24 18:26:18 +010074 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010075
Filip Pytlounfd6726a2017-02-28 19:31:16 +010076 stage('Install OpenStack CLI') {
77 openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
78 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010079
Filip Pytlounfd6726a2017-02-28 19:31:16 +010080 stage('Connect to OpenStack cloud') {
81 openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
82 openstack.getKeystoneToken(openstackCloud, openstackEnv)
83 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010084
Filip Pytlounfd6726a2017-02-28 19:31:16 +010085 if (HEAT_STACK_REUSE == 'false') {
86 stage('Launch new Heat stack') {
87 envParams = [
88 'instance_zone': HEAT_STACK_ZONE,
89 'public_net': HEAT_STACK_PUBLIC_NET
90 ]
91 openstack.createHeatStack(openstackCloud, HEAT_STACK_NAME, HEAT_STACK_TEMPLATE, envParams, HEAT_STACK_ENVIRONMENT, openstackEnv)
92 }
93 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010094
Filip Pytlounfd6726a2017-02-28 19:31:16 +010095 stage('Connect to Salt master') {
96 def saltMasterPort
97 try {
98 saltMasterPort = SALT_MASTER_PORT
99 } catch (MissingPropertyException e) {
100 saltMasterPort = 8000
101 }
102 saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, HEAT_STACK_NAME, 'salt_master_ip', openstackEnv)
103 saltMasterUrl = "http://${saltMasterHost}:${saltMasterPort}"
104 saltMaster = salt.connection(saltMasterUrl, SALT_MASTER_CREDENTIALS)
105 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100106
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100107 //
108 // Install
109 //
Filip Pytloun0a07f702017-02-24 18:26:18 +0100110
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100111 stage('Install core infra') {
112 // salt.master, reclass
113 // refresh_pillar
114 // sync_all
115 // linux,openssh,salt.minion.ntp
Filip Pytloun0a07f702017-02-24 18:26:18 +0100116
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100117 orchestrate.installFoundationInfra(saltMaster)
118 orchestrate.validateFoundationInfra(saltMaster)
119 }
Filip Pytloun23741982017-02-27 17:43:00 +0100120
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100121 stage("Deploy GlusterFS") {
122 salt.enforceState(saltMaster, 'I@glusterfs:server', 'glusterfs.server.service', true)
123 salt.enforceState(saltMaster, 'ci01*', 'glusterfs.server.setup', true)
124 sleep(5)
125 salt.enforceState(saltMaster, 'I@glusterfs:client', 'glusterfs.client', true)
126 print salt.cmdRun(saltMaster, 'I@glusterfs:client', 'mount|grep fuse.glusterfs || echo "Command failed"')
127 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100128
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100129 stage("Deploy GlusterFS") {
130 salt.enforceState(saltMaster, 'I@haproxy:proxy', 'haproxy,keepalived')
131 }
132
133 stage("Setup Docker Swarm") {
134 salt.enforceState(saltMaster, 'I@docker:host', 'docker.host', true)
135 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.swarm', true)
136 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'salt', true)
137 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.flush')
138 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.update')
139 salt.enforceState(saltMaster, 'I@docker:swarm', 'docker.swarm', true)
140 print salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'docker node ls')
141 }
142
143 stage("Deploy Docker services") {
144 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
145
146 // XXX: Hack to fix dependency of gerrit on mysql
147 print salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', "docker service rm gerrit; sleep 5; rm -rf /srv/volumes/gerrit/*")
Filip Pytloun8fe1f7e2017-03-01 18:45:09 +0100148 waitForServices(saltMaster)
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100149 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
150 // ---- cut here (end of hack) ----
151
Filip Pytloun8fe1f7e2017-03-01 18:45:09 +0100152 waitForServices(saltMaster)
Filip Pytloun0a07f702017-02-24 18:26:18 +0100153 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100154
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100155 stage("Configure CI/CD services") {
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100156 // Aptly
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100157 salt.enforceState(saltMaster, 'I@aptly:server', 'aptly', true)
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100158
159 // Gerrit
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100160 timeout(10) {
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100161 println "Waiting for Gerrit to come up.."
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100162 salt.cmdRun(saltMaster, 'I@gerrit:client', 'while true; do curl -svf 172.16.10.254:8080 >/dev/null && break; done')
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100163 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100164 retry(2) {
165 // Needs to run twice to pass __virtual__ method of gerrit module
166 // after installation of dependencies
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100167 try {
168 salt.enforceState(saltMaster, 'I@gerrit:client', 'gerrit', true)
169 } catch (Exception e) {
Filip Pytloun22cc2382017-03-01 12:06:04 +0100170 print "Restarting Salt minion"
171 salt.cmdRun(saltMaster, 'I@gerrit:client', "exec 0>&-; exec 1>&-; exec 2>&-; nohup /bin/sh -c 'salt-call --local service.restart salt-minion' &")
Filip Pytloun199a2fb2017-03-01 12:47:47 +0100172 sleep(5)
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100173 throw e
174 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100175 }
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100176
177 // Jenkins
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100178 timeout(10) {
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100179 println "Waiting for Jenkins to come up.."
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100180 salt.cmdRun(saltMaster, 'I@jenkins:client', 'while true; do curl -svf 172.16.10.254:8081 >/dev/null && break; done')
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100181 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100182 retry(2) {
183 // Same for jenkins
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100184 try {
185 salt.enforceState(saltMaster, 'I@jenkins:client', 'jenkins', true)
186 } catch (Exception e) {
Filip Pytloun22cc2382017-03-01 12:06:04 +0100187 print "Restarting Salt minion"
188 salt.cmdRun(saltMaster, 'I@jenkins:client', "exec 0>&-; exec 1>&-; exec 2>&-; nohup /bin/sh -c 'salt-call --local service.restart salt-minion' &")
Filip Pytloun199a2fb2017-03-01 12:47:47 +0100189 sleep(5)
Filip Pytloundd3d8e22017-03-01 10:22:44 +0100190 throw e
191 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100192 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100193 }
Filip Pytloun3d045f82017-03-01 09:44:52 +0100194
195 stage("Finalize") {
196 //
197 // Generate docs
198 //
199 try {
200 retry(3) {
Filip Pytlounf2961622017-03-01 12:13:30 +0100201 print salt.orchestrateSystem(saltMaster, ['expression': '*', 'type': 'compound'], 'sphinx.orch.generate_doc')
Filip Pytloun3d045f82017-03-01 09:44:52 +0100202 }
203 } catch (Throwable e) {
204 // We don't want sphinx docs to ruin whole build, so possible
205 // errors are just ignored here
206 true
207 }
208 salt.enforceState(saltMaster, 'I@nginx:server', 'nginx')
209
Filip Pytlounf2961622017-03-01 12:13:30 +0100210 print """============================================================
Filip Pytloun64123cd2017-03-01 11:26:17 +0100211Your CI/CD lab has been deployed and you can enjoy it:
Filip Pytlounf2961622017-03-01 12:13:30 +0100212Use sshuttle -r ubuntu@${saltMasterHost} 172.16.10.0/24
213to connect to your private subnet and visit services
214running at 172.16.10.254 (vip address):
215 9600 haproxy stats
216 8080 gerrit
217 8081 jenkins
218 8091 Docker swarm visualizer
219 8090 Reclass-generated documentation
Filip Pytloun64123cd2017-03-01 11:26:17 +0100220
221Don't forget to terminate your stack when you don't needed!
222============================================================"""
Filip Pytloun3d045f82017-03-01 09:44:52 +0100223 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100224 } catch (Throwable e) {
225 // If there was an error or exception thrown, the build failed
226 currentBuild.result = "FAILURE"
227 throw e
228 } finally {
229 // Cleanup
230 if (HEAT_STACK_DELETE == 'true') {
231 stage('Trigger cleanup job') {
232 build job: 'deploy_heat_cleanup', parameters: [[$class: 'StringParameterValue', name: 'HEAT_STACK_NAME', value: HEAT_STACK_NAME]]
233 }
Filip Pytloun23741982017-02-27 17:43:00 +0100234 }
Filip Pytlounf6e877f2017-02-28 19:38:16 +0100235 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100236}