Fixed the config dir and default policy scope
[optf/osdf.git] / osdf / adapters / policy / interface.py
1  # -------------------------------------------------------------------------
2 #   Copyright (c) 2015-2017 AT&T Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17 #
18
19 import base64
20 import itertools
21 import json
22
23
24 from requests import RequestException
25 from osdf.operation.exceptions import BusinessException
26 from osdf.adapters.local_data.local_policies import get_local_policies
27 from osdf.adapters.policy.utils import policy_name_as_regex, retrieve_node
28 from osdf.utils.programming_utils import list_flatten, dot_notation
29 from osdf.config.base import osdf_config
30 from osdf.logging.osdf_logging import audit_log, MH, metrics_log, debug_log
31 from osdf.utils.interfaces import RestClient
32
33
34 def get_by_name(rest_client, policy_name_list, wildcards=True):
35     policy_list = []
36     for policy_name in policy_name_list:
37         try:
38             query_name = policy_name
39             if wildcards:
40                 query_name = policy_name_as_regex(query_name)
41             policy_list.append(rest_client.request(json={"policyName": query_name}))
42         except RequestException as err:
43             audit_log.warn("Error in fetching policy: " + policy_name)
44             raise BusinessException("Cannot fetch policy {}: ".format(policy_name), err)
45     return policy_list
46
47
48 def get_by_scope(rest_client, req, config_local, type_service):
49     """ Get policies by scopes as defined in the configuration file.
50     :param rest_client: a rest client object to make a call.
51     :param req: an optimization request.
52     :param config_local: application configuration file.
53     :param type_service: the type of optimization service.
54     :return: policies in the form of list of list where inner list contains policies for a single a scope.
55     """
56     scope_policies = []
57     references = config_local.get('references', {})
58     pscope = config_local.get('policy_info', {}).get(type_service, {}).get('policy_scope', {})
59     service_name = dot_notation(req, references.get('service_name', {}).get('value', None))
60     primary_scope = pscope['{}_scope'.format(service_name.lower() if pscope.get(service_name + "_scope", None)
61                                              else "default")]
62     for sec_scope in pscope.get('secondary_scopes', []):
63         policies, scope_fields = [], []
64         for field in sec_scope:
65             scope_fields.extend([get_scope_fields(field, references, req, list_flatten(scope_policies))
66                                 if 'get_param' in field else field])
67         scope_fields = set(list_flatten(scope_fields))
68         scope_fields = set([x.lower() for x in scope_fields])
69         for scope in scope_fields:
70             policies.extend(policy_api_call(rest_client, primary_scope, scope))
71         scope_policies.append([policy for policy in policies
72                               if scope_fields <= set(json.loads(policy['config'])['content']['policyScope'])])
73     return scope_policies
74
75
76 def get_scope_fields(field, references, req, policy_info):
77     """ Retrieve the values for scope fields from a request and policies as per the configuration
78     and references defined in a configuration file. If the value of a scope field missing in a request or
79     policies, throw an exception since correct policies cannot be retrieved.
80     :param field: details on a scope field from a configuration file.
81     :param references: references defined in a configuration file.
82     :param req: an optimization request.
83     :param policy_info: a list of policies.
84     :return: scope fields retrieved from a request and policies.
85     """
86     ref_source = references.get(field.get('get_param', ""), {}).get('source')
87     ref_value = references.get(field.get('get_param', ""), {}).get('value')
88     if ref_source == "request":
89         scope_field = dot_notation(req, ref_value)
90         if scope_field:
91             return scope_field
92         raise BusinessException("Field {} is missing a value in a request".format(ref_value.split('.')[-1]))
93     else:
94         scope_fields = []
95         for policy in policy_info:
96             policy_content = json.loads(policy.get('config', "{}"))
97             if policy_content.get('content', {}).get('policyType', "invalid_policy") == ref_source:
98                 scope_fields.append(dot_notation(policy_content, ref_value))
99         scope_values = list_flatten(scope_fields)
100         if len(scope_values) > 0:
101             return scope_values
102         raise BusinessException("Field {} is missing a value in all policies of type {}".format(
103             ref_value.split('.')[-1], ref_source))
104
105
106 def policy_api_call(rest_client, primary_scope, scope_field):
107     """ Makes a getConfig API call to the policy system to retrieve policies matching a scope.
108     :param rest_client: rest client object to make a call
109     :param primary_scope: the primary scope of policies, which is a folder in the policy system
110     where policies are stored.
111     :param scope_field: the secondary scope of policies, which is a collection of domain values.
112     :return: a list of policies matching both primary and secondary scopes.
113     """
114     api_call_body = {"policyName": "{}.*".format(primary_scope),
115                      "configAttributes": {"policyScope": "{}".format(scope_field)}}
116     return rest_client.request(json=api_call_body)
117
118
119 def remote_api(req_json, osdf_config, service_type="placement"):
120     """Make a request to policy and return response -- it accounts for multiple requests that be needed
121     :param req_json: policy request object (can have multiple policy names)
122     :param osdf_config: main config that will have credential information
123     :param service_type: the type of service to call: "placement", "scheduling"
124     :return: all related policies and provStatus retrieved from Subscriber policy
125     """
126     config = osdf_config.deployment
127     uid, passwd = config['policyPlatformUsername'], config['policyPlatformPassword']
128     pcuid, pcpasswd = config['policyClientUsername'], config['policyClientPassword']
129     headers = {"ClientAuth": base64.b64encode(bytes("{}:{}".format(pcuid, pcpasswd), "ascii"))}
130     headers.update({'Environment': config['policyPlatformEnv']})
131     url = config['policyPlatformUrl']
132     rc = RestClient(userid=uid, passwd=passwd, headers=headers, url=url, log_func=debug_log.debug)
133
134     if osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name":
135         policies = get_by_name(rc, req_json[service_type + "Info"]['policyId'], wildcards=True)
136     elif osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name_no_wildcards":
137         policies = get_by_name(rc, req_json[service_type + "Info"]['policyId'], wildcards=False)
138     else:
139         policies = get_by_scope(rc, req_json, osdf_config.core, service_type)
140
141     formatted_policies = []
142     for x in itertools.chain(*policies):
143         if x['config'] is None:
144             raise BusinessException("Config not found for policy with name %s" % x['policyName'])
145         else:
146             formatted_policies.append(json.loads(x['config']))
147     return formatted_policies
148
149
150 def local_policies_location(req_json, osdf_config, service_type):
151     """
152     Get folder and list of policy_files if "local policies" option is enabled
153     :param service_type: placement supported for now, but can be any other service
154     :return: a tuple (folder, file_list) or None
155     """
156     lp = osdf_config.core.get('osdf_temp', {}).get('local_policies', {})
157     if lp.get('global_disabled'):
158         return None  # short-circuit to disable all local policies
159     if lp.get('local_{}_policies_enabled'.format(service_type)):
160         if service_type == "scheduling":
161             return lp.get('{}_policy_dir'.format(service_type)), lp.get('{}_policy_files'.format(service_type))
162         else:
163             service_name = req_json['serviceInfo']['serviceName']  # TODO: data_mapping.get_service_type(model_name)
164             return lp.get('{}_policy_dir_{}'.format(service_type, service_name.lower())), \
165                    lp.get('{}_policy_files_{}'.format(service_type, service_name.lower()))
166     return None
167
168
169 def get_policies(request_json, service_type):
170     """Validate the request and get relevant policies
171     :param request_json: Request object
172     :param service_type: the type of service to call: "placement", "scheduling"
173     :return: policies associated with this request and provStatus retrieved from Subscriber policy
174     """
175     req_info = request_json['requestInfo']
176     req_id = req_info['requestId']
177     metrics_log.info(MH.requesting("policy", req_id))
178     local_info = local_policies_location(request_json, osdf_config, service_type)
179
180     if local_info:  # tuple containing location and list of files
181         to_filter = None
182         if osdf_config.core['policy_info'][service_type]['policy_fetch'] == "by_name":
183             to_filter = request_json[service_type + "Info"]['policyId']
184         policies = get_local_policies(local_info[0], local_info[1], to_filter)
185     else:
186         policies = remote_api(request_json, osdf_config, service_type)
187
188     return policies