vFW DT tutorial improvements
[demo.git] / tutorials / vFWDT / workflow / workflow.py
1 '''
2 /*-
3 * ============LICENSE_START=======================================================
4 * Copyright (C) 2019 Orange
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 *      http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * ============LICENSE_END=========================================================
19 */
20 '''
21
22 import os
23 import json
24 import sys
25 import uuid
26 import time
27 import copy
28 import netifaces as ni
29 import warnings
30 import contextlib
31 import requests
32 import simplejson
33 import http.server
34 import threading
35 from datetime import datetime
36 from datetime import timedelta
37 from simple_rest_client.api import API
38 from simple_rest_client.resource import Resource
39 from basicauth import encode
40 from pprint import pprint
41 from random import randint
42 from urllib3.exceptions import InsecureRequestWarning
43
44
45 old_merge_environment_settings = requests.Session.merge_environment_settings
46
47 hostname_cache = []
48 ansible_inventory = {}
49 osdf_response = {"last": { "id": "id", "data": None}}
50
51
52 class BaseServer(http.server.BaseHTTPRequestHandler):
53
54     def __init__(self, one, two, three):
55         self.osdf_resp = osdf_response
56         super().__init__(one, two, three)
57
58     def _set_headers(self):
59         self.send_response(200)
60         self.send_header('Content-type', 'application/json')
61         self.end_headers()
62
63     def do_GET(self):
64         self._set_headers()
65
66     def do_HEAD(self):
67         self._set_headers()
68
69     def do_POST(self):
70         self._set_headers()
71         self.data_string = self.rfile.read(int(self.headers['Content-Length']))
72         self.send_response(200)
73         self.end_headers()
74
75         data = simplejson.loads(self.data_string)
76         self.osdf_resp["last"]["data"] = data
77         self.osdf_resp["last"]["id"] = data["requestId"]
78         with open("response.json", "w") as outfile:
79             simplejson.dump(data, outfile)
80
81
82 def _run_osdf_resp_server():
83     server_address = ('', 9000)
84     httpd = http.server.HTTPServer(server_address, BaseServer)
85     print('Starting OSDF Response Server...')
86     httpd.serve_forever()
87
88 @contextlib.contextmanager
89 def _no_ssl_verification():
90     opened_adapters = set()
91
92     def merge_environment_settings(self, url, proxies, stream, verify, cert):
93         # Verification happens only once per connection so we need to close
94         # all the opened adapters once we're done. Otherwise, the effects of
95         # verify=False persist beyond the end of this context manager.
96         opened_adapters.add(self.get_adapter(url))
97
98         settings = old_merge_environment_settings(self, url, proxies, stream, verify, cert)
99         settings['verify'] = False
100
101         return settings
102
103     requests.Session.merge_environment_settings = merge_environment_settings
104
105     try:
106         with warnings.catch_warnings():
107             warnings.simplefilter('ignore', InsecureRequestWarning)
108             yield
109     finally:
110         requests.Session.merge_environment_settings = old_merge_environment_settings
111
112         for adapter in opened_adapters:
113             try:
114                 adapter.close()
115             except:
116                 pass
117
118
119 def _get_aai_rel_link_data(data, related_to, search_key=None, match_dict=None):
120     # some strings that we will encounter frequently
121     rel_lst = "relationship-list"
122     rkey = "relationship-key"
123     rval = "relationship-value"
124     rdata = "relationship-data"
125     response = list()
126     if match_dict:
127         m_key = match_dict.get('key')
128         m_value = match_dict.get('value')
129     else:
130         m_key = None
131         m_value = None
132     rel_dict = data.get(rel_lst)
133     if rel_dict:  # check if data has relationship lists
134         for key, rel_list in rel_dict.items():
135             for rel in rel_list:
136                 if rel.get("related-to") == related_to:
137                     dval = None
138                     matched = False
139                     link = rel.get("related-link")
140                     r_data = rel.get(rdata, [])
141                     if search_key:
142                         for rd in r_data:
143                             if rd.get(rkey) == search_key:
144                                 dval = rd.get(rval)
145                                 if not match_dict:  # return first match
146                                     response.append(
147                                         {"link": link, "d_value": dval}
148                                     )
149                                     break  # go to next relation
150                             if rd.get(rkey) == m_key \
151                                     and rd.get(rval) == m_value:
152                                 matched = True
153                         if match_dict and matched:  # if matching required
154                             response.append(
155                                 {"link": link, "d_value": dval}
156                             )
157                             # matched, return search value corresponding
158                             # to the matched r_data group
159                     else:  # no search key; just return the link
160                         response.append(
161                             {"link": link, "d_value": dval}
162                         )
163     if len(response) == 0:
164         response.append(
165             {"link": None, "d_value": None}
166         )
167     return response
168
169
170 class AAIApiResource(Resource):
171     actions = {
172         'generic_vnf': {'method': 'GET', 'url': 'network/generic-vnfs/generic-vnf/{}'},
173         'link': {'method': 'GET', 'url': '{}'},
174         'service_instance': {'method': 'GET',
175                              'url': 'business/customers/customer/{}/service-subscriptions/service-subscription/{}/service-instances/service-instance/{}'}
176     }
177
178
179 class HASApiResource(Resource):
180     actions = {
181         'plans': {'method': 'POST', 'url': 'plans/'},
182         'plan': {'method': 'GET', 'url': 'plans/{}'}
183     }
184
185
186 class OSDFApiResource(Resource):
187     actions = {
188         'placement': {'method': 'POST', 'url': 'placement'}
189     }
190
191
192 class APPCLcmApiResource(Resource):
193     actions = {
194         'distribute_traffic': {'method': 'POST', 'url': 'appc-provider-lcm:distribute-traffic/'},
195         'distribute_traffic_check': {'method': 'POST', 'url': 'appc-provider-lcm:distribute-traffic-check/'},
196         'action_status': {'method': 'POST', 'url': 'appc-provider-lcm:action-status/'},
197     }
198
199
200 def _init_python_aai_api(onap_ip):
201     api = API(
202         api_root_url="https://{}:30233/aai/v14/".format(onap_ip),
203         params={},
204         headers={
205             'Authorization': encode("AAI", "AAI"),
206             'X-FromAppId': 'SCRIPT',
207             'Accept': 'application/json',
208             'Content-Type': 'application/json',
209             'X-TransactionId': str(uuid.uuid4()),
210         },
211         timeout=30,
212         append_slash=False,
213         json_encode_body=True # encode body as json
214     )
215     api.add_resource(resource_name='aai', resource_class=AAIApiResource)
216     return api
217
218
219 def _init_python_has_api(onap_ip):
220     api = API(
221         api_root_url="https://{}:30275/v1/".format(onap_ip),
222         params={},
223         headers={
224             'Authorization': encode("admin1", "plan.15"),
225             'X-FromAppId': 'SCRIPT',
226             'Accept': 'application/json',
227             'Content-Type': 'application/json',
228             'X-TransactionId': str(uuid.uuid4()),
229         },
230         timeout=30,
231         append_slash=False,
232         json_encode_body=True # encode body as json
233     )
234     api.add_resource(resource_name='has', resource_class=HASApiResource)
235     return api
236
237
238 def _init_python_osdf_api(onap_ip):
239     api = API(
240         api_root_url="https://{}:30248/api/oof/v1/".format(onap_ip),
241         params={},
242         headers={
243             'Authorization': encode("test", "testpwd"),
244             'X-FromAppId': 'SCRIPT',
245             'Accept': 'application/json',
246             'Content-Type': 'application/json',
247             'X-TransactionId': str(uuid.uuid4()),
248         },
249         timeout=30,
250         append_slash=False,
251         json_encode_body=True # encode body as json
252     )
253     api.add_resource(resource_name='osdf', resource_class=OSDFApiResource)
254     return api
255
256
257 def _init_python_appc_lcm_api(onap_ip):
258     api = API(
259         api_root_url="http://{}:30230/restconf/operations/".format(onap_ip),
260         params={},
261         headers={
262             'Authorization': encode("admin", "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"),
263             'X-FromAppId': 'SCRIPT',
264             'Accept': 'application/json',
265             'Content-Type': 'application/json',
266         },
267         timeout=300,
268         append_slash=False,
269         json_encode_body=True # encode body as json
270     )
271     api.add_resource(resource_name='lcm', resource_class=APPCLcmApiResource)
272     return api
273
274
275 def load_aai_data(vfw_vnf_id, onap_ip):
276     api = _init_python_aai_api(onap_ip)
277     aai_data = {}
278     aai_data['service-info'] = {'global-customer-id': '', 'service-instance-id': '', 'service-type': ''}
279     aai_data['vfw-model-info'] = {'model-invariant-id': '', 'model-version-id': '', 'vnf-name': '', 'vnf-type': ''}
280     aai_data['vpgn-model-info'] = {'model-invariant-id': '', 'model-version-id': '', 'vnf-name': '', 'vnf-type': ''}
281     with _no_ssl_verification():
282         response = api.aai.generic_vnf(vfw_vnf_id, body=None, params={'depth': 2}, headers={})
283         aai_data['vfw-model-info']['model-invariant-id'] = response.body.get('model-invariant-id')
284         aai_data['vfw-model-info']['model-version-id'] = response.body.get('model-version-id')
285         aai_data['vfw-model-info']['vnf-name'] = response.body.get('vnf-name')
286         aai_data['vfw-model-info']['vnf-type'] = response.body.get('vnf-type')
287         aai_data['vf-module-id'] = response.body['vf-modules']['vf-module'][0]['vf-module-id']
288
289         related_to = "service-instance"
290         search_key = "customer.global-customer-id"
291         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
292         aai_data['service-info']['global-customer-id'] = rl_data_list[0]['d_value']
293
294         search_key = "service-subscription.service-type"
295         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
296         aai_data['service-info']['service-type'] = rl_data_list[0]['d_value']
297
298         search_key = "service-instance.service-instance-id"
299         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
300         aai_data['service-info']['service-instance-id'] = rl_data_list[0]['d_value']
301
302         service_link = rl_data_list[0]['link']
303         response = api.aai.link(service_link, body=None, params={}, headers={})
304
305         related_to = "generic-vnf"
306         search_key = "generic-vnf.vnf-id"
307         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
308         for i in range(0, len(rl_data_list)):
309             vnf_id = rl_data_list[i]['d_value']
310
311             if vnf_id != vfw_vnf_id:
312                 vnf_link = rl_data_list[i]['link']
313                 response = api.aai.link(vnf_link, body=None, params={}, headers={})
314                 if aai_data['vfw-model-info']['model-invariant-id'] != response.body.get('model-invariant-id'):
315                     aai_data['vpgn-model-info']['model-invariant-id'] = response.body.get('model-invariant-id')
316                     aai_data['vpgn-model-info']['model-version-id'] = response.body.get('model-version-id')
317                     aai_data['vpgn-model-info']['vnf-name'] = response.body.get('vnf-name')
318                     aai_data['vpgn-model-info']['vnf-type'] = response.body.get('vnf-type')
319                     break
320     return aai_data
321
322
323 def _osdf_request(rancher_ip, onap_ip, aai_data, exclude, use_oof_cache):
324     dirname = os.path.join('templates/oof-cache/', aai_data['vf-module-id'])
325     if exclude:
326         file = os.path.join(dirname, 'sample-osdf-excluded.json')
327     else:
328         file = os.path.join(dirname, 'sample-osdf-required.json')
329     if use_oof_cache and os.path.exists(file):
330         migrate_from = json.loads(open(file).read())
331         return migrate_from
332
333     print('Making OSDF request for excluded {}'.format(str(exclude)))
334     api = _init_python_osdf_api(onap_ip)
335     request_id = str(uuid.uuid4())
336     transaction_id = str(uuid.uuid4())
337     callback_url = "http://{}:9000/osdfCallback/".format(str(rancher_ip))
338     template = json.loads(open('templates/osdfRequest.json').read())
339     template["requestInfo"]["transactionId"] = transaction_id
340     template["requestInfo"]["requestId"] = request_id
341     template["requestInfo"]["callbackUrl"] = callback_url
342     template["serviceInfo"]["serviceInstanceId"] = aai_data['service-info']['service-instance-id']
343     template["placementInfo"]["requestParameters"]["chosenCustomerId"] = aai_data['service-info']['global-customer-id']
344     template["placementInfo"]["placementDemands"][0]["resourceModelInfo"]["modelInvariantId"] =\
345         aai_data['vfw-model-info']['model-invariant-id']
346     template["placementInfo"]["placementDemands"][0]["resourceModelInfo"]["modelVersionId"] =\
347         aai_data['vfw-model-info']['model-version-id']
348     template["placementInfo"]["placementDemands"][1]["resourceModelInfo"]["modelInvariantId"] =\
349         aai_data['vpgn-model-info']['model-invariant-id']
350     template["placementInfo"]["placementDemands"][1]["resourceModelInfo"]["modelVersionId"] =\
351         aai_data['vpgn-model-info']['model-version-id']
352     if exclude:
353         template["placementInfo"]["placementDemands"][0]["excludedCandidates"][0]["identifiers"].\
354             append(aai_data['vf-module-id'])
355     else:
356         template["placementInfo"]["placementDemands"][0]["requiredCandidates"][0]["identifiers"].\
357             append(aai_data['vf-module-id'])
358
359     #print(json.dumps(template, indent=4))
360
361     with _no_ssl_verification():
362         response = api.osdf.placement(body=template, params={}, headers={})
363         #if response.body.get('error_message') is not None:
364         #    raise Exception(response.body['error_message']['explanation'])
365
366     counter = 0
367     while counter < 600 and osdf_response["last"]["id"] != request_id:
368         time.sleep(1)
369         if counter % 20 == 0:
370             print("solving")
371         counter += 1
372
373     if osdf_response["last"]["id"] == request_id:
374         status = osdf_response["last"]["data"]["requestStatus"]
375         if status == "completed":
376             result = {
377                 "solution": osdf_response["last"]["data"]["solutions"]["placementSolutions"]
378             }
379             if not os.path.exists(dirname):
380                 os.makedirs(dirname)
381             f = open(file, 'w+')
382             f.write(json.dumps(result, indent=4))
383             f.close()
384             return result
385         else:
386             message = osdf_response["last"]["data"]["statusMessage"]
387             raise Exception("OOF request {}: {}".format(status, message))
388     else:
389         raise Exception("No response for OOF request")
390
391
392 def _has_request(onap_ip, aai_data, exclude, use_oof_cache):
393     dirname = os.path.join('templates/oof-cache/', aai_data['vf-module-id'])
394     if exclude:
395         file = os.path.join(dirname, 'sample-has-excluded.json')
396     else:
397         file = os.path.join(dirname, 'sample-has-required.json')
398     if use_oof_cache and os.path.exists(file):
399         migrate_from = json.loads(open(file).read())
400         return migrate_from
401
402     print('Making HAS request for excluded {}'.format(str(exclude)))
403     api = _init_python_has_api(onap_ip)
404     request_id = str(uuid.uuid4())
405     template = json.loads(open('templates/hasRequest.json').read())
406     result = {}
407     template['name'] = request_id
408     node = template['template']['parameters']
409     node['chosen_customer_id'] = aai_data['service-info']['global-customer-id']
410     node['service_id'] = aai_data['service-info']['service-instance-id']
411     node = template['template']['demands']['vFW-SINK'][0]
412     node['attributes']['model-invariant-id'] = aai_data['vfw-model-info']['model-invariant-id']
413     node['attributes']['model-version-id'] = aai_data['vfw-model-info']['model-version-id']
414     if exclude:
415         node['excluded_candidates'][0]['candidate_id'][0] = aai_data['vf-module-id']
416         del node['required_candidates']
417     else:
418         node['required_candidates'][0]['candidate_id'][0] = aai_data['vf-module-id']
419         del node['excluded_candidates']
420     node = template['template']['demands']['vPGN'][0]
421     node['attributes']['model-invariant-id'] = aai_data['vpgn-model-info']['model-invariant-id']
422     node['attributes']['model-version-id'] = aai_data['vpgn-model-info']['model-version-id']
423
424     #print(json.dumps(template, indent=4))
425
426     with _no_ssl_verification():
427         response = api.has.plans(body=template, params={}, headers={})
428         if response.body.get('error_message') is not None:
429             raise Exception(response.body['error_message']['explanation'])
430         else:
431             plan_id = response.body['id']
432             response = api.has.plan(plan_id, body=None, params={}, headers={})
433             status = response.body['plans'][0]['status']
434             while status != 'done' and status != 'error':
435                 print(status)
436                 response = api.has.plan(plan_id, body=None, params={}, headers={})
437                 status = response.body['plans'][0]['status']
438             if status == 'done':
439                 result = response.body['plans'][0]['recommendations'][0]
440             else:
441                 raise Exception(response.body['plans'][0]['message'])
442
443     if not os.path.exists(dirname):
444         os.makedirs(dirname)
445     f = open(file, 'w+')
446     f.write(json.dumps(result, indent=4))
447     f.close()
448     return result
449
450
451 def _extract_has_appc_identifiers(has_result, demand):
452     if demand == 'vPGN':
453         v_server = has_result[demand]['attributes']['vservers'][0]
454     else:
455         if len(has_result[demand]['attributes']['vservers'][0]['l-interfaces']) == 4:
456             v_server = has_result[demand]['attributes']['vservers'][0]
457         else:
458             v_server = has_result[demand]['attributes']['vservers'][1]
459     for itf in v_server['l-interfaces']:
460         if itf['ipv4-addresses'][0].startswith("10.0."):
461             ip = itf['ipv4-addresses'][0]
462             break
463
464     if v_server['vserver-name'] in hostname_cache and demand != 'vPGN':
465         v_server['vserver-name'] = v_server['vserver-name'].replace("01", "02")
466     hostname_cache.append(v_server['vserver-name'])
467
468     config = {
469         'vnf-id': has_result[demand]['attributes']['nf-id'],
470         'vf-module-id': has_result[demand]['attributes']['vf-module-id'],
471         'ip': ip,
472         'vserver-id': v_server['vserver-id'],
473         'vserver-name': v_server['vserver-name'],
474         'vnfc-type': demand.lower(),
475         'physical-location-id': has_result[demand]['attributes']['physical-location-id']
476     }
477     ansible_inventory_entry = "{} ansible_ssh_host={} ansible_ssh_user=ubuntu".format(config['vserver-name'], config['ip'])
478     if demand.lower() not in ansible_inventory:
479         ansible_inventory[demand.lower()] = {}
480     ansible_inventory[demand.lower()][config['vserver-name']] = ansible_inventory_entry
481     return config
482
483
484 def _extract_osdf_appc_identifiers(has_result, demand):
485     if demand == 'vPGN':
486         v_server = has_result[demand]['vservers'][0]
487     else:
488         if len(has_result[demand]['vservers'][0]['l-interfaces']) == 4:
489             v_server = has_result[demand]['vservers'][0]
490         else:
491             v_server = has_result[demand]['vservers'][1]
492     for itf in v_server['l-interfaces']:
493         if itf['ipv4-addresses'][0].startswith("10.0."):
494             ip = itf['ipv4-addresses'][0]
495             break
496
497     if v_server['vserver-name'] in hostname_cache and demand != 'vPGN':
498         v_server['vserver-name'] = v_server['vserver-name'].replace("01", "02")
499     hostname_cache.append(v_server['vserver-name'])
500
501     config = {
502         'vnf-id': has_result[demand]['nf-id'],
503         'vf-module-id': has_result[demand]['vf-module-id'],
504         'ip': ip,
505         'vserver-id': v_server['vserver-id'],
506         'vserver-name': v_server['vserver-name'],
507         'vnfc-type': demand.lower(),
508         'physical-location-id': has_result[demand]['locationId']
509     }
510     ansible_inventory_entry = "{} ansible_ssh_host={} ansible_ssh_user=ubuntu".format(config['vserver-name'], config['ip'])
511     if demand.lower() not in ansible_inventory:
512         ansible_inventory[demand.lower()] = {}
513     ansible_inventory[demand.lower()][config['vserver-name']] = ansible_inventory_entry
514     return config
515
516
517 def _extract_has_appc_dt_config(has_result, demand):
518     if demand == 'vPGN':
519         return {}
520     else:
521         config = {
522             "nf-type": has_result[demand]['attributes']['nf-type'],
523             "nf-name": has_result[demand]['attributes']['nf-name'],
524             "vf-module-name": has_result[demand]['attributes']['vf-module-name'],
525             "vnf-type": has_result[demand]['attributes']['vnf-type'],
526             "service_instance_id": "319e60ef-08b1-47aa-ae92-51b97f05e1bc",
527             "cloudClli": has_result[demand]['attributes']['physical-location-id'],
528             "nf-id": has_result[demand]['attributes']['nf-id'],
529             "vf-module-id": has_result[demand]['attributes']['vf-module-id'],
530             "aic_version": has_result[demand]['attributes']['aic_version'],
531             "ipv4-oam-address": has_result[demand]['attributes']['ipv4-oam-address'],
532             "vnfHostName": has_result[demand]['candidate']['host_id'],
533             "ipv6-oam-address": has_result[demand]['attributes']['ipv6-oam-address'],
534             "cloudOwner": has_result[demand]['candidate']['cloud_owner'],
535             "isRehome": has_result[demand]['candidate']['is_rehome'],
536             "locationId": has_result[demand]['candidate']['location_id'],
537             "locationType": has_result[demand]['candidate']['location_type'],
538             'vservers': has_result[demand]['attributes']['vservers']
539         }
540         return config
541
542
543 def _extract_osdf_appc_dt_config(osdf_result, demand):
544     if demand == 'vPGN':
545         return {}
546     else:
547         return osdf_result[demand]
548
549
550 def _build_config_from_has(has_result):
551     v_pgn_result = _extract_has_appc_identifiers(has_result, 'vPGN')
552     v_fw_result = _extract_has_appc_identifiers(has_result, 'vFW-SINK')
553     dt_config = _extract_has_appc_dt_config(has_result, 'vFW-SINK')
554
555     config = {
556         'vPGN': v_pgn_result,
557         'vFW-SINK': v_fw_result
558     }
559     #print(json.dumps(config, indent=4))
560     config['dt-config'] = {
561         'destinations': [dt_config]
562     }
563     return config
564
565
566 def _adapt_osdf_result(osdf_result):
567     result = {}
568     demand = _build_osdf_result_demand(osdf_result["solution"][0][0])
569     result[demand["name"]] = demand["value"]
570     demand = _build_osdf_result_demand(osdf_result["solution"][0][1])
571     result[demand["name"]] = demand["value"]
572     return result
573
574
575 def _build_osdf_result_demand(solution):
576     result = {}
577     result["name"] = solution["resourceModuleName"]
578     value = {"candidateId": solution["solution"]["identifiers"][0]}
579     for info in solution["assignmentInfo"]:
580         value[info["key"]] = info["value"]
581     result["value"] = value
582     return result
583
584
585 def _build_config_from_osdf(osdf_result):
586     osdf_result = _adapt_osdf_result(osdf_result)
587     v_pgn_result = _extract_osdf_appc_identifiers(osdf_result, 'vPGN')
588     v_fw_result = _extract_osdf_appc_identifiers(osdf_result, 'vFW-SINK')
589     dt_config = _extract_osdf_appc_dt_config(osdf_result, 'vFW-SINK')
590
591     config = {
592         'vPGN': v_pgn_result,
593         'vFW-SINK': v_fw_result
594     }
595     #print(json.dumps(config, indent=4))
596     config['dt-config'] = {
597         'destinations': [dt_config]
598     }
599     return config
600
601
602 def _build_appc_lcm_dt_payload(is_vpkg, oof_config, book_name, traffic_presence):
603     is_check = traffic_presence is not None
604     oof_config = copy.deepcopy(oof_config)
605     #if is_vpkg:
606     #    node_list = "[ {} ]".format(oof_config['vPGN']['vserver-id'])
607     #else:
608     #    node_list = "[ {} ]".format(oof_config['vFW-SINK']['vserver-id'])
609
610     if is_vpkg:
611         config = oof_config['vPGN']
612     else:
613         config = oof_config['vFW-SINK']
614     #node = {
615     #    'site': config['physical-location-id'],
616     #    'vnfc_type': config['vnfc-type'],
617     #    'vm_info': [{
618     #        'ne_id': config['vserver-name'],
619     #        'fixed_ip_address': config['ip']
620     #   }]
621     #}
622     #node_list = list()
623     #node_list.append(node)
624
625     if is_check:
626         oof_config['dt-config']['trafficpresence'] = traffic_presence
627
628     file_content = oof_config['dt-config']
629
630     config = {
631         "configuration-parameters": {
632             #"node_list": node_list,
633             "ne_id": config['vserver-name'],
634             "fixed_ip_address": config['ip'],
635             "file_parameter_content":  json.dumps(file_content)
636         }
637     }
638     if book_name != '':
639         config["configuration-parameters"]["book_name"] = book_name
640     payload = json.dumps(config)
641     return payload
642
643
644 def _build_appc_lcm_status_body(req):
645     payload = {
646         'request-id': req['input']['common-header']['request-id'],
647         'sub-request-id': req['input']['common-header']['sub-request-id'],
648         'originator-id': req['input']['common-header']['originator-id']
649     }
650     payload = json.dumps(payload)
651     template = json.loads(open('templates/appcRestconfLcm.json').read())
652     template['input']['action'] = 'ActionStatus'
653     template['input']['payload'] = payload
654     template['input']['common-header']['request-id'] = req['input']['common-header']['request-id']
655     template['input']['common-header']['sub-request-id'] = str(uuid.uuid4())
656     template['input']['action-identifiers']['vnf-id'] = req['input']['action-identifiers']['vnf-id']
657     return template
658
659
660 def _build_appc_lcm_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
661     if is_vpkg:
662         demand = 'vPGN'
663     else:
664         demand = 'vFW-SINK'
665
666     book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
667     payload = _build_appc_lcm_dt_payload(is_vpkg, config, book_name, traffic_presence)
668     template = json.loads(open('templates/appcRestconfLcm.json').read())
669     template['input']['action'] = action
670     template['input']['payload'] = payload
671     template['input']['common-header']['request-id'] = req_id
672     template['input']['common-header']['sub-request-id'] = str(uuid.uuid4())
673     template['input']['action-identifiers']['vnf-id'] = config[demand]['vnf-id']
674     return template
675
676
677 def _set_appc_lcm_timestamp(body, timestamp=None):
678     if timestamp is None:
679         t = datetime.utcnow() + timedelta(seconds=-10)
680         timestamp = t.strftime('%Y-%m-%dT%H:%M:%S.244Z')
681     body['input']['common-header']['timestamp'] = timestamp
682
683
684 def build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw):
685     if_has = False
686
687     if if_has:
688         migrate_from = _has_request(onap_ip, aai_data, False, use_oof_cache)
689
690         if if_close_loop_vfw:
691             migrate_to = migrate_from
692         else:
693             migrate_to = _has_request(onap_ip, aai_data, True, use_oof_cache)
694
695         migrate_from = _build_config_from_has(migrate_from)
696         migrate_to = _build_config_from_has(migrate_to)
697     else:
698         migrate_from = _osdf_request(rancher_ip, onap_ip, aai_data, False, use_oof_cache)
699
700         if if_close_loop_vfw:
701             migrate_to = migrate_from
702         else:
703             migrate_to = _osdf_request(rancher_ip, onap_ip, aai_data, True, use_oof_cache)
704
705         migrate_from = _build_config_from_osdf(migrate_from)
706         migrate_to = _build_config_from_osdf(migrate_to)
707
708     #print(json.dumps(migrate_from, indent=4))
709     #print(json.dumps(migrate_to, indent=4))
710     req_id = str(uuid.uuid4())
711     payload_dt_check_vpkg = _build_appc_lcm_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
712     payload_dt_vpkg_to = _build_appc_lcm_request_body(True, migrate_to, req_id, 'DistributeTraffic')
713     payload_dt_check_vfw_from = _build_appc_lcm_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck',
714                                                              False)
715     payload_dt_check_vfw_to = _build_appc_lcm_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
716
717     result = list()
718     result.append(payload_dt_check_vpkg)
719     result.append(payload_dt_vpkg_to)
720     result.append(payload_dt_check_vfw_from)
721     result.append(payload_dt_check_vfw_to)
722     return result
723
724
725 def appc_lcm_request(onap_ip, req):
726     api = _init_python_appc_lcm_api(onap_ip)
727     #print(json.dumps(req, indent=4))
728     if req['input']['action'] == "DistributeTraffic":
729         result = api.lcm.distribute_traffic(body=req, params={}, headers={})
730     elif req['input']['action'] == "DistributeTrafficCheck":
731         result = api.lcm.distribute_traffic_check(body=req, params={}, headers={})
732     else:
733         raise Exception("{} action not supported".format(req['input']['action']))
734
735     if result.body['output']['status']['code'] == 400:
736         print("Request Completed")
737     elif result.body['output']['status']['code'] == 100:
738         print("Request Accepted. Receiving result status...")
739 #    elif result.body['output']['status']['code'] == 311:
740 #        timestamp = result.body['output']['common-header']['timestamp']
741 #        _set_appc_lcm_timestamp(req, timestamp)
742 #        appc_lcm_request(onap_ip, req)
743 #        return
744     else:
745         raise Exception("{} - {}".format(result.body['output']['status']['code'],
746                                          result.body['output']['status']['message']))
747     #print(result)
748     return result.body['output']['status']['code']
749
750
751 def appc_lcm_status_request(onap_ip, req):
752     api = _init_python_appc_lcm_api(onap_ip)
753     status_body = _build_appc_lcm_status_body(req)
754     _set_appc_lcm_timestamp(status_body)
755
756     result = api.lcm.action_status(body=status_body, params={}, headers={})
757
758     if result.body['output']['status']['code'] == 400:
759         status = json.loads(result.body['output']['payload'])
760         return status
761     else:
762         raise Exception("{} - {}".format(result.body['output']['status']['code'],
763                                          result.body['output']['status']['message']))
764
765
766 def confirm_appc_lcm_action(onap_ip, req, check_appc_result):
767     print("Checking LCM {} Status".format(req['input']['action']))
768
769     while True:
770         time.sleep(2)
771         status = appc_lcm_status_request(onap_ip, req)
772         print(status['status'])
773         if status['status'] == 'SUCCESSFUL':
774             return
775         elif status['status'] == 'IN_PROGRESS':
776             continue
777         elif check_appc_result:
778             raise Exception("LCM {} {} - {}".format(req['input']['action'], status['status'], status['status-reason']))
779         else:
780             return
781
782
783 def execute_workflow(vfw_vnf_id, rancher_ip, onap_ip, use_oof_cache, if_close_loop_vfw, info_only, check_result):
784     print("\nExecuting workflow for VNF ID '{}' on Rancher with IP {} and ONAP with IP {}".format(
785         vfw_vnf_id, rancher_ip, onap_ip))
786     print("\nOOF Cache {}, is CL vFW {}, only info {}, check LCM result {}".format(use_oof_cache, if_close_loop_vfw,
787                                                                                    info_only, check_result))
788     x = threading.Thread(target=_run_osdf_resp_server, daemon=True)
789     x.start()
790     aai_data = load_aai_data(vfw_vnf_id, onap_ip)
791     print("\nvFWDT Service Information:")
792     print(json.dumps(aai_data, indent=4))
793     lcm_requests = build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw)
794     print("\nAnsible Inventory:")
795     for key in ansible_inventory:
796         print("[{}]".format(key))
797         for host in ansible_inventory[key]:
798             print(ansible_inventory[key][host])
799
800     if info_only:
801         return
802     print("\nDistribute Traffic Workflow Execution:")
803     for i in range(len(lcm_requests)):
804         req = lcm_requests[i]
805         print("APPC REQ {} - {}".format(i, req['input']['action']))
806         _set_appc_lcm_timestamp(req)
807         result = appc_lcm_request(onap_ip, req)
808         if result == 100:
809             confirm_appc_lcm_action(onap_ip, req, check_result)
810             #time.sleep(30)
811
812
813 #vnf_id, Rancher node IP, K8s node IP, use OOF cache, if close loop vfw, if info_only, if check APPC result
814 execute_workflow(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4].lower() == 'true', sys.argv[5].lower() == 'true',
815                  sys.argv[6].lower() == 'true', sys.argv[7].lower() == 'true')