blob: b97911210b42f441d77ea340c6897b525d87ffc1 [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
34node {
Filip Pytlounfd6726a2017-02-28 19:31:16 +010035 try {
36 // connection objects
37 def openstackCloud
38 def saltMaster
Filip Pytloun0a07f702017-02-24 18:26:18 +010039
Filip Pytlounfd6726a2017-02-28 19:31:16 +010040 // value defaults
41 def openstackVersion = OPENSTACK_API_CLIENT ? OPENSTACK_API_CLIENT : 'liberty'
42 def openstackEnv = "${env.WORKSPACE}/venv"
Filip Pytloun0a07f702017-02-24 18:26:18 +010043
Filip Pytlounfd6726a2017-02-28 19:31:16 +010044 if (HEAT_STACK_NAME == '') {
45 HEAT_STACK_NAME = BUILD_TAG
Filip Pytloun0a07f702017-02-24 18:26:18 +010046 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010047
Filip Pytlounfd6726a2017-02-28 19:31:16 +010048 //
49 // Bootstrap
50 //
51
52 stage ('Download Heat templates') {
53 git.checkoutGitRepository('template', HEAT_TEMPLATE_URL, HEAT_TEMPLATE_BRANCH, HEAT_TEMPLATE_CREDENTIALS)
Filip Pytloune32fda82017-02-24 18:26:18 +010054 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010055
Filip Pytlounfd6726a2017-02-28 19:31:16 +010056 stage('Install OpenStack CLI') {
57 openstack.setupOpenstackVirtualenv(openstackEnv, openstackVersion)
58 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010059
Filip Pytlounfd6726a2017-02-28 19:31:16 +010060 stage('Connect to OpenStack cloud') {
61 openstackCloud = openstack.createOpenstackEnv(OPENSTACK_API_URL, OPENSTACK_API_CREDENTIALS, OPENSTACK_API_PROJECT)
62 openstack.getKeystoneToken(openstackCloud, openstackEnv)
63 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010064
Filip Pytlounfd6726a2017-02-28 19:31:16 +010065 if (HEAT_STACK_REUSE == 'false') {
66 stage('Launch new Heat stack') {
67 envParams = [
68 'instance_zone': HEAT_STACK_ZONE,
69 'public_net': HEAT_STACK_PUBLIC_NET
70 ]
71 openstack.createHeatStack(openstackCloud, HEAT_STACK_NAME, HEAT_STACK_TEMPLATE, envParams, HEAT_STACK_ENVIRONMENT, openstackEnv)
72 }
73 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010074
Filip Pytlounfd6726a2017-02-28 19:31:16 +010075 stage('Connect to Salt master') {
76 def saltMasterPort
77 try {
78 saltMasterPort = SALT_MASTER_PORT
79 } catch (MissingPropertyException e) {
80 saltMasterPort = 8000
81 }
82 saltMasterHost = openstack.getHeatStackOutputParam(openstackCloud, HEAT_STACK_NAME, 'salt_master_ip', openstackEnv)
83 saltMasterUrl = "http://${saltMasterHost}:${saltMasterPort}"
84 saltMaster = salt.connection(saltMasterUrl, SALT_MASTER_CREDENTIALS)
85 }
Filip Pytloun0a07f702017-02-24 18:26:18 +010086
Filip Pytlounfd6726a2017-02-28 19:31:16 +010087 //
88 // Install
89 //
Filip Pytloun0a07f702017-02-24 18:26:18 +010090
Filip Pytlounfd6726a2017-02-28 19:31:16 +010091 stage('Install core infra') {
92 // salt.master, reclass
93 // refresh_pillar
94 // sync_all
95 // linux,openssh,salt.minion.ntp
Filip Pytloun0a07f702017-02-24 18:26:18 +010096
Filip Pytlounfd6726a2017-02-28 19:31:16 +010097 orchestrate.installFoundationInfra(saltMaster)
98 orchestrate.validateFoundationInfra(saltMaster)
99 }
Filip Pytloun23741982017-02-27 17:43:00 +0100100
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100101 stage("Deploy GlusterFS") {
102 salt.enforceState(saltMaster, 'I@glusterfs:server', 'glusterfs.server.service', true)
103 salt.enforceState(saltMaster, 'ci01*', 'glusterfs.server.setup', true)
104 sleep(5)
105 salt.enforceState(saltMaster, 'I@glusterfs:client', 'glusterfs.client', true)
106 print salt.cmdRun(saltMaster, 'I@glusterfs:client', 'mount|grep fuse.glusterfs || echo "Command failed"')
107 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100108
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100109 stage("Deploy GlusterFS") {
110 salt.enforceState(saltMaster, 'I@haproxy:proxy', 'haproxy,keepalived')
111 }
112
113 stage("Setup Docker Swarm") {
114 salt.enforceState(saltMaster, 'I@docker:host', 'docker.host', true)
115 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.swarm', true)
116 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'salt', true)
117 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.flush')
118 salt.runSaltProcessStep(saltMaster, 'I@docker:swarm:role:master', 'mine.update')
119 salt.enforceState(saltMaster, 'I@docker:swarm', 'docker.swarm', true)
120 print salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'docker node ls')
121 }
122
123 stage("Deploy Docker services") {
124 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
125
126 // XXX: Hack to fix dependency of gerrit on mysql
127 print salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', "docker service rm gerrit; sleep 5; rm -rf /srv/volumes/gerrit/*")
128 sleep(10)
Filip Pytlounc6d3bbf2017-03-01 08:48:02 +0100129 print salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', "apt-get install -y mysql-client; mysql -ppassword -h172.16.10.11 -P13306 -e'drop database gerrit;create database gerrit;'")
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100130 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'docker.client')
131 // ---- cut here (end of hack) ----
132
133 retry(30) {
134 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'""")
135 for (int a = 0; a < out['return'].size(); a++) {
136 def entry = out['return'].get(a)
137 for (int i = 0; i < entry.size(); i++) {
138 def node = entry.get(i)
139 if (node) {
140 if (node.value =~ /Some services are not running/) {
141 sleep(10)
142 throw new Exception("$node.key: $node.value")
143 } else {
144 print out
145 }
Filip Pytloun7e2a8af2017-02-28 15:36:29 +0100146 }
Filip Pytloun2a9d78d2017-02-27 19:53:21 +0100147 }
148 }
149 }
Filip Pytloune8657fc2017-02-28 21:46:13 +0100150 // Give services some time to settle
151 sleep(60)
Filip Pytloun0a07f702017-02-24 18:26:18 +0100152 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100153
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100154 stage("Configure CI/CD services") {
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100155 // Aptly
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100156 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'aptly', true)
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100157
158 // Gerrit
159 timeout(600) {
160 println "Waiting for Gerrit to come up.."
161 salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'while true; do curl -svf 172.16.10.254:8080 >/dev/null && break; done')
162 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100163 retry(2) {
164 // Needs to run twice to pass __virtual__ method of gerrit module
165 // after installation of dependencies
166 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'gerrit', true)
167 }
Filip Pytloun5b0954b2017-03-01 10:10:18 +0100168
169 // Jenkins
170 timeout(600) {
171 println "Waiting for Jenkins to come up.."
172 salt.cmdRun(saltMaster, 'I@docker:swarm:role:master', 'while true; do curl -svf 172.16.10.254:8081 >/dev/null && break; done')
173 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100174 retry(2) {
175 // Same for jenkins
176 salt.enforceState(saltMaster, 'I@docker:swarm:role:master', 'jenkins', true)
177 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100178 }
Filip Pytloun3d045f82017-03-01 09:44:52 +0100179
180 stage("Finalize") {
181 //
182 // Generate docs
183 //
184 try {
185 retry(3) {
186 salt.runSaltProcessStep(saltMaster, '*', 'state.orchestrate', ['sphinx.orch.generate_doc'])
187 }
188 } catch (Throwable e) {
189 // We don't want sphinx docs to ruin whole build, so possible
190 // errors are just ignored here
191 true
192 }
193 salt.enforceState(saltMaster, 'I@nginx:server', 'nginx')
194
195 if (HEAT_STACK_DELETE != 'true') {
196 println "============================================================"
197 println "Your CI/CD lab has been deployed and you can enjoy it:"
198 println " Use sshuttle -r ubuntu@${saltMasterHost} 172.16.10.0/24"
Filip Pytlounee0b8c62017-03-01 09:48:19 +0100199 println " to connect to your private subnet and visit services"
200 println " running at 172.16.10.254 (vip address):"
Filip Pytloun3d045f82017-03-01 09:44:52 +0100201 println " 9600 haproxy stats"
202 println " 8080 gerrit"
203 println " 8081 jenkins"
204 println " 8091 Docker swarm visualizer"
205 println " 8090 Reclass-generated documentation"
206 println ""
207 println "Don't forget to terminate your stack when you don't needed!"
208 println "============================================================"
209 }
210 }
Filip Pytlounfd6726a2017-02-28 19:31:16 +0100211 } catch (Throwable e) {
212 // If there was an error or exception thrown, the build failed
213 currentBuild.result = "FAILURE"
214 throw e
215 } finally {
216 // Cleanup
217 if (HEAT_STACK_DELETE == 'true') {
218 stage('Trigger cleanup job') {
219 build job: 'deploy_heat_cleanup', parameters: [[$class: 'StringParameterValue', name: 'HEAT_STACK_NAME', value: HEAT_STACK_NAME]]
220 }
Filip Pytloun23741982017-02-27 17:43:00 +0100221 }
Filip Pytlounf6e877f2017-02-28 19:38:16 +0100222 }
Filip Pytloun0a07f702017-02-24 18:26:18 +0100223}