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