1    	from collections.abc import Mapping, Sequence
2    	from typing import Any, cast
3    	
4    	from pcs.common import reports
5    	from pcs.lib import validate
6    	from pcs.lib.cib.constraint import common, ticket
7    	from pcs.lib.cib.tools import (
8    	    ElementNotFound,
9    	    IdProvider,
10   	    get_constraints,
11   	    get_element_by_id,
12   	    get_pacemaker_version_by_which_cib_was_validated,
13   	)
14   	from pcs.lib.env import LibraryEnvironment
15   	from pcs.lib.errors import LibraryError
16   	
17   	from .common import _load_resource_set_list, _primitive_resource_set_list
18   	
19   	
20   	def create(
21   	    env: LibraryEnvironment,
22   	    ticket_key: str,
23   	    resource_id: str,
24   	    options: Mapping[str, str],
25   	    resource_in_clone_alowed: bool = False,
(3) Event identifier_use: Example 1: Using identifier "duplication_alowed" (2 total uses in this function).
Also see events: [identifier_typo][remediation]
26   	    duplication_alowed: bool = False,
27   	) -> None:
28   	    """
29   	    create a plain ticket constraint
30   	
31   	    ticket_key -- ticket for constraining a resource
32   	    resource_id -- resource to be constrained
33   	    options -- desired constraint attributes
34   	    resource_in_clone_alowed -- allow to constrain a resource in a clone
35   	    duplication_alowed -- allow to create a duplicate constraint
36   	    """
37   	    cib = env.get_cib()
38   	    id_provider = IdProvider(cib)
39   	    constraint_section = get_constraints(cib)
40   	
41   	    # validation
42   	    constrained_el = None
43   	    try:
44   	        constrained_el = get_element_by_id(cib, resource_id)
45   	    except ElementNotFound:
46   	        env.report_processor.report(
47   	            reports.ReportItem.error(
48   	                reports.messages.IdNotFound(resource_id, [])
49   	            )
50   	        )
51   	
52   	    options_pairs = validate.values_to_pairs(
53   	        options,
54   	        validate.option_value_normalization(
55   	            {
56   	                "loss-policy": lambda value: value.lower(),
57   	                "rsc-role": lambda value: value.capitalize(),
58   	            }
59   	        ),
60   	    )
61   	
62   	    env.report_processor.report_list(
63   	        ticket.validate_create_plain(
64   	            id_provider,
65   	            ticket_key,
66   	            constrained_el,
67   	            options_pairs,
68   	            in_multiinstance_allowed=resource_in_clone_alowed,
69   	        )
70   	    )
71   	
72   	    if env.report_processor.has_errors:
73   	        raise LibraryError()
74   	
75   	    # modify CIB
76   	    new_constraint = ticket.create_plain(
77   	        constraint_section,
78   	        id_provider,
79   	        get_pacemaker_version_by_which_cib_was_validated(cib),
80   	        ticket_key,
81   	        resource_id,
82   	        validate.pairs_to_values(options_pairs),
83   	    )
84   	
85   	    # Check whether the created constraint is a duplicate of an existing one
86   	    env.report_processor.report_list(
87   	        ticket.DuplicatesCheckerTicketPlain().check(
88   	            constraint_section,
89   	            new_constraint,
90   	            {reports.codes.FORCE} if duplication_alowed else set(),
91   	        )
92   	    )
93   	    if env.report_processor.has_errors:
94   	        raise LibraryError()
95   	
96   	    # push CIB
97   	    env.push_cib()
98   	
99   	
100  	def create_with_set(
101  	    env: LibraryEnvironment,
102  	    resource_set_list: Sequence[Mapping[str, Any]],
103  	    constraint_options: Mapping[str, str],
104  	    resource_in_clone_alowed: bool = False,
105  	    duplication_alowed: bool = False,
106  	) -> None:
107  	    """
108  	    create a set ticket constraint
109  	
110  	    resource_set_list -- description of resource sets, for example:
111  	        {"ids": ["A", "B"], "options": {"sequential": "true"}},
112  	    constraint_options -- desired constraint attributes
113  	    resource_in_clone_alowed -- allow to constrain resources in a clone
114  	    duplication_alowed -- allow to create a duplicate constraint
115  	    """
116  	    cib = env.get_cib()
117  	    id_provider = IdProvider(cib)
118  	    constraint_section = get_constraints(cib)
119  	
120  	    # find all specified constrained resources and transform set options to
121  	    # value pairs for normalization and validation
122  	    resource_set_loaded_list = _load_resource_set_list(
123  	        cib,
124  	        env.report_processor,
125  	        # dacite doesn't support TypedDicts (https://github.com/konradhalas/dacite/issues/125)
126  	        # and therefore this command fails when called from APIv2, so we had to
127  	        # use Mapping in the signature of the lib command
128  	        cast(common.CmdInputResourceSetList, resource_set_list),
129  	        validate.option_value_normalization(
130  	            {
131  	                "role": lambda value: value.capitalize(),
132  	            }
133  	        ),
134  	    )
135  	    # Unlike in plain constraints, validation cannot continue if even a single
136  	    # resource could not be found. If such resources were omitted in their sets
137  	    # for purposes of validation, similarly to plain constraint commands, then
138  	    # those sets could become invalid, and thus validating such sets would
139  	    # provide false results.
140  	    if env.report_processor.has_errors:
141  	        raise LibraryError()
142  	
143  	    # transform constraint options to value pairs for normalization and
144  	    # validation
145  	    constraint_options_pairs = validate.values_to_pairs(
146  	        constraint_options,
147  	        validate.option_value_normalization(
148  	            {
149  	                "loss-policy": lambda value: value.lower(),
150  	            }
151  	        ),
152  	    )
153  	
154  	    # validation
155  	    env.report_processor.report_list(
156  	        ticket.validate_create_with_set(
157  	            id_provider,
158  	            resource_set_loaded_list,
159  	            constraint_options_pairs,
160  	            in_multiinstance_allowed=resource_in_clone_alowed,
161  	        )
162  	    )
163  	    if env.report_processor.has_errors:
164  	        raise LibraryError()
165  	
166  	    # modify CIB
167  	    new_constraint = ticket.create_with_set(
168  	        constraint_section,
169  	        id_provider,
170  	        get_pacemaker_version_by_which_cib_was_validated(cib),
171  	        _primitive_resource_set_list(resource_set_loaded_list),
172  	        validate.pairs_to_values(constraint_options_pairs),
173  	    )
174  	
175  	    # Check whether the created constraint is a duplicate of an existing one
176  	    env.report_processor.report_list(
177  	        ticket.DuplicatesCheckerTicketWithSet().check(
178  	            constraint_section,
179  	            new_constraint,
180  	            {reports.codes.FORCE} if duplication_alowed else set(),
181  	        )
182  	    )
183  	    if env.report_processor.has_errors:
184  	        raise LibraryError()
185  	
186  	    # push CIB
187  	    env.push_cib()
188  	
189  	
190  	def remove(env: LibraryEnvironment, ticket_key: str, resource_id: str) -> bool:
191  	    """
192  	    remove all ticket constraint from resource
193  	    If resource is in resource set with another resources then only resource
194  	    ref is removed. If resource is alone in resource set whole constraint is
195  	    removed.
196  	    """
197  	    constraint_section = get_constraints(env.get_cib())
198  	    any_plain_removed = ticket.remove_plain(
199  	        constraint_section, ticket_key, resource_id
200  	    )
201  	    any_with_resource_set_removed = ticket.remove_with_resource_set(
202  	        constraint_section, ticket_key, resource_id
203  	    )
204  	
205  	    env.push_cib()
206  	
207  	    return any_plain_removed or any_with_resource_set_removed
208