1 # -------------------------------------------------------------------------
2 # Copyright (c) 2015-2017 AT&T Intellectual Property
3 # Copyright (C) 2020 Wipro Limited.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # -------------------------------------------------------------------------
21 from jinja2 import Template
22 from requests import RequestException
25 from osdf.operation.error_handling import build_json_error_body
26 from osdf.logging.osdf_logging import metrics_log, MH, error_log, debug_log
27 from osdf.adapters.conductor import conductor
28 from apps.license.optimizers.simple_license_allocation import license_optim
29 from osdf.utils.interfaces import get_rest_client
30 from osdf.utils.mdc_utils import mdc_from_json
33 def conductor_response_processor(conductor_response, req_id, transaction_id):
34 """Build a response object to be sent to client's callback URL from Conductor's response
35 This includes Conductor's placement optimization response, and required ASDC license artifacts
37 :param conductor_response: JSON response from Conductor
38 :param raw_response: Raw HTTP response corresponding to above
39 :param req_id: Id of a request
40 :return: JSON object that can be sent to the client's callback URL
42 composite_solutions = []
43 name_map = {"physical-location-id": "cloudClli", "host_id": "vnfHostName",
44 "cloud_version": "cloudVersion", "cloud_owner": "cloudOwner",
45 "cloud": "cloudRegionId", "service": "serviceInstanceId", "is_rehome": "isRehome",
46 "location_id": "locationId", "location_type": "locationType", "directives": "oof_directives"}
47 for reco in conductor_response['plans'][0]['recommendations']:
48 for resource in reco.keys():
49 c = reco[resource]['candidate']
51 'resourceModuleName': resource,
52 'serviceResourceId': reco[resource].get('service_resource_id', ""),
53 'solution': {"identifierType": name_map.get(c['inventory_type'], c['inventory_type']),
54 'identifiers': [c['candidate_id']],
55 'cloudOwner': c.get('cloud_owner', "")},
58 for key, value in c.items():
59 if key in ["location_id", "location_type", "is_rehome", "host_id"]:
61 solution['assignmentInfo'].append({"key": name_map.get(key, key), "value": value})
63 debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info".format(key))
65 for key, value in reco[resource]['attributes'].items():
67 solution['assignmentInfo'].append({"key": name_map.get(key, key), "value": value})
69 debug_log.debug("The key[{}] is not mapped and will not be returned in assignment info".format(key))
70 composite_solutions.append(solution)
72 request_status = "completed" if conductor_response['plans'][0]['status'] == "done" \
73 else conductor_response['plans'][0]['status']
74 status_message = conductor_response.get('plans')[0].get('message', "")
77 if composite_solutions:
78 solution_info.setdefault('placementSolutions', [])
79 solution_info['placementSolutions'].append(composite_solutions)
82 "transactionId": transaction_id,
84 "requestStatus": request_status,
85 "statusMessage": status_message,
86 "solutions": solution_info
91 def conductor_no_solution_processor(conductor_response, request_id, transaction_id,
92 template_placement_response="templates/plc_opt_response.jsont"):
93 """Build a response object to be sent to client's callback URL from Conductor's response
94 This is for case where no solution is found
96 :param conductor_response: JSON response from Conductor
97 :param raw_response: Raw HTTP response corresponding to above
98 :param request_id: request Id associated with the client request (same as conductor response's "name")
99 :param template_placement_response: the template for generating response to client (plc_opt_response.jsont)
100 :return: JSON object that can be sent to the client's callback URL
102 status_message = conductor_response["plans"][0].get("message")
103 templ = Template(open(template_placement_response).read())
104 return json.loads(templ.render(composite_solutions=[], requestId=request_id, license_solutions=[],
105 transactionId=transaction_id,
106 requestStatus="completed", statusMessage=status_message, json=json))
109 def process_placement_opt(request_json, policies, osdf_config):
110 """Perform the work for placement optimization (e.g. call SDC artifact and make conductor request)
111 NOTE: there is scope to make the requests to policy asynchronous to speed up overall performance
112 :param request_json: json content from original request
113 :param policies: flattened policies corresponding to this request
114 :param osdf_config: configuration specific to OSDF app
115 :param prov_status: provStatus retrieved from Subscriber policy
116 :return: None, but make a POST to callback URL
120 mdc_from_json(request_json)
121 rc = get_rest_client(request_json, service="so")
122 req_id = request_json["requestInfo"]["requestId"]
123 transaction_id = request_json['requestInfo']['transactionId']
125 metrics_log.info(MH.inside_worker_thread(req_id))
127 if request_json.get('licenseInfo', {}).get('licenseDemands'):
128 license_info = license_optim(request_json)
130 # Conductor only handles placement, only call Conductor if placementDemands exist
131 if request_json.get('placementInfo', {}).get('placementDemands'):
132 metrics_log.info(MH.requesting("placement/conductor", req_id))
133 req_info = request_json['requestInfo']
134 demands = request_json['placementInfo']['placementDemands']
135 request_parameters = request_json['placementInfo']['requestParameters']
136 service_info = request_json['serviceInfo']
137 resp = conductor.request(req_info, demands, request_parameters, service_info, True,
138 osdf_config, policies)
139 if resp["plans"][0].get("recommendations"):
140 placement_response = conductor_response_processor(resp, req_id, transaction_id)
141 else: # "solved" but no solutions found
142 placement_response = conductor_no_solution_processor(resp, req_id, transaction_id)
143 if license_info: # Attach license solution if it exists
144 placement_response['solutionInfo']['licenseInfo'] = license_info
145 else: # License selection only scenario
146 placement_response = {
147 "transactionId": transaction_id,
149 "requestStatus": "completed",
150 "statusMessage": "License selection completed successfully",
151 "solutionInfo": {"licenseInfo": license_info}
153 except Exception as err:
154 error_log.error("Error for {} {}".format(req_id, traceback.format_exc()))
157 body = build_json_error_body(err)
158 metrics_log.info(MH.sending_response(req_id, "ERROR"))
159 rc.request(json=body, noresponse=True)
160 except RequestException:
161 error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc()))
165 metrics_log.info(MH.calling_back_with_body(req_id, rc.url,placement_response))
166 rc.request(json=placement_response, noresponse=True)
167 except RequestException : # can't do much here but log it and move on
168 error_log.error("Error sending asynchronous notification for {} {}".format(req_id, traceback.format_exc()))