blob: 30b2f16b1c6a03c28d9dc51890e35f99403d62ae [file] [log] [blame]
Sergey Kolekonovba203982016-12-21 18:32:17 +04001package com.mirantis.mk
2
3/**
4 *
5 * SaltStack functions
6 *
7 */
8
9/**
10 * Login to Salt API and return auth token
11 *
12 * @param url Salt API server URL
13 * @param params Salt connection params
14 */
15def getSaltToken(url, params) {
16 def http = new com.mirantis.mk.http()
17 data = [
18 'username': params.creds.username,
19 'password': params.creds.password.toString(),
20 'eauth': 'pam'
21 ]
22 authToken = http.sendHttpGetRequest("${url}/login", data, ['Accept': '*/*'])['return'][0]['token']
23 return authToken
24}
25
26/**
27 * Salt connection and context parameters
28 *
29 * @param url Salt API server URL
30 * @param credentialsID ID of credentials store entry
31 */
32def createSaltConnection(url, credentialsId) {
33 def common = new com.mirantis.mk.common()
34 params = [
35 "url": url,
36 "credentialsId": credentialsId,
37 "authToken": null,
38 "creds": common.getPasswordCredentials(credentialsId)
39 ]
40 params["authToken"] = getSaltToken(url, params)
41
42 return params
43}
44
45/**
46 * Run action using Salt API
47 *
48 * @param master Salt connection object
49 * @param client Client type
50 * @param target Target specification, eg. for compound matches by Pillar
51 * data: ['expression': 'I@openssh:server', 'type': 'compound'])
52 * @param function Function to execute (eg. "state.sls")
53 * @param args Additional arguments to function
54 * @param kwargs Additional key-value arguments to function
55 */
56def runSaltCommand(master, client, target, function, args = null, kwargs = null) {
Ales Komarekbfd10f42017-01-03 13:40:12 +010057 def http = new com.mirantis.mk.http()
58
Sergey Kolekonovba203982016-12-21 18:32:17 +040059 data = [
60 'tgt': target.expression,
61 'fun': function,
62 'client': client,
63 'expr_form': target.type,
64 ]
65
66 if (args) {
67 data['arg'] = args
68 }
69 if (kwargs) {
70 data['kwarg'] = kwargs
71 }
72
73 headers = [
74 'X-Auth-Token': "${master.authToken}"
75 ]
76
77 return http.sendHttpPostRequest("${master.url}/", data, headers)
78}
79
80def getSaltPillar(master, target, pillar) {
81 def out = runSaltCommand(master, 'local', target, 'pillar.get', [pillar.replace('.', ':')])
82 return out
83}
84
85def enforceSaltState(master, target, state, output = false) {
86 def run_states
87 if (state instanceof String) {
88 run_states = state
89 } else {
90 run_states = state.join(',')
91 }
92
93 def out = runSaltCommand(master, 'local', target, 'state.sls', [run_states])
94 try {
95 checkSaltResult(out)
96 } finally {
97 if (output == true) {
98 printSaltResult(out)
99 }
100 }
101 return out
102}
103
104def runSaltCmd(master, target, cmd) {
105 return runSaltCommand(master, 'local', target, 'cmd.run', [cmd])
106}
107
108def syncSaltAll(master, target) {
109 return runSaltCommand(master, 'local', target, 'saltutil.sync_all')
110}
111
112def enforceSaltApply(master, target, output = false) {
113 def out = runSaltCommand(master, 'local', target, 'state.highstate')
114 try {
115 checkSaltResult(out)
116 } finally {
117 if (output == true) {
118 printSaltResult(out)
119 }
120 }
121 return out
122}
123
124def generateSaltNodeKey(master, target, host, keysize = 4096) {
125 args = [host]
126 kwargs = ['keysize': keysize]
127 return runSaltCommand(master, 'wheel', target, 'key.gen_accept', args, kwargs)
128}
129
130def generateSaltNodeMetadata(master, target, host, classes, parameters) {
131 args = [host, '_generated']
132 kwargs = ['classes': classes, 'parameters': parameters]
133 return runSaltCommand(master, 'local', target, 'reclass.node_create', args, kwargs)
134}
135
136def orchestrateSaltSystem(master, target, orchestrate) {
137 return runSaltCommand(master, 'runner', target, 'state.orchestrate', [orchestrate])
138}
139
140/**
141 * Check result for errors and throw exception if any found
142 *
143 * @param result Parsed response of Salt API
144 */
145def checkSaltResult(result) {
146 for (entry in result['return']) {
147 if (!entry) {
148 throw new Exception("Salt API returned empty response: ${result}")
149 }
150 for (node in entry) {
151 for (resource in node.value) {
152 if (resource instanceof String || resource.value.result.toString().toBoolean() != true) {
153 throw new Exception("Salt state on node ${node.key} failed: ${node.value}")
154 }
155 }
156 }
157 }
158}
159
160/**
161 * Print Salt run results in human-friendly form
162 *
163 * @param result Parsed response of Salt API
164 * @param onlyChanges If true (default), print only changed resources
165 * @param raw Simply pretty print what we have, no additional
166 * parsing
167 */
168def printSaltResult(result, onlyChanges = true, raw = false) {
169 if (raw == true) {
170 print new groovy.json.JsonBuilder(result).toPrettyString()
171 } else {
172 def out = [:]
173 for (entry in result['return']) {
174 for (node in entry) {
175 out[node.key] = [:]
176 for (resource in node.value) {
177 if (resource instanceof String) {
178 out[node.key] = node.value
179 } else if (resource.value.result.toString().toBoolean() == false || resource.value.changes || onlyChanges == false) {
180 out[node.key][resource.key] = resource.value
181 }
182 }
183 }
184 }
185
186 for (node in out) {
187 if (node.value) {
188 println "Node ${node.key} changes:"
189 print new groovy.json.JsonBuilder(node.value).toPrettyString()
190 } else {
191 println "No changes for node ${node.key}"
192 }
193 }
194 }
195}
196
197@NonCPS
198def getSaltProcess(saltProcess) {
199
200 def process_def = [
201 'validate_foundation_infra': [
202 [tgt: 'I@salt:master', fun: 'cmd.run', arg: ['salt-key']],
203 [tgt: 'I@salt:minion', fun: 'test.version'],
204 [tgt: 'I@salt:master', fun: 'cmd.run', arg: ['reclass-salt --top']],
205 [tgt: 'I@reclass:storage', fun: 'reclass.inventory'],
206 [tgt: 'I@salt:minion', fun: 'state.show_top'],
207 ],
208 'install_foundation_infra': [
209 [tgt: 'I@salt:master', fun: 'state.sls', arg: ['salt.master,reclass']],
210 [tgt: 'I@linux:system', fun: 'saltutil.refresh_pillar'],
211 [tgt: 'I@linux:system', fun: 'saltutil.sync_all'],
212 [tgt: 'I@linux:system', fun: 'state.sls', arg: ['linux,openssh,salt.minion,ntp']],
213 ],
214 'install_openstack_mk_infra': [
215 // Install keepaliveds
216 [tgt: 'I@keepalived:cluster', fun: 'state.sls', arg: ['keepalived'], batch:1],
217 // Check the keepalived VIPs
218 [tgt: 'I@keepalived:cluster', fun: 'cmd.run', arg: ['ip a | grep 172.16.10.2']],
219 // Install glusterfs
220 [tgt: 'I@glusterfs:server', fun: 'state.sls', arg: ['glusterfs.server.service']],
221 [tgt: 'I@glusterfs:server', fun: 'state.sls', arg: ['glusterfs.server.setup'], batch:1],
222 [tgt: 'I@glusterfs:server', fun: 'cmd.run', arg: ['gluster peer status']],
223 [tgt: 'I@glusterfs:server', fun: 'cmd.run', arg: ['gluster volume status']],
224 // Install rabbitmq
225 [tgt: 'I@rabbitmq:server', fun: 'state.sls', arg: ['rabbitmq']],
226 // Check the rabbitmq status
227 [tgt: 'I@rabbitmq:server', fun: 'cmd.run', arg: ['rabbitmqctl cluster_status']],
228 // Install galera
229 [tgt: 'I@galera:master', fun: 'state.sls', arg: ['galera']],
230 [tgt: 'I@galera:slave', fun: 'state.sls', arg: ['galera']],
231 // Check galera status
232 [tgt: 'I@galera:master', fun: 'mysql.status'],
233 [tgt: 'I@galera:slave', fun: 'mysql.status'],
234 // Install haproxy
235 [tgt: 'I@haproxy:proxy', fun: 'state.sls', arg: ['haproxy']],
236 [tgt: 'I@haproxy:proxy', fun: 'service.status', arg: ['haproxy']],
237 [tgt: 'I@haproxy:proxy', fun: 'service.restart', arg: ['rsyslog']],
238 // Install memcached
239 [tgt: 'I@memcached:server', fun: 'state.sls', arg: ['memcached']],
240 ],
241 'install_openstack_mk_control': [
242 // setup keystone service
243 [tgt: 'I@keystone:server', fun: 'state.sls', arg: ['keystone.server'], batch:1],
244 // populate keystone services/tenants/roles/users
245 [tgt: 'I@keystone:client', fun: 'state.sls', arg: ['keystone.client']],
246 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; keystone service-list']],
247 // Install glance and ensure glusterfs clusters
248 [tgt: 'I@glance:server', fun: 'state.sls', arg: ['glance.server'], batch:1],
249 [tgt: 'I@glance:server', fun: 'state.sls', arg: ['glusterfs.client']],
250 // Update fernet tokens before doing request on keystone server
251 [tgt: 'I@keystone:server', fun: 'state.sls', arg: ['keystone.server']],
252 // Check glance service
253 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; glance image-list']],
254 // Install and check nova service
255 [tgt: 'I@nova:controller', fun: 'state.sls', arg: ['nova'], batch:1],
256 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; nova service-list']],
257 // Install and check cinder service
258 [tgt: 'I@cinder:controller', fun: 'state.sls', arg: ['cinder'], batch:1],
259 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; cinder list']],
260 // Install neutron service
261 [tgt: 'I@neutron:server', fun: 'state.sls', arg: ['neutron'], batch:1],
262 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; neutron agent-list']],
263 // Install heat service
264 [tgt: 'I@heat:server', fun: 'state.sls', arg: ['heat'], batch:1],
265 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; heat resource-type-list']],
266 // Install horizon dashboard
267 [tgt: 'I@horizon:server', fun: 'state.sls', arg: ['horizon']],
268 [tgt: 'I@nginx:server', fun: 'state.sls', arg: ['nginx']],
269 ],
270 'install_openstack_mk_network': [
271 // Install opencontrail database services
272 [tgt: 'I@opencontrail:database', fun: 'state.sls', arg: ['opencontrail.database'], batch:1],
273 // Install opencontrail control services
274 [tgt: 'I@opencontrail:control', fun: 'state.sls', arg: ['opencontrail'], batch:1],
275 // Provision opencontrail control services
276 [tgt: 'I@opencontrail:control:id:1', fun: 'cmd.run', arg: ['/usr/share/contrail-utils/provision_control.py --api_server_ip 172.16.10.254 --api_server_port 8082 --host_name ctl01 --host_ip 172.16.10.101 --router_asn 64512 --admin_password workshop --admin_user admin --admin_tenant_name admin --oper add']],
277 [tgt: 'I@opencontrail:control:id:1', fun: 'cmd.run', arg: ['/usr/share/contrail-utils/provision_control.py --api_server_ip 172.16.10.254 --api_server_port 8082 --host_name ctl02 --host_ip 172.16.10.102 --router_asn 64512 --admin_password workshop --admin_user admin --admin_tenant_name admin --oper add']],
278 [tgt: 'I@opencontrail:control:id:1', fun: 'cmd.run', arg: ['/usr/share/contrail-utils/provision_control.py --api_server_ip 172.16.10.254 --api_server_port 8082 --host_name ctl03 --host_ip 172.16.10.103 --router_asn 64512 --admin_password workshop --admin_user admin --admin_tenant_name admin --oper add']],
279 // Test opencontrail
280 [tgt: 'I@opencontrail:control', fun: 'cmd.run', arg: ['contrail-status']],
281 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; neutron net-list']],
282 [tgt: 'I@keystone:server', fun: 'cmd.run', arg: ['. /root/keystonerc; nova net-list']],
283 ],
284 'install_openstack_mk_compute': [
285 // Configure compute nodes
286 [tgt: 'I@nova:compute', fun: 'state.apply'],
287 [tgt: 'I@nova:compute', fun: 'state.apply'],
288 // Provision opencontrail virtual routers
289 [tgt: 'I@opencontrail:control:id:1', fun: 'cmd.run', arg: ['/usr/share/contrail-utils/provision_vrouter.py --host_name cmp01 --host_ip 172.16.10.105 --api_server_ip 172.16.10.254 --oper add --admin_user admin --admin_password workshop --admin_tenant_name admin']],
290 [tgt: 'I@nova:compute', fun: 'system.reboot'],
291 ],
292 'install_openstack_mcp_infra': [
293 // Comment nameserver
294 [tgt: 'I@kubernetes:master', fun: 'cmd.run', arg: ["sed -i 's/nameserver 10.254.0.10/#nameserver 10.254.0.10/g' /etc/resolv.conf"]],
295 // Install glusterfs
296 [tgt: 'I@glusterfs:server', fun: 'state.sls', arg: ['glusterfs.server.service']],
297 // Install keepalived
298 [tgt: 'I@keepalived:cluster', fun: 'state.sls', arg: ['keepalived'], batch:1],
299 // Check the keepalived VIPs
300 [tgt: 'I@keepalived:cluster', fun: 'cmd.run', arg: ['ip a | grep 172.16.10.2']],
301 // Setup glusterfs
302 [tgt: 'I@glusterfs:server', fun: 'state.sls', arg: ['glusterfs.server.setup'], batch:1],
303 [tgt: 'I@glusterfs:server', fun: 'cmd.run', arg: ['gluster peer status']],
304 [tgt: 'I@glusterfs:server', fun: 'cmd.run', arg: ['gluster volume status']],
305 // Install haproxy
306 [tgt: 'I@haproxy:proxy', fun: 'state.sls', arg: ['haproxy']],
307 [tgt: 'I@haproxy:proxy', fun: 'service.status', arg: ['haproxy']],
308 // Install docker
309 [tgt: 'I@docker:host', fun: 'state.sls', arg: ['docker.host']],
310 [tgt: 'I@docker:host', fun: 'cmd.run', arg: ['docker ps']],
311 // Install bird
312 [tgt: 'I@bird:server', fun: 'state.sls', arg: ['bird']],
313 // Install etcd
314 [tgt: 'I@etcd:server', fun: 'state.sls', arg: ['etcd.server.service']],
315 [tgt: 'I@etcd:server', fun: 'cmd.run', arg: ['etcdctl cluster-health']],
316 ],
Ales Komarekc000c152016-12-23 15:32:54 +0100317 'install_openstack_mcp_control': [
318 // Pull Calico image
319 [tgt: 'I@kubernetes:pool', fun: 'dockerng.pull', arg: ['calico/node:latest']],
320 // Install Kubernetes and Calico
321 [tgt: 'I@kubernetes:master', fun: 'state.sls', arg: ['kubernetes.master.service,kubernetes.master.kube-addons']],
322 [tgt: 'I@kubernetes:pool', fun: 'state.sls', arg: ['kubernetes.pool']],
323 [tgt: 'I@kubernetes:pool', fun: 'cmd.run', arg: ['calicoctl status']],
324 // Setup NAT for Calico
325 [tgt: 'I@kubernetes:master', fun: 'state.sls', arg: ['etcd.server.setup']],
326 // Run whole k8s controller
327 [tgt: 'I@kubernetes:master', fun: 'state.sls', arg: ['kubernetes.controller']],
328 // Run whole k8s controller
329 [tgt: 'I@kubernetes:master', fun: 'state.sls', arg: ['kubernetes'], batch: 1],
330 // Revert comment nameserver
331 [tgt: 'I@kubernetes:master', fun: 'cmd.run', arg: ["sed -i 's/nameserver 10.254.0.10/#nameserver 10.254.0.10/g' /etc/resolv.conf"]],
332 ],
333 'install_openstack_mcp_compute': [
334 // Install opencontrail
335 [tgt: 'I@opencontrail:compute', fun: 'state.sls', arg: ['opencontrail']],
336 // Reboot compute nodes
337 [tgt: 'I@opencontrail:compute', fun: 'system.reboot'],
338 ],
Sergey Kolekonovba203982016-12-21 18:32:17 +0400339 'install_stacklight_control': [
340 [tgt: 'I@elasticsearch:server', fun: 'state.sls', arg: ['elasticsearch.server'], batch:1],
341 [tgt: 'I@influxdb:server', fun: 'state.sls', arg: ['influxdb'], batch:1],
342 [tgt: 'I@kibana:server', fun: 'state.sls', arg: ['kibana.server'], batch:1],
343 [tgt: 'I@grafana:server', fun: 'state.sls', arg: ['grafana'], batch:1],
344 [tgt: 'I@nagios:server', fun: 'state.sls', arg: ['nagios'], batch:1],
345 [tgt: 'I@elasticsearch:client', fun: 'state.sls', arg: ['elasticsearch.client'], batch:1],
346 [tgt: 'I@kibana:client', fun: 'state.sls', arg: ['kibana.client'], batch:1],
347 ],
348 'install_stacklight_client': [
349 ]
350 ]
351 return process_def[saltProcess]
352}
353
354/**
355 * Run predefined salt process
356 *
357 * @param master Salt connection object
358 * @param process Process name to be run
359 */
360def runSaltProcess(master, process) {
361
362 tasks = getSaltProcess(process)
363
364 for (i = 0; i <tasks.size(); i++) {
365 task = tasks[i]
Ales Komarekfc65ea72017-01-03 12:13:36 +0100366 echo("[Salt master ${master.url}] Task ${task}")
Sergey Kolekonovba203982016-12-21 18:32:17 +0400367 if (task.containsKey('arg')) {
368 result = runSaltCommand(master, 'local', ['expression': task.tgt, 'type': 'compound'], task.fun, task.arg)
369 }
370 else {
371 result = runSaltCommand(master, 'local', ['expression': task.tgt, 'type': 'compound'], task.fun)
372 }
373 if (task.fun == 'state.sls') {
374 printSaltResult(result, false)
375 }
376 else {
377 echo("${result}")
378 }
379 }
380}
381
382/**
383 * Print Salt state run results in human-friendly form
384 *
385 * @param result Parsed response of Salt API
386 * @param onlyChanges If true (default), print only changed resources
387 * parsing
388 */
389def printSaltStateResult(result, onlyChanges = true) {
390 def out = [:]
391 for (entry in result['return']) {
392 for (node in entry) {
393 out[node.key] = [:]
394 for (resource in node.value) {
395 if (resource instanceof String) {
396 out[node.key] = node.value
397 } else if (resource.value.result.toString().toBoolean() == false || resource.value.changes || onlyChanges == false) {
398 out[node.key][resource.key] = resource.value
399 }
400 }
401 }
402 }
403
404 for (node in out) {
405 if (node.value) {
406 println "Node ${node.key} changes:"
407 print new groovy.json.JsonBuilder(node.value).toPrettyString()
408 } else {
409 println "No changes for node ${node.key}"
410 }
411 }
412}
413
414/**
415 * Print Salt state run results in human-friendly form
416 *
417 * @param result Parsed response of Salt API
418 * @param onlyChanges If true (default), print only changed resources
419 * parsing
420 */
421def printSaltCommandResult(result, onlyChanges = true) {
422 def out = [:]
423 for (entry in result['return']) {
424 for (node in entry) {
425 out[node.key] = [:]
426 for (resource in node.value) {
427 out[node.key] = node.value
428 }
429 }
430 }
431
432 for (node in out) {
433 if (node.value) {
434 println "Node ${node.key} changes:"
435 print new groovy.json.JsonBuilder(node.value).toPrettyString()
436 } else {
437 println "No changes for node ${node.key}"
438 }
439 }
440}