1# Copyright (C) 2008-2011 Dejan Muhamedagic <dmuhamedagic@suse.de>
2# See COPYING for license information.
3
4import os
5import subprocess
6import copy
7import re
8import glob
9from lxml import etree
10from . import cache
11from . import constants
12from . import config
13from . import options
14from . import userdir
15from . import utils
16from .utils import stdout2list, is_program, is_process, to_ascii
17from .utils import os_types_list, get_stdout, find_value
18from .utils import crm_msec, crm_time_cmp
19from .msg import common_debug, common_err, common_warn, common_info
20
21#
22# Resource Agents interface (meta-data, parameters, etc)
23#
24
25lrmadmin_prog = "lrmadmin"
26
27
28def lrmadmin(opts, xml=False):
29    """
30    Get information directly from lrmd using lrmadmin.
31    """
32    _rc, l = stdout2list("%s %s" % (lrmadmin_prog, opts))
33    if l and not xml:
34        l = l[1:]  # skip the first line
35    return l
36
37
38def crm_resource(opts):
39    '''
40    Get information from crm_resource.
41    '''
42    _rc, l = stdout2list("crm_resource %s" % opts, stderr_on=False)
43    return l
44
45
46@utils.memoize
47def can_use_lrmadmin():
48    from distutils import version
49    # after this glue release all users can get meta-data and
50    # similar from lrmd
51    minimum_glue = "1.0.10"
52    _rc, glue_ver = get_stdout("%s -v" % lrmadmin_prog, stderr_on=False)
53    if not glue_ver:  # lrmadmin probably not found
54        return False
55    v_min = version.LooseVersion(minimum_glue)
56    v_this = version.LooseVersion(glue_ver)
57    if v_this < v_min:
58        return False
59    if userdir.getuser() not in ("root", config.path.crm_daemon_user):
60        return False
61    if not (is_program(lrmadmin_prog) and is_process(pacemaker_execd())):
62        return False
63    return utils.ext_cmd(">/dev/null 2>&1 %s -C" % lrmadmin_prog) == 0
64
65
66@utils.memoize
67def can_use_crm_resource():
68    _rc, s = get_stdout("crm_resource --list-standards", stderr_on=False)
69    return s != ""
70
71
72def ra_classes():
73    '''
74    List of RA classes.
75    '''
76    if cache.is_cached("ra_classes"):
77        return cache.retrieve("ra_classes")
78    if can_use_crm_resource():
79        l = crm_resource("--list-standards")
80    elif can_use_lrmadmin():
81        l = lrmadmin("-C")
82    else:
83        l = ["heartbeat", "lsb", "nagios", "ocf", "stonith", "systemd"]
84    l.sort()
85    return cache.store("ra_classes", l)
86
87
88def ra_providers(ra_type, ra_class="ocf"):
89    'List of providers for a class:type.'
90    ident = "ra_providers-%s-%s" % (ra_class, ra_type)
91    if cache.is_cached(ident):
92        return cache.retrieve(ident)
93    if can_use_crm_resource():
94        if ra_class != "ocf":
95            common_err("no providers for class %s" % ra_class)
96            return []
97        l = crm_resource("--list-ocf-alternatives %s" % ra_type)
98    elif can_use_lrmadmin():
99        l = lrmadmin("-P %s %s" % (ra_class, ra_type), True)
100    else:
101        l = []
102        if ra_class == "ocf":
103            for s in glob.glob("%s/resource.d/*/%s" % (os.environ["OCF_ROOT"], ra_type)):
104                a = s.split("/")
105                if len(a) == 7:
106                    l.append(a[5])
107    l.sort()
108    return cache.store(ident, l)
109
110
111def ra_providers_all(ra_class="ocf"):
112    '''
113    List of providers for a class.
114    '''
115    if ra_class != "ocf":
116        return []
117    ident = "ra_providers_all-%s" % ra_class
118    if cache.is_cached(ident):
119        return cache.retrieve(ident)
120    ocf = os.path.join(os.environ["OCF_ROOT"], "resource.d")
121    if os.path.isdir(ocf):
122        return cache.store(ident, sorted(s for s in os.listdir(ocf)
123                                         if os.path.isdir(os.path.join(ocf, s))))
124    return []
125
126
127def os_types(ra_class):
128    'List of types for a class.'
129    def stonith_types():
130        rc, l = stdout2list("stonith -L")
131        if rc != 0:
132            # stonith(8) may not be installed
133            common_debug("stonith exited with code %d" % rc)
134            l = []
135        for ra in os_types_list("/usr/sbin/fence_*"):
136            if ra not in ("fence_ack_manual", "fence_pcmk", "fence_legacy"):
137                l.append(ra)
138        return l
139
140    def systemd_types():
141        l = []
142        rc, lines = stdout2list("systemctl list-unit-files --full")
143        if rc != 0:
144            return l
145        t = re.compile(r'^(.+)\.service')
146        for line in lines:
147            m = t.search(line)
148            if m:
149                l.append(m.group(1))
150        return l
151
152    l = []
153    if ra_class == "ocf":
154        l = os_types_list("%s/resource.d/*/*" % (os.environ["OCF_ROOT"]))
155    elif ra_class == "lsb":
156        l = os_types_list("/etc/init.d/*")
157    elif ra_class == "stonith":
158        l = stonith_types()
159    elif ra_class == "nagios":
160        l = [x.replace("check_", "")
161             for x in os_types_list("%s/check_*" % config.path.nagios_plugins)]
162    elif ra_class == "systemd":
163        l = systemd_types()
164    l = list(set(l))
165    l.sort()
166    return l
167
168
169def ra_types(ra_class="ocf", ra_provider=""):
170    '''
171    List of RA type for a class.
172    '''
173
174    def find_types():
175        """
176        Actually go out and ask for the types of a class.
177        """
178        if can_use_crm_resource():
179            l = crm_resource("--list-agents %s" % ra_class)
180        elif can_use_lrmadmin():
181            l = lrmadmin("-T %s" % ra_class)
182        else:
183            l = os_types(ra_class)
184        return l
185
186    if not ra_class:
187        ra_class = "ocf"
188    ident = "ra_types-%s-%s" % (ra_class, ra_provider)
189    if cache.is_cached(ident):
190        return cache.retrieve(ident)
191
192    if not ra_provider:
193        def include(ra):
194            return True
195    else:
196        def include(ra):
197            return ra_provider in ra_providers(ra, ra_class)
198    return cache.store(ident, sorted(list(set(ra for ra in find_types() if include(ra)))))
199
200
201@utils.memoize
202def ra_meta(ra_class, ra_type, ra_provider):
203    """
204    Return metadata for the given class/type/provider
205    """
206    if can_use_crm_resource():
207        if ra_provider:
208            return crm_resource("--show-metadata %s:%s:%s" % (ra_class, ra_provider, ra_type))
209        return crm_resource("--show-metadata %s:%s" % (ra_class, ra_type))
210    elif can_use_lrmadmin():
211        return lrmadmin("-M %s %s %s" % (ra_class, ra_type, ra_provider), True)
212    else:
213        l = []
214        if ra_class == "ocf":
215            _rc, l = stdout2list("%s/resource.d/%s/%s meta-data" %
216                                 (os.environ["OCF_ROOT"], ra_provider, ra_type))
217        elif ra_class == "stonith":
218            if ra_type.startswith("fence_") and os.path.exists("/usr/sbin/%s" % ra_type):
219                _rc, l = stdout2list("/usr/sbin/%s -o metadata" % ra_type)
220            else:
221                _rc, l = stdout2list("stonith -m -t %s" % ra_type)
222        elif ra_class == "nagios":
223            _rc, l = stdout2list("%s/check_%s --metadata" %
224                                 (config.path.nagios_plugins, ra_type))
225        return l
226
227
228@utils.memoize
229def get_pe_meta():
230    return RAInfo(utils.pacemaker_schedulerd(), "metadata")
231
232
233@utils.memoize
234def get_crmd_meta():
235    return RAInfo(utils.pacemaker_controld(), "metadata",
236                  exclude_from_completion=constants.crmd_metadata_do_not_complete)
237
238
239@utils.memoize
240def get_stonithd_meta():
241    return RAInfo(utils.pacemaker_fenced(), "metadata")
242
243
244@utils.memoize
245def get_cib_meta():
246    return RAInfo(utils.pacemaker_based(), "metadata")
247
248
249@utils.memoize
250def get_properties_meta():
251    meta = copy.deepcopy(get_crmd_meta())
252    meta.add_ra_params(get_pe_meta())
253    meta.add_ra_params(get_cib_meta())
254    return meta
255
256
257@utils.memoize
258def get_properties_list():
259    try:
260        return list(get_properties_meta().params().keys())
261    except:
262        return []
263
264
265def prog_meta(prog):
266    '''
267    Do external program metadata.
268    '''
269    prog = utils.pacemaker_daemon(prog)
270    if prog:
271        rc, l = stdout2list("%s metadata" % prog)
272        if rc == 0:
273            return l
274        common_debug("%s metadata exited with code %d" % (prog, rc))
275    return []
276
277
278def get_nodes_text(n, tag):
279    try:
280        return n.findtext(tag).strip()
281    except:
282        return ''
283
284
285def mk_monitor_name(role, depth):
286    depth = ("_%s" % depth) if depth != "0" else ""
287    return role and role != "Started" and \
288        "monitor_%s%s" % (role, depth) or \
289        "monitor%s" % depth
290
291
292def monitor_name_node(node):
293    depth = node.get("depth") or '0'
294    role = node.get("role")
295    return mk_monitor_name(role, depth)
296
297
298def monitor_name_pl(pl):
299    depth = find_value(pl, "depth") or '0'
300    role = find_value(pl, "role")
301    return mk_monitor_name(role, depth)
302
303
304def _param_type_default(n):
305    """
306    Helper function to get (type, default) from XML parameter node
307    """
308    try:
309        content = n.find("content")
310        return content.get("type"), content.get("default")
311    except:
312        return None, None
313
314
315class RAInfo(object):
316    '''
317    A resource agent and whatever's useful about it.
318    '''
319    ra_tab = "    "  # four horses
320    required_ops = ("start", "stop")
321    skip_ops = ("meta-data", "validate-all")
322    skip_op_attr = ("name", "depth", "role")
323
324    def __init__(self, ra_class, ra_type, ra_provider="heartbeat", exclude_from_completion=None):
325        self.excluded_from_completion = exclude_from_completion or []
326        self.ra_class = ra_class
327        self.ra_type = ra_type
328        self.ra_provider = ra_provider
329        if ra_class == 'ocf' and not self.ra_provider:
330            self.ra_provider = "heartbeat"
331        self.ra_elem = None
332        self.broken_ra = False
333
334    def __str__(self):
335        return "%s:%s:%s" % (self.ra_class, self.ra_provider, self.ra_type) \
336            if self.ra_class == "ocf" \
337               else "%s:%s" % (self.ra_class, self.ra_type)
338
339    def error(self, s):
340        common_err("%s: %s" % (self, s))
341
342    def warn(self, s):
343        common_warn("%s: %s" % (self, s))
344
345    def info(self, s):
346        common_info("%s: %s" % (self, s))
347
348    def debug(self, s):
349        common_debug("%s: %s" % (self, s))
350
351    def add_ra_params(self, ra):
352        '''
353        Add parameters from another RAInfo instance.
354        '''
355        try:
356            if self.mk_ra_node() is None or ra.mk_ra_node() is None:
357                return
358        except:
359            return
360        try:
361            params_node = self.ra_elem.findall("parameters")[0]
362        except:
363            params_node = etree.SubElement(self.ra_elem, "parameters")
364        for n in ra.ra_elem.xpath("//parameters/parameter"):
365            params_node.append(copy.deepcopy(n))
366
367    def mk_ra_node(self):
368        '''
369        Return the resource_agent node.
370        '''
371        if self.ra_elem is not None:
372            return self.ra_elem
373        # don't try again in vain
374        if self.broken_ra:
375            return None
376        self.broken_ra = True
377        meta = self.meta()
378        if meta is None:
379            if not config.core.ignore_missing_metadata:
380                self.error("got no meta-data, does this RA exist?")
381            return None
382        self.ra_elem = meta
383        try:
384            assert self.ra_elem.tag == 'resource-agent'
385        except Exception:
386            self.error("meta-data contains no resource-agent element")
387            return None
388        if self.ra_class == "stonith":
389            self.add_ra_params(get_stonithd_meta())
390        self.broken_ra = False
391        return self.ra_elem
392
393    def params(self, completion=False):
394        '''
395        Construct a dict of dicts: parameters are keys and
396        dictionary of attributes/values are values. Cached too.
397
398        completion:
399        If true, filter some (advanced) parameters out.
400        '''
401        if completion:
402            if self.mk_ra_node() is None:
403                return None
404            return [c.get("name")
405                    for c in self.ra_elem.xpath("//parameters/parameter")
406                    if c.get("name") and c.get("name") not in self.excluded_from_completion]
407        ident = "ra_params-%s" % self
408        if cache.is_cached(ident):
409            return cache.retrieve(ident)
410        if self.mk_ra_node() is None:
411            return None
412        d = {}
413        for c in self.ra_elem.xpath("//parameters/parameter"):
414            name = c.get("name")
415            if not name:
416                continue
417            required = c.get("required") if not (c.get("deprecated") or c.get("obsoletes")) else "0"
418            unique = c.get("unique")
419            typ, default = _param_type_default(c)
420            d[name] = {
421                "required": required,
422                "unique": unique,
423                "type": typ,
424                "default": default,
425            }
426        return cache.store(ident, d)
427
428    def actions(self):
429        '''
430        Construct a dict of dicts: actions are keys and
431        dictionary of attributes/values are values. Cached too.
432        '''
433        ident = "ra_actions-%s" % self
434        if cache.is_cached(ident):
435            return cache.retrieve(ident)
436        if self.mk_ra_node() is None:
437            return None
438        d = {}
439        for c in self.ra_elem.xpath("//actions/action"):
440            name = c.get("name")
441            if not name or name in self.skip_ops:
442                continue
443            if name == "monitor":
444                name = monitor_name_node(c)
445            d[name] = {}
446            for a in list(c.attrib.keys()):
447                if a in self.skip_op_attr:
448                    continue
449                v = c.get(a)
450                if v:
451                    d[name][a] = v
452        # add monitor ops without role, if they don't already
453        # exist
454        d2 = {}
455        for op in d:
456            if re.match("monitor_[^0-9]", op):
457                norole_op = re.sub(r'monitor_[^0-9_]+_(.*)', r'monitor_\1', op)
458                if norole_op not in d:
459                    d2[norole_op] = d[op]
460        d.update(d2)
461        return cache.store(ident, d)
462
463    def param_default(self, pname):
464        '''
465        Parameter's default.
466        '''
467        d = self.params()
468        try:
469            return d[pname]["default"]
470        except:
471            return None
472
473    def normalize_parameters(self, root):
474        """
475        Find all instance_attributes/nvpair objects,
476        check if parameter exists. If not, normalize name
477        and check if THAT exists (replacing - with _).
478        If so, change the name of the parameter.
479        """
480        params = self.params()
481        if not params:
482            return
483        for nvp in root.xpath("instance_attributes/nvpair"):
484            name = nvp.get("name")
485            if name is not None and name not in params:
486                name = name.replace("-", "_")
487                if name in params:
488                    nvp.attrib["name"] = name
489
490    def sanity_check_params(self, ident, nvpairs, existence_only=False):
491        '''
492        nvpairs is a list of <nvpair> tags.
493        - are all required parameters defined
494        - do all parameters exist
495        '''
496        def reqd_params_list():
497            '''
498            List of required parameters.
499            '''
500            d = self.params()
501            if not d:
502                return []
503            return [x for x in d if d[x]["required"] == '1']
504
505        def unreq_param(p):
506            '''
507            Allow for some exceptions.
508
509            - the rhcs stonith agents sometimes require "action" (in
510              the meta-data) and "port", but they're automatically
511              supplied by stonithd
512            '''
513            if self.ra_class == "stonith" and \
514                (self.ra_type.startswith("rhcs/") or
515                 self.ra_type.startswith("fence_")):
516                if p in ("action", "port"):
517                    return True
518            return False
519
520        rc = 0
521        d = {}
522        for nvp in nvpairs:
523            if 'name' in nvp.attrib:
524                d[nvp.get('name')] = nvp.get('value')
525        if not existence_only:
526            for p in reqd_params_list():
527                if unreq_param(p):
528                    continue
529                if p not in d:
530                    common_err("{}: required parameter \"{}\" not defined".format(ident, p))
531                    rc |= utils.get_check_rc()
532        for p in d:
533            if p.startswith("$"):
534                # these are special, non-RA parameters
535                continue
536            if p not in self.params():
537                common_err("{}: parameter \"{}\" is not known".format(ident, p))
538                rc |= utils.get_check_rc()
539        return rc
540
541    def get_adv_timeout(self, op, node=None):
542        if node is not None and op == "monitor":
543            name = monitor_name_node(node)
544        else:
545            name = op
546        try:
547            return self.actions()[name]["timeout"]
548        except:
549            return None
550
551    def sanity_check_ops(self, ident, ops, default_timeout):
552        '''
553        ops is a list of operations
554        - do all operations exist
555        - are timeouts sensible
556        '''
557        def sanity_check_op(op, n_ops, intervals):
558            """
559            Helper method used by sanity_check_ops.
560            """
561            rc = 0
562            if self.ra_class == "stonith" and op in ("start", "stop"):
563                return rc
564            if op not in self.actions():
565                common_warn("%s: action '%s' not found in Resource Agent meta-data" % (ident, op))
566                rc |= 1
567            if "interval" in n_ops[op]:
568                v = n_ops[op]["interval"]
569                v_msec = crm_msec(v)
570                if op in ("start", "stop") and v_msec != 0:
571                    common_warn("%s: Specified interval for %s is %s, it must be 0" % (ident, op, v))
572                    rc |= 1
573                if op.startswith("monitor") and v_msec != 0:
574                    if v_msec not in intervals:
575                        intervals[v_msec] = 1
576                    else:
577                        common_warn("%s: interval in %s must be unique" % (ident, op))
578                        rc |= 1
579            try:
580                adv_timeout = self.actions()[op]["timeout"]
581            except:
582                return rc
583            if "timeout" in n_ops[op]:
584                v = n_ops[op]["timeout"]
585                timeout_string = "specified timeout"
586            else:
587                v = default_timeout
588                timeout_string = "default timeout"
589            if crm_msec(v) < 0:
590                return rc
591            if crm_time_cmp(adv_timeout, v) > 0:
592                common_warn("%s: %s %s for %s is smaller than the advised %s" %
593                            (ident, timeout_string, v, op, adv_timeout))
594                rc |= 1
595            return rc
596
597        rc = 0
598        n_ops = {}
599        for op in ops:
600            n_op = monitor_name_pl(op[1]) if op[0] == "monitor" else op[0]
601            n_ops[n_op] = {}
602            for p, v in op[1]:
603                if p in self.skip_op_attr:
604                    continue
605                n_ops[n_op][p] = v
606        for req_op in self.required_ops:
607            if req_op not in n_ops:
608                if not (self.ra_class == "stonith" and req_op in ("start", "stop")):
609                    n_ops[req_op] = {}
610        intervals = {}
611        for op in n_ops:
612            rc |= sanity_check_op(op, n_ops, intervals)
613        return rc
614
615    def meta(self):
616        '''
617        RA meta-data as raw xml.
618        Returns an etree xml object.
619        '''
620        sid = "ra_meta-%s" % self
621        if cache.is_cached(sid):
622            return cache.retrieve(sid)
623        if self.ra_class in constants.meta_progs:
624            l = prog_meta(self.ra_class)
625        elif self.ra_class in constants.meta_progs_20:
626            l = prog_meta(self.ra_class)
627        else:
628            l = ra_meta(self.ra_class, self.ra_type, self.ra_provider)
629        if not l:
630            return None
631        try:
632            xml = etree.fromstring('\n'.join(l))
633        except Exception:
634            self.error("Cannot parse meta-data XML")
635            return None
636        self.debug("read and cached meta-data")
637        return cache.store(sid, xml)
638
639    def meta_pretty(self):
640        '''
641        Print the RA meta-data in a human readable form.
642        '''
643        if self.mk_ra_node() is None:
644            return ''
645        l = []
646        title = self.meta_title()
647        l.append(title)
648        longdesc = get_nodes_text(self.ra_elem, "longdesc")
649        if longdesc:
650            l.append(longdesc)
651        if self.ra_class != "heartbeat":
652            params = self.meta_parameters()
653            if params:
654                l.append(params.rstrip())
655        actions = self.meta_actions()
656        if actions:
657            l.append(actions)
658        return '\n\n'.join(l)
659
660    def get_shortdesc(self, n):
661        name = n.get("name")
662        shortdesc = get_nodes_text(n, "shortdesc")
663        longdesc = get_nodes_text(n, "longdesc")
664        if shortdesc and shortdesc not in (name, longdesc, self.ra_type):
665            return shortdesc
666        return ''
667
668    def meta_title(self):
669        s = str(self)
670        shortdesc = self.get_shortdesc(self.ra_elem)
671        if shortdesc:
672            s = "%s (%s)" % (shortdesc, s)
673        return s
674
675    def format_parameter(self, n):
676        def meta_param_head():
677            name = n.get("name")
678            if not name:
679                return None
680            s = name
681            if n.get("required") == "1":
682                s = s + "*"
683            typ, default = _param_type_default(n)
684            if typ and default:
685                s = "%s (%s, [%s])" % (s, typ, default)
686            elif typ:
687                s = "%s (%s)" % (s, typ)
688            shortdesc = self.get_shortdesc(n)
689            s = "%s: %s" % (s, shortdesc)
690            return s
691        head = meta_param_head()
692        if not head:
693            self.error("no name attribute for parameter")
694            return ""
695        l = [head]
696        longdesc = get_nodes_text(n, "longdesc")
697        if longdesc:
698            l.append(self.ra_tab + longdesc.replace("\n", "\n" + self.ra_tab) + '\n')
699        return '\n'.join(l)
700
701    def meta_parameter(self, param):
702        if self.mk_ra_node() is None:
703            return ''
704        for c in self.ra_elem.xpath("//parameters/parameter"):
705            if c.get("name") == param:
706                return self.format_parameter(c)
707
708    def meta_parameters(self):
709        if self.mk_ra_node() is None:
710            return ''
711        l = []
712        for c in self.ra_elem.xpath("//parameters/parameter"):
713            s = self.format_parameter(c)
714            if s:
715                l.append(s)
716        if l:
717            return "Parameters (*: required, []: default):\n\n" + '\n'.join(l)
718
719    def meta_actions(self):
720        def meta_action_head(n):
721            name = n.get("name")
722            if not name or name in self.skip_ops:
723                return ''
724            if name == "monitor":
725                name = monitor_name_node(n)
726            s = "%-13s" % name
727            for a in list(n.attrib.keys()):
728                if a in self.skip_op_attr:
729                    continue
730                v = n.get(a)
731                if v:
732                    s = "%s %s=%s" % (s, a, v)
733            return s
734        l = []
735        for c in self.ra_elem.xpath("//actions/action"):
736            s = meta_action_head(c)
737            if s:
738                l.append(self.ra_tab + s)
739        if not l:
740            return None
741        return "Operations' defaults (advisory minimum):\n\n" + '\n'.join(l)
742
743
744def get_ra(r):
745    """
746    Argument is either an xml resource tag with class, provider and type attributes,
747    or a CLI style class:provider:type string.
748    """
749    if isinstance(r, str):
750        cls, provider, typ = disambiguate_ra_type(r)
751    else:
752        cls, provider, typ = r.get('class'), r.get('provider'), r.get('type')
753    # note order of arguments!
754    return RAInfo(cls, typ, provider)
755
756
757#
758# resource type definition
759#
760def ra_type_validate(s, ra_class, provider, rsc_type):
761    '''
762    Only ocf ra class supports providers.
763    '''
764    if not rsc_type:
765        common_err("bad resource type specification %s" % s)
766        return False
767    if ra_class == "ocf":
768        if not provider:
769            common_err("provider could not be determined for %s" % s)
770            return False
771    else:
772        if provider:
773            common_warn("ra class %s does not support providers" % ra_class)
774            return True
775    return True
776
777
778def pick_provider(providers):
779    '''
780    Pick the most appropriate choice from a
781    list of providers, falling back to
782    'heartbeat' if no good choice is found
783    '''
784    if not providers or 'heartbeat' in providers:
785        return 'heartbeat'
786    elif 'pacemaker' in providers:
787        return 'pacemaker'
788    return providers[0]
789
790
791def disambiguate_ra_type(s):
792    '''
793    Unravel [class:[provider:]]type
794    '''
795    l = s.split(':')
796    if not l or len(l) > 3:
797        return ["", "", ""]
798    if len(l) == 3:
799        return l
800    elif len(l) == 2:
801        cl, tp = l
802    else:
803        cl, tp = "ocf", l[0]
804    pr = pick_provider(ra_providers(tp, cl)) if cl == 'ocf' else ''
805    return cl, pr, tp
806
807
808def can_validate_agent(agent):
809    if utils.getuser() != 'root':
810        return False
811    if isinstance(agent, str):
812        c, p, t = disambiguate_ra_type(agent)
813        if c != "ocf":
814            return False
815        agent = RAInfo(c, t, p)
816        if agent.mk_ra_node() is None:
817            return False
818    if len(agent.ra_elem.xpath('.//actions/action[@name="validate-all"]')) < 1:
819        return False
820    return True
821
822
823def validate_agent(agentname, params, log=False):
824    """
825    Call the validate-all action on the agent, given
826    the parameter hash params.
827    agent: either a c:p:t agent name, or an RAInfo instance
828    params: a hash of agent parameters
829    Returns: (rc, out)
830    """
831    def find_agent():
832        if not can_validate_agent(agentname):
833            return None
834        if isinstance(agentname, str):
835            c, p, t = disambiguate_ra_type(agentname)
836            if c != "ocf":
837                raise ValueError("Only OCF agents are supported by this command")
838            agent = RAInfo(c, t, p)
839            if agent.mk_ra_node() is None:
840                return None
841        else:
842            agent = agentname
843        if len(agent.ra_elem.xpath('.//actions/action[@name="validate-all"]')) < 1:
844            raise ValueError("validate-all action not supported by agent")
845        return agent
846    agent = find_agent()
847    if agent is None:
848        return (-1, "")
849
850    my_env = os.environ.copy()
851    my_env["OCF_ROOT"] = config.path.ocf_root
852    for k, v in params.items():
853        my_env["OCF_RESKEY_" + k] = v
854    cmd = [os.path.join(config.path.ocf_root, "resource.d", agent.ra_provider, agent.ra_type), "validate-all"]
855    if options.regression_tests:
856        print(".EXT", " ".join(cmd))
857    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=my_env)
858    _out, _ = p.communicate()
859    out = to_ascii(_out)
860    p.wait()
861
862    if log is True:
863        from . import msg as msglog
864        for msg in out.splitlines():
865            if msg.startswith("ERROR: "):
866                msglog.err_buf.error(msg[7:])
867            elif msg.startswith("WARNING: "):
868                msglog.err_buf.warning(msg[9:])
869            elif msg.startswith("INFO: "):
870                msglog.err_buf.info(msg[6:])
871            elif msg.startswith("DEBUG: "):
872                msglog.err_buf.debug(msg[7:])
873            else:
874                msglog.err_buf.writemsg(msg)
875    return p.returncode, out
876
877
878DLM_RA_SCRIPTS = """
879primitive {id} ocf:pacemaker:controld \
880op start timeout=90 \
881op stop timeout=100 \
882op monitor interval=60 timeout=60"""
883FILE_SYSTEM_RA_SCRIPTS = """
884primitive {id} ocf:heartbeat:Filesystem \
885params directory="{mnt_point}" fstype="{fs_type}" device="{device}" \
886op monitor interval=20 timeout=40 \
887op start timeout=60 \
888op stop timeout=60"""
889LVMLOCKD_RA_SCRIPTS = """
890primitive {id} ocf:heartbeat:lvmlockd \
891op start timeout=90 \
892op stop timeout=100 \
893op monitor interval=30 timeout=90"""
894LVMACTIVATE_RA_SCRIPTS = """
895primitive {id} ocf:heartbeat:LVM-activate \
896params vgname={vgname} vg_access_mode=lvmlockd activation_mode=shared \
897op start timeout=90s \
898op stop timeout=90s \
899op monitor interval=30s timeout=90s"""
900GROUP_SCRIPTS = """
901group {id} {ra_string}"""
902CLONE_SCRIPTS = """
903clone {id} {group_id} meta interleave=true"""
904
905
906CONFIGURE_RA_TEMPLATE_DICT = {
907        "DLM": DLM_RA_SCRIPTS,
908        "Filesystem": FILE_SYSTEM_RA_SCRIPTS,
909        "LVMLockd": LVMLOCKD_RA_SCRIPTS,
910        "LVMActivate": LVMACTIVATE_RA_SCRIPTS,
911        "GROUP": GROUP_SCRIPTS,
912        "CLONE": CLONE_SCRIPTS
913        }
914# vim:ts=4:sw=4:et:
915