aboutsummaryrefslogtreecommitdiffstats
path: root/policyhandler/policy_matcher.py
blob: d0786bae11b218d2eeb27e360ff87fc13399544c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# ================================================================================
# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
# ================================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============LICENSE_END=========================================================
#
# ECOMP is a trademark and service mark of AT&T Intellectual Property.

"""policy-matcher matches the policies from deployment-handler to policies from policy-engine"""

import json
import logging
import re

from .deploy_handler import DeployHandler, PolicyUpdateMessage
from .onap.audit import AuditHttpCode, AuditResponseCode
from .policy_consts import (ERRORED_POLICIES, LATEST_POLICIES,
                            MATCHING_CONDITIONS, POLICY_BODY, POLICY_FILTER,
                            POLICY_NAME, POLICY_VERSION, POLICY_VERSIONS)
from .policy_rest import PolicyRest
from .policy_utils import RegexCoarser


class PolicyMatcher(object):
    """policy-matcher - static class"""
    _logger = logging.getLogger("policy_handler.policy_matcher")
    PENDING_UPDATE = "pending_update"

    @staticmethod
    def get_deployed_policies(audit):
        """get the deployed policies and policy-filters"""
        deployed_policies, deployed_policy_filters = DeployHandler.get_deployed_policies(audit)

        if audit.is_not_found():
            warning_txt = "got no deployed policies or policy-filters"
            PolicyMatcher._logger.warning(warning_txt)
            return {"warning": warning_txt}, None, None

        if not audit.is_success() or (not deployed_policies and not deployed_policy_filters):
            error_txt = "failed to retrieve policies from deployment-handler"
            PolicyMatcher._logger.error(error_txt)
            return {"error": error_txt}, None, None

        return None, deployed_policies, deployed_policy_filters


    @staticmethod
    def build_catch_up_message(audit, deployed_policies, deployed_policy_filters):
        """
        find the latest policies from policy-engine for the deployed policies and policy-filters
        """

        if not (deployed_policies or deployed_policy_filters):
            error_txt = "no deployed policies or policy-filters"
            PolicyMatcher._logger.warning(error_txt)
            return {"error": error_txt}, None

        coarse_regex_patterns = PolicyMatcher.calc_coarse_patterns(
            audit, deployed_policies, deployed_policy_filters)

        if not coarse_regex_patterns:
            error_txt = ("failed to construct the coarse_regex_patterns from " +
                         "deployed_policies: {} and deployed_policy_filters: {}"
                         .format(deployed_policies, deployed_policy_filters))
            PolicyMatcher._logger.error(audit.error(
                error_txt, error_code=AuditResponseCode.DATA_ERROR))
            audit.set_http_status_code(AuditHttpCode.DATA_ERROR.value)
            return {"error": error_txt}, None

        pdp_response = PolicyRest.get_latest_policies(
            audit, policy_filters=[{POLICY_NAME: policy_name_pattern}
                                   for policy_name_pattern in coarse_regex_patterns]
        )

        if not audit.is_success():
            error_txt = "failed to retrieve policies from policy-engine"
            PolicyMatcher._logger.warning(error_txt)
            return {"error": error_txt}, None

        latest_policies = pdp_response.get(LATEST_POLICIES, {})
        errored_policies = pdp_response.get(ERRORED_POLICIES, {})

        latest_policies, changed_policies, policy_filter_matches = PolicyMatcher._match_policies(
            audit, latest_policies, deployed_policies, deployed_policy_filters)

        errored_policies = dict((policy_id, policy)
                                for (policy_id, policy) in errored_policies.items()
                                if deployed_policies.get(policy_id, {}).get(POLICY_VERSIONS))

        removed_policies = dict(
            (policy_id, True)
            for (policy_id, deployed_policy) in deployed_policies.items()
            if deployed_policy.get(POLICY_VERSIONS)
            and policy_id not in latest_policies
            and policy_id not in errored_policies
        )

        return ({LATEST_POLICIES: latest_policies, ERRORED_POLICIES: errored_policies},
                PolicyUpdateMessage(changed_policies,
                                    removed_policies,
                                    policy_filter_matches))


    @staticmethod
    def calc_coarse_patterns(audit, deployed_policies, deployed_policy_filters):
        """calculate the coarsed patterns on policy-names in policies and policy-filters"""
        coarse_regex = RegexCoarser()
        for policy_id in deployed_policies or {}:
            coarse_regex.add_regex_pattern(policy_id)

        for policy_filter in (deployed_policy_filters or {}).values():
            policy_name_pattern = policy_filter.get(POLICY_FILTER, {}).get(POLICY_NAME)
            coarse_regex.add_regex_pattern(policy_name_pattern)

        coarse_regex_patterns = coarse_regex.get_coarse_regex_patterns()
        PolicyMatcher._logger.debug(
            audit.debug("coarse_regex_patterns({}) combined_regex_pattern({}) for patterns({})"
                        .format(coarse_regex_patterns,
                                coarse_regex.get_combined_regex_pattern(),
                                coarse_regex.patterns)))
        return coarse_regex_patterns


    @staticmethod
    def match_to_deployed_policies(audit, policies_updated, policies_removed):
        """match the policies_updated, policies_removed versus deployed policies"""
        deployed_policies, deployed_policy_filters = DeployHandler.get_deployed_policies(audit)
        if not audit.is_success():
            return {}, {}, {}

        _, changed_policies, policy_filter_matches = PolicyMatcher._match_policies(
            audit, policies_updated, deployed_policies, deployed_policy_filters)

        policies_removed = dict((policy_id, policy)
                                for (policy_id, policy) in policies_removed.items()
                                if deployed_policies.get(policy_id, {}).get(POLICY_VERSIONS))

        return changed_policies, policies_removed, policy_filter_matches


    @staticmethod
    def _match_policies(audit, policies, deployed_policies, deployed_policy_filters):
        """
        Match policies to deployed policies either by policy_id or the policy-filters.

        Also calculates the policies that changed in comparison to deployed policies
        """
        matching_policies = {}
        changed_policies = {}
        policy_filter_matches = {}

        policies = policies or {}
        deployed_policies = deployed_policies or {}
        deployed_policy_filters = deployed_policy_filters or {}

        for (policy_id, policy) in policies.items():
            new_version = policy.get(POLICY_BODY, {}).get(POLICY_VERSION)
            deployed_policy = deployed_policies.get(policy_id)

            if deployed_policy:
                matching_policies[policy_id] = policy

            policy_changed = (deployed_policy and new_version
                              and (deployed_policy.get(PolicyMatcher.PENDING_UPDATE)
                                   or {new_version} ^
                                   deployed_policy.get(POLICY_VERSIONS, {}).keys()))
            if policy_changed:
                changed_policies[policy_id] = policy
                policy_filter_matches[policy_id] = {}

            in_filters = False
            for (policy_filter_id, policy_filter) in deployed_policy_filters.items():
                if not PolicyMatcher._match_policy_to_filter(
                        audit, policy_id, policy,
                        policy_filter_id, policy_filter.get(POLICY_FILTER)):
                    continue

                if policy_changed or not deployed_policy:
                    in_filters = True
                    if policy_id not in policy_filter_matches:
                        policy_filter_matches[policy_id] = {}
                    policy_filter_matches[policy_id][policy_filter_id] = True

            if not deployed_policy and in_filters:
                matching_policies[policy_id] = policy
                changed_policies[policy_id] = policy

        return matching_policies, changed_policies, policy_filter_matches


    @staticmethod
    def _match_policy_to_filter(audit, policy_id, policy, policy_filter_id, policy_filter):
        """Match the policy to the policy-filter"""
        if not policy_id or not policy or not policy_filter or not policy_filter_id:
            return False

        filter_policy_name = policy_filter.get(POLICY_NAME)
        if not filter_policy_name:
            return False

        policy_body = policy.get(POLICY_BODY)
        if not policy_body:
            return False

        policy_name = policy_body.get(POLICY_NAME)
        if not policy_name:
            return False

        log_line = "policy {} to filter id {}: {}".format(json.dumps(policy),
                                                          policy_filter_id,
                                                          json.dumps(policy_filter))
        # PolicyMatcher._logger.debug(audit.debug("matching {}...".format(log_line)))

        if (filter_policy_name != policy_id and filter_policy_name != policy_name
                and not re.match(filter_policy_name, policy_name)):
            PolicyMatcher._logger.debug(
                audit.debug("not match by policyName: {} != {}: {}"
                            .format(policy_name, filter_policy_name, log_line)))
            return False

        matching_conditions = policy_body.get(MATCHING_CONDITIONS, {})
        if not isinstance(matching_conditions, dict):
            return False

        filter_onap_name = policy_filter.get("onapName")
        policy_onap_name = matching_conditions.get("ONAPName")
        if filter_onap_name and filter_onap_name != policy_onap_name:
            PolicyMatcher._logger.debug(
                audit.debug("not match by ONAPName: {} != {}: {}"
                            .format(policy_onap_name, filter_onap_name, log_line)))
            return False

        filter_config_name = policy_filter.get("configName")
        policy_config_name = matching_conditions.get("ConfigName")
        if filter_config_name and filter_config_name != policy_config_name:
            PolicyMatcher._logger.debug(
                audit.debug("not match by configName: {} != {}: {}"
                            .format(policy_config_name, filter_config_name, log_line)))
            return False

        filter_config_attributes = policy_filter.get("configAttributes")
        if filter_config_attributes and isinstance(filter_config_attributes, dict):
            for filter_key, filter_config_attribute in filter_config_attributes.items():
                if (filter_key not in matching_conditions
                        or filter_config_attribute != matching_conditions.get(filter_key)):
                    PolicyMatcher._logger.debug(
                        audit.debug("not match by configAttributes: {} != {}: {}"
                                    .format(json.dumps(matching_conditions),
                                            json.dumps(filter_config_attributes),
                                            log_line)))
                    return False

        PolicyMatcher._logger.debug(audit.debug("matched {}".format(log_line)))
        return True