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