1"""
2Configure a Pacemaker/Corosync cluster with PCS
3===============================================
4
5Configure Pacemaker/Cororsync clusters with the
6Pacemaker/Cororsync conifguration system (PCS)
7
8:depends: pcs
9
10.. versionadded:: 2016.3.0
11"""
12
13
14import logging
15
16import salt.utils.path
17
18log = logging.getLogger(__name__)
19
20
21def __virtual__():
22    """
23    Only load if pcs package is installed
24    """
25    if salt.utils.path.which("pcs"):
26        return "pcs"
27    return (False, "Missing dependency: pcs")
28
29
30def __use_new_commands():
31    """
32    The command line arguments of pcs changed after version 0.10
33    This will return True if the new arguments are needed and
34    false if the old ones are needed
35    """
36    pcs_version = __salt__["pkg.version"]("pcs")
37    log.debug("PCS package version %s", pcs_version)
38
39    if __salt__["pkg.version_cmp"](pcs_version, "0.10") == 1:
40        log.debug("New version, new command")
41        return True
42    else:
43        log.debug("Old Version")
44        return False
45
46
47def item_show(
48    item, item_id=None, item_type=None, show="show", extra_args=None, cibfile=None
49):
50    """
51    Show an item via pcs command
52    (mainly for use with the pcs state module)
53
54    item
55        config, property, resource, constraint etc.
56    item_id
57        id of the item
58    item_type
59        item type
60    show
61        show command (probably None, default: show or status for newer implementation)
62    extra_args
63        additional options for the pcs command
64    cibfile
65        use cibfile instead of the live CIB
66    """
67
68    new_commands = __use_new_commands()
69
70    cmd = ["pcs"]
71
72    if isinstance(cibfile, str):
73        cmd += ["-f", cibfile]
74
75    if isinstance(item, str):
76        cmd += [item]
77    elif isinstance(item, (list, tuple)):
78        cmd += item
79
80    # constraint command follows a different order
81    if item in ["constraint"]:
82        cmd += [item_type]
83
84    # New implementions use config instead of show. This resolves that issue.
85    if new_commands and (
86        item != "config" and item != "constraint" and item != "property"
87    ):
88        if show == "show":
89            show = "config"
90        elif isinstance(show, (list, tuple)):
91            for index, value in enumerate(show):
92                if show[index] == "show":
93                    show[index] = "config"
94
95    if isinstance(show, str):
96        cmd += [show]
97    elif isinstance(show, (list, tuple)):
98        cmd += show
99
100    if isinstance(item_id, str):
101        cmd += [item_id]
102
103    if isinstance(extra_args, (list, tuple)):
104        cmd += extra_args
105
106    # constraint command only shows id, when using '--full'-parameter
107    if item in ["constraint"]:
108        if not isinstance(extra_args, (list, tuple)) or "--full" not in extra_args:
109            cmd += ["--full"]
110    log.debug("Running item show %s", cmd)
111    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
112
113
114def item_create(
115    item, item_id, item_type, create="create", extra_args=None, cibfile=None
116):
117    """
118    Create an item via pcs command
119    (mainly for use with the pcs state module)
120
121    item
122        config, property, resource, constraint etc.
123    item_id
124        id of the item
125    item_type
126        item type
127    create
128        create command (create or set f.e., default: create)
129    extra_args
130        additional options for the pcs command
131    cibfile
132        use cibfile instead of the live CIB
133    """
134    cmd = ["pcs"]
135    if isinstance(cibfile, str):
136        cmd += ["-f", cibfile]
137
138    if isinstance(item, str):
139        cmd += [item]
140    elif isinstance(item, (list, tuple)):
141        cmd += item
142
143    # constraint command follows a different order
144    if item in ["constraint"]:
145        if isinstance(item_type, str):
146            cmd += [item_type]
147
148    if isinstance(create, str):
149        cmd += [create]
150    elif isinstance(create, (list, tuple)):
151        cmd += create
152
153    # constraint command needs item_id in format 'id=<id' after all params
154    # constraint command follows a different order
155    if item not in ["constraint"]:
156        cmd += [item_id]
157        if isinstance(item_type, str):
158            cmd += [item_type]
159
160    if isinstance(extra_args, (list, tuple)):
161        # constraint command needs item_id in format 'id=<id' after all params
162        if item in ["constraint"]:
163            extra_args = extra_args + ["id={}".format(item_id)]
164        cmd += extra_args
165
166    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
167
168
169def auth(nodes, pcsuser="hacluster", pcspasswd="hacluster", extra_args=None):
170    """
171    Authorize nodes to the cluster
172
173    nodes
174        a list of nodes which should be authorized to the cluster
175    pcsuser
176        user for communitcation with PCS (default: hacluster)
177    pcspasswd
178        password for pcsuser (default: hacluster)
179    extra_args
180        list of extra option for the \'pcs cluster auth\' command. The newer cluster host command has no extra args and so will ignore it.
181
182    CLI Example:
183
184    .. code-block:: bash
185
186        salt '*' pcs.auth nodes='[ node1.example.org node2.example.org ]' pcsuser=hacluster pcspasswd=hoonetorg extra_args=[ '--force' ]
187    """
188    if __use_new_commands():
189        cmd = ["pcs", "host", "auth"]
190    else:
191        cmd = ["pcs", "cluster", "auth"]
192
193    cmd.extend(["-u", pcsuser, "-p", pcspasswd])
194
195    if not __use_new_commands() and isinstance(extra_args, (list, tuple)):
196        cmd += extra_args
197
198    cmd += nodes
199
200    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
201
202
203def is_auth(nodes, pcsuser="hacluster", pcspasswd="hacluster"):
204    """
205    Check if nodes are already authorized
206
207    nodes
208        a list of nodes to be checked for authorization to the cluster
209    pcsuser
210        user for communitcation with PCS (default: hacluster)
211    pcspasswd
212        password for pcsuser (default: hacluster)
213
214    CLI Example:
215
216    .. code-block:: bash
217
218        salt '*' pcs.is_auth nodes='[node1.example.org node2.example.org]' pcsuser=hacluster pcspasswd=hoonetorg
219    """
220    if __use_new_commands():
221        cmd = ["pcs", "host", "auth", "-u", pcsuser, "-p", pcspasswd]
222    else:
223        cmd = ["pcs", "cluster", "auth"]
224
225    cmd += nodes
226
227    return __salt__["cmd.run_all"](
228        cmd, stdin="\n\n", output_loglevel="trace", python_shell=False
229    )
230
231
232def cluster_setup(nodes, pcsclustername="pcscluster", extra_args=None):
233    """
234    Setup pacemaker cluster via pcs command
235
236    nodes
237        a list of nodes which should be set up
238    pcsclustername
239        Name of the Pacemaker cluster (default: pcscluster)
240    extra_args
241        list of extra option for the \'pcs cluster setup\' command
242
243    CLI Example:
244
245    .. code-block:: bash
246
247        salt '*' pcs.cluster_setup nodes='[ node1.example.org node2.example.org ]' pcsclustername=pcscluster
248    """
249    cmd = ["pcs", "cluster", "setup"]
250
251    if __use_new_commands():
252        cmd += [pcsclustername]
253    else:
254        cmd += ["--name", pcsclustername]
255
256    cmd += nodes
257    if isinstance(extra_args, (list, tuple)):
258        cmd += extra_args
259
260    log.debug("Running cluster setup: %s", cmd)
261
262    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
263
264
265def cluster_destroy(extra_args=None):
266    """
267    Destroy corosync cluster using the pcs command
268
269    extra_args
270        list of extra option for the \'pcs cluster destroy\' command (only really --all)
271
272    CLI Example:
273
274    .. code-block:: bash
275
276        salt '*' pcs.cluster_destroy extra_args=--all
277    """
278    cmd = ["pcs", "cluster", "destroy"]
279
280    if isinstance(extra_args, (list, tuple)):
281        cmd += extra_args
282
283    log.debug("Running cluster destroy: %s", cmd)
284
285    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
286
287
288def cluster_node_add(node, extra_args=None):
289    """
290    Add a node to the pacemaker cluster via pcs command
291
292    node
293        node that should be added
294    extra_args
295        list of extra option for the \'pcs cluster node add\' command
296
297    CLI Example:
298
299    .. code-block:: bash
300
301        salt '*' pcs.cluster_node_add node=node2.example.org
302    """
303    cmd = ["pcs", "cluster", "node", "add"]
304
305    cmd += [node]
306    if isinstance(extra_args, (list, tuple)):
307        cmd += extra_args
308
309    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
310
311
312def cib_create(cibfile, scope="configuration", extra_args=None):
313    """
314    Create a CIB-file from the current CIB of the cluster
315
316    cibfile
317        name/path of the file containing the CIB
318    scope
319        specific section of the CIB (default: configuration)
320    extra_args
321        additional options for creating the CIB-file
322
323    CLI Example:
324
325    .. code-block:: bash
326
327        salt '*' pcs.cib_create cibfile='/tmp/VIP_apache_1.cib' scope=False
328    """
329    cmd = ["pcs", "cluster", "cib", cibfile]
330    if isinstance(scope, str):
331        cmd += ["scope={}".format(scope)]
332    if isinstance(extra_args, (list, tuple)):
333        cmd += extra_args
334
335    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
336
337
338def cib_push(cibfile, scope="configuration", extra_args=None):
339    """
340    Push a CIB-file as the new CIB to the cluster
341
342    cibfile
343        name/path of the file containing the CIB
344    scope
345        specific section of the CIB (default: configuration)
346    extra_args
347        additional options for creating the CIB-file
348
349    CLI Example:
350
351    .. code-block:: bash
352
353        salt '*' pcs.cib_push cibfile='/tmp/VIP_apache_1.cib' scope=False
354    """
355    cmd = ["pcs", "cluster", "cib-push", cibfile]
356    if isinstance(scope, str):
357        cmd += ["scope={}".format(scope)]
358    if isinstance(extra_args, (list, tuple)):
359        cmd += extra_args
360
361    return __salt__["cmd.run_all"](cmd, output_loglevel="trace", python_shell=False)
362
363
364def config_show(cibfile=None):
365    """
366    Show config of cluster
367
368    cibfile
369        name/path of the file containing the CIB
370
371    CLI Example:
372
373    .. code-block:: bash
374
375        salt '*' pcs.config_show cibfile='/tmp/cib_for_galera'
376    """
377    return item_show(item="config", item_id=None, extra_args=None, cibfile=cibfile)
378
379
380def prop_show(prop, extra_args=None, cibfile=None):
381    """
382    Show the value of a cluster property
383
384    prop
385        name of the property
386    extra_args
387        additional options for the pcs property command
388    cibfile
389        use cibfile instead of the live CIB
390
391    CLI Example:
392
393    .. code-block:: bash
394
395        salt '*' pcs.prop_show cibfile='/tmp/2_node_cluster.cib' prop='no-quorum-policy' cibfile='/tmp/2_node_cluster.cib'
396    """
397    return item_show(
398        item="property", item_id=prop, extra_args=extra_args, cibfile=cibfile
399    )
400
401
402def prop_set(prop, value, extra_args=None, cibfile=None):
403    """
404    Set the value of a cluster property
405
406    prop
407        name of the property
408    value
409        value of the property prop
410    extra_args
411        additional options for the pcs property command
412    cibfile
413        use cibfile instead of the live CIB
414
415    CLI Example:
416
417    .. code-block:: bash
418
419        salt '*' pcs.prop_set prop='no-quorum-policy' value='ignore' cibfile='/tmp/2_node_cluster.cib'
420    """
421    return item_create(
422        item="property",
423        item_id="{}={}".format(prop, value),
424        item_type=None,
425        create="set",
426        extra_args=extra_args,
427        cibfile=cibfile,
428    )
429
430
431def stonith_show(stonith_id, extra_args=None, cibfile=None):
432    """
433    Show the value of a cluster stonith
434
435    stonith_id
436        name for the stonith resource
437    extra_args
438        additional options for the pcs stonith command
439    cibfile
440        use cibfile instead of the live CIB
441
442    CLI Example:
443
444    .. code-block:: bash
445
446        salt '*' pcs.stonith_show stonith_id='eps_fence' cibfile='/tmp/2_node_cluster.cib'
447    """
448    return item_show(
449        item="stonith", item_id=stonith_id, extra_args=extra_args, cibfile=cibfile
450    )
451
452
453def stonith_create(
454    stonith_id, stonith_device_type, stonith_device_options=None, cibfile=None
455):
456    """
457    Create a stonith resource via pcs command
458
459    stonith_id
460        name for the stonith resource
461    stonith_device_type
462        name of the stonith agent fence_eps, fence_xvm f.e.
463    stonith_device_options
464        additional options for creating the stonith resource
465    cibfile
466        use cibfile instead of the live CIB for manipulation
467
468    CLI Example:
469
470    .. code-block:: bash
471
472        salt '*' pcs.stonith_create stonith_id='eps_fence' stonith_device_type='fence_eps'
473                                    stonith_device_options="['pcmk_host_map=node1.example.org:01;node2.example.org:02', 'ipaddr=myepsdevice.example.org', 'action=reboot', 'power_wait=5', 'verbose=1', 'debug=/var/log/pcsd/eps_fence.log', 'login=hidden', 'passwd=hoonetorg']" cibfile='/tmp/cib_for_stonith.cib'
474    """
475    return item_create(
476        item="stonith",
477        item_id=stonith_id,
478        item_type=stonith_device_type,
479        extra_args=stonith_device_options,
480        cibfile=cibfile,
481    )
482
483
484def resource_show(resource_id, extra_args=None, cibfile=None):
485    """
486    Show a resource via pcs command
487
488    resource_id
489        name of the resource
490    extra_args
491        additional options for the pcs command
492    cibfile
493        use cibfile instead of the live CIB
494
495    CLI Example:
496
497    .. code-block:: bash
498
499        salt '*' pcs.resource_show resource_id='galera' cibfile='/tmp/cib_for_galera.cib'
500    """
501    return item_show(
502        item="resource", item_id=resource_id, extra_args=extra_args, cibfile=cibfile
503    )
504
505
506def resource_create(resource_id, resource_type, resource_options=None, cibfile=None):
507    """
508    Create a resource via pcs command
509
510    resource_id
511        name for the resource
512    resource_type
513        resource type (f.e. ocf:heartbeat:IPaddr2 or VirtualIP)
514    resource_options
515        additional options for creating the resource
516    cibfile
517        use cibfile instead of the live CIB for manipulation
518
519    CLI Example:
520
521    .. code-block:: bash
522
523        salt '*' pcs.resource_create resource_id='galera' resource_type='ocf:heartbeat:galera' resource_options="['wsrep_cluster_address=gcomm://node1.example.org,node2.example.org,node3.example.org', '--master']" cibfile='/tmp/cib_for_galera.cib'
524    """
525    return item_create(
526        item="resource",
527        item_id=resource_id,
528        item_type=resource_type,
529        extra_args=resource_options,
530        cibfile=cibfile,
531    )
532