413fa4aaf83f1c4f52799610ad21f5b297143b79
[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         'upgrade_software': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-software/'},
198         'upgrade_pre_check': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-pre-check/'},
199         'upgrade_post_check': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-post-check/'},
200         'action_status': {'method': 'POST', 'url': 'appc-provider-lcm:action-status/'},
201     }
202
203
204 def _init_python_aai_api(onap_ip):
205     api = API(
206         api_root_url="https://{}:30233/aai/v14/".format(onap_ip),
207         params={},
208         headers={
209             'Authorization': encode("AAI", "AAI"),
210             'X-FromAppId': 'SCRIPT',
211             'Accept': 'application/json',
212             'Content-Type': 'application/json',
213             'X-TransactionId': str(uuid.uuid4()),
214         },
215         timeout=30,
216         append_slash=False,
217         json_encode_body=True # encode body as json
218     )
219     api.add_resource(resource_name='aai', resource_class=AAIApiResource)
220     return api
221
222
223 def _init_python_has_api(onap_ip):
224     api = API(
225         api_root_url="https://{}:30275/v1/".format(onap_ip),
226         params={},
227         headers={
228             'Authorization': encode("admin1", "plan.15"),
229             'X-FromAppId': 'SCRIPT',
230             'Accept': 'application/json',
231             'Content-Type': 'application/json',
232             'X-TransactionId': str(uuid.uuid4()),
233         },
234         timeout=30,
235         append_slash=False,
236         json_encode_body=True # encode body as json
237     )
238     api.add_resource(resource_name='has', resource_class=HASApiResource)
239     return api
240
241
242 def _init_python_osdf_api(onap_ip):
243     api = API(
244         api_root_url="https://{}:30248/api/oof/v1/".format(onap_ip),
245         params={},
246         headers={
247             'Authorization': encode("test", "testpwd"),
248             'X-FromAppId': 'SCRIPT',
249             'Accept': 'application/json',
250             'Content-Type': 'application/json',
251             'X-TransactionId': str(uuid.uuid4()),
252         },
253         timeout=30,
254         append_slash=False,
255         json_encode_body=True # encode body as json
256     )
257     api.add_resource(resource_name='osdf', resource_class=OSDFApiResource)
258     return api
259
260
261 def _init_python_appc_lcm_api(onap_ip):
262     api = API(
263         api_root_url="https://{}:30230/restconf/operations/".format(onap_ip),
264         params={},
265         headers={
266             'Authorization': encode("admin", "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"),
267             'X-FromAppId': 'SCRIPT',
268             'Accept': 'application/json',
269             'Content-Type': 'application/json',
270         },
271         timeout=300,
272         append_slash=False,
273         json_encode_body=True # encode body as json
274     )
275     api.add_resource(resource_name='lcm', resource_class=APPCLcmApiResource)
276     return api
277
278
279 def load_aai_data(vfw_vnf_id, onap_ip):
280     api = _init_python_aai_api(onap_ip)
281     aai_data = {}
282     aai_data['service-info'] = {'global-customer-id': '', 'service-instance-id': '', 'service-type': ''}
283     aai_data['vfw-model-info'] = {'model-invariant-id': '', 'model-version-id': '', 'vnf-name': '', 'vnf-type': ''}
284     aai_data['vpgn-model-info'] = {'model-invariant-id': '', 'model-version-id': '', 'vnf-name': '', 'vnf-type': ''}
285     with _no_ssl_verification():
286         response = api.aai.generic_vnf(vfw_vnf_id, body=None, params={'depth': 2}, headers={})
287         aai_data['vfw-model-info']['model-invariant-id'] = response.body.get('model-invariant-id')
288         aai_data['vfw-model-info']['model-version-id'] = response.body.get('model-version-id')
289         aai_data['vfw-model-info']['vnf-name'] = response.body.get('vnf-name')
290         aai_data['vfw-model-info']['vnf-type'] = response.body.get('vnf-type')
291         aai_data['vf-module-id'] = response.body['vf-modules']['vf-module'][0]['vf-module-id']
292
293         related_to = "service-instance"
294         search_key = "customer.global-customer-id"
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']['global-customer-id'] = rl_data_list[0]['d_value']
297
298         search_key = "service-subscription.service-type"
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-type'] = rl_data_list[0]['d_value']
301
302         search_key = "service-instance.service-instance-id"
303         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
304         aai_data['service-info']['service-instance-id'] = rl_data_list[0]['d_value']
305
306         service_link = rl_data_list[0]['link']
307         response = api.aai.link(service_link, body=None, params={}, headers={})
308
309         related_to = "generic-vnf"
310         search_key = "generic-vnf.vnf-id"
311         rl_data_list = _get_aai_rel_link_data(data=response.body, related_to=related_to, search_key=search_key)
312         for i in range(0, len(rl_data_list)):
313             vnf_id = rl_data_list[i]['d_value']
314
315             if vnf_id != vfw_vnf_id:
316                 vnf_link = rl_data_list[i]['link']
317                 response = api.aai.link(vnf_link, body=None, params={}, headers={})
318                 if aai_data['vfw-model-info']['model-invariant-id'] != response.body.get('model-invariant-id'):
319                     aai_data['vpgn-model-info']['model-invariant-id'] = response.body.get('model-invariant-id')
320                     aai_data['vpgn-model-info']['model-version-id'] = response.body.get('model-version-id')
321                     aai_data['vpgn-model-info']['vnf-name'] = response.body.get('vnf-name')
322                     aai_data['vpgn-model-info']['vnf-type'] = response.body.get('vnf-type')
323                     break
324     return aai_data
325
326
327 def _osdf_request(rancher_ip, onap_ip, aai_data, exclude, use_oof_cache):
328     dirname = os.path.join('templates/oof-cache/', aai_data['vf-module-id'])
329     if exclude:
330         file = os.path.join(dirname, 'sample-osdf-excluded.json')
331     else:
332         file = os.path.join(dirname, 'sample-osdf-required.json')
333     if use_oof_cache and os.path.exists(file):
334         migrate_from = json.loads(open(file).read())
335         return migrate_from
336
337     print('Making OSDF request for excluded {}'.format(str(exclude)))
338     api = _init_python_osdf_api(onap_ip)
339     request_id = str(uuid.uuid4())
340     transaction_id = str(uuid.uuid4())
341     callback_url = "http://{}:9000/osdfCallback/".format(str(rancher_ip))
342     template = json.loads(open('templates/osdfRequest.json').read())
343     template["requestInfo"]["transactionId"] = transaction_id
344     template["requestInfo"]["requestId"] = request_id
345     template["requestInfo"]["callbackUrl"] = callback_url
346     template["serviceInfo"]["serviceInstanceId"] = aai_data['service-info']['service-instance-id']
347     template["placementInfo"]["requestParameters"]["chosenCustomerId"] = aai_data['service-info']['global-customer-id']
348     template["placementInfo"]["placementDemands"][0]["resourceModelInfo"]["modelInvariantId"] =\
349         aai_data['vfw-model-info']['model-invariant-id']
350     template["placementInfo"]["placementDemands"][0]["resourceModelInfo"]["modelVersionId"] =\
351         aai_data['vfw-model-info']['model-version-id']
352     template["placementInfo"]["placementDemands"][1]["resourceModelInfo"]["modelInvariantId"] =\
353         aai_data['vpgn-model-info']['model-invariant-id']
354     template["placementInfo"]["placementDemands"][1]["resourceModelInfo"]["modelVersionId"] =\
355         aai_data['vpgn-model-info']['model-version-id']
356     if exclude:
357         template["placementInfo"]["placementDemands"][0]["excludedCandidates"][0]["identifiers"].\
358             append(aai_data['vf-module-id'])
359         del template["placementInfo"]["placementDemands"][0]["requiredCandidates"]
360     else:
361         template["placementInfo"]["placementDemands"][0]["requiredCandidates"][0]["identifiers"].\
362             append(aai_data['vf-module-id'])
363         del template["placementInfo"]["placementDemands"][0]["excludedCandidates"]
364
365     #print(json.dumps(template, indent=4))
366
367     with _no_ssl_verification():
368         response = api.osdf.placement(body=template, params={}, headers={})
369         #if response.body.get('error_message') is not None:
370         #    raise Exception(response.body['error_message']['explanation'])
371
372     counter = 0
373     while counter < 600 and osdf_response["last"]["id"] != request_id:
374         time.sleep(1)
375         if counter % 20 == 0:
376             print("solving")
377         counter += 1
378
379     if osdf_response["last"]["id"] == request_id:
380         status = osdf_response["last"]["data"]["requestStatus"]
381         if status == "completed":
382             result = {
383                 "solution": osdf_response["last"]["data"]["solutions"]["placementSolutions"]
384             }
385             if not os.path.exists(dirname):
386                 os.makedirs(dirname)
387             f = open(file, 'w+')
388             f.write(json.dumps(result, indent=4))
389             f.close()
390             return result
391         else:
392             message = osdf_response["last"]["data"]["statusMessage"]
393             raise Exception("OOF request {}: {}".format(status, message))
394     else:
395         raise Exception("No response for OOF request")
396
397
398 def _has_request(onap_ip, aai_data, exclude, use_oof_cache):
399     dirname = os.path.join('templates/oof-cache/', aai_data['vf-module-id'])
400     if exclude:
401         file = os.path.join(dirname, 'sample-has-excluded.json')
402     else:
403         file = os.path.join(dirname, 'sample-has-required.json')
404     if use_oof_cache and os.path.exists(file):
405         migrate_from = json.loads(open(file).read())
406         return migrate_from
407
408     print('Making HAS request for excluded {}'.format(str(exclude)))
409     api = _init_python_has_api(onap_ip)
410     request_id = str(uuid.uuid4())
411     template = json.loads(open('templates/hasRequest.json').read())
412     result = {}
413     template['name'] = request_id
414     node = template['template']['parameters']
415     node['chosen_customer_id'] = aai_data['service-info']['global-customer-id']
416     node['service_id'] = aai_data['service-info']['service-instance-id']
417     node = template['template']['demands']['vFW-SINK'][0]
418     node['attributes']['model-invariant-id'] = aai_data['vfw-model-info']['model-invariant-id']
419     node['attributes']['model-version-id'] = aai_data['vfw-model-info']['model-version-id']
420     if exclude:
421         node['excluded_candidates'][0]['candidate_id'][0] = aai_data['vf-module-id']
422         del node['required_candidates']
423     else:
424         node['required_candidates'][0]['candidate_id'][0] = aai_data['vf-module-id']
425         del node['excluded_candidates']
426     node = template['template']['demands']['vPGN'][0]
427     node['attributes']['model-invariant-id'] = aai_data['vpgn-model-info']['model-invariant-id']
428     node['attributes']['model-version-id'] = aai_data['vpgn-model-info']['model-version-id']
429
430     #print(json.dumps(template, indent=4))
431
432     with _no_ssl_verification():
433         response = api.has.plans(body=template, params={}, headers={})
434         if response.body.get('error_message') is not None:
435             raise Exception(response.body['error_message']['explanation'])
436         else:
437             plan_id = response.body['id']
438             response = api.has.plan(plan_id, body=None, params={}, headers={})
439             status = response.body['plans'][0]['status']
440             while status != 'done' and status != 'error':
441                 print(status)
442                 response = api.has.plan(plan_id, body=None, params={}, headers={})
443                 status = response.body['plans'][0]['status']
444             if status == 'done':
445                 result = response.body['plans'][0]['recommendations'][0]
446             else:
447                 raise Exception(response.body['plans'][0]['message'])
448
449     if not os.path.exists(dirname):
450         os.makedirs(dirname)
451     f = open(file, 'w+')
452     f.write(json.dumps(result, indent=4))
453     f.close()
454     return result
455
456
457 def _extract_has_appc_identifiers(has_result, demand):
458     if demand == 'vPGN':
459         v_server = has_result[demand]['attributes']['vservers'][0]
460     else:
461         if len(has_result[demand]['attributes']['vservers'][0]['l-interfaces']) == 4:
462             v_server = has_result[demand]['attributes']['vservers'][0]
463         else:
464             v_server = has_result[demand]['attributes']['vservers'][1]
465     for itf in v_server['l-interfaces']:
466         if itf['ipv4-addresses'][0].startswith("10.0."):
467             ip = itf['ipv4-addresses'][0]
468             break
469
470     if v_server['vserver-name'] in hostname_cache and demand != 'vPGN':
471         v_server['vserver-name'] = v_server['vserver-name'].replace("01", "02")
472     hostname_cache.append(v_server['vserver-name'])
473
474     config = {
475         'vnf-id': has_result[demand]['attributes']['nf-id'],
476         'vf-module-id': has_result[demand]['attributes']['vf-module-id'],
477         'ip': ip,
478         'vserver-id': v_server['vserver-id'],
479         'vserver-name': v_server['vserver-name'],
480         'vnfc-type': demand.lower(),
481         'physical-location-id': has_result[demand]['attributes']['physical-location-id']
482     }
483     ansible_inventory_entry = "{} ansible_ssh_host={} ansible_ssh_user=ubuntu".format(config['vserver-name'], config['ip'])
484     if demand.lower() not in ansible_inventory:
485         ansible_inventory[demand.lower()] = {}
486     ansible_inventory[demand.lower()][config['vserver-name']] = ansible_inventory_entry
487     return config
488
489
490 def _extract_osdf_appc_identifiers(has_result, demand):
491     if demand == 'vPGN':
492         v_server = has_result[demand]['vservers'][0]
493     else:
494         if len(has_result[demand]['vservers'][0]['l-interfaces']) == 4:
495             v_server = has_result[demand]['vservers'][0]
496         else:
497             v_server = has_result[demand]['vservers'][1]
498     for itf in v_server['l-interfaces']:
499         if itf['ipv4-addresses'][0].startswith("10.0."):
500             ip = itf['ipv4-addresses'][0]
501             break
502
503     if v_server['vserver-name'] in hostname_cache and demand != 'vPGN':
504         v_server['vserver-name'] = v_server['vserver-name'].replace("01", "02")
505     hostname_cache.append(v_server['vserver-name'])
506
507     config = {
508         'vnf-id': has_result[demand]['nf-id'],
509         'vf-module-id': has_result[demand]['vf-module-id'],
510         'ip': ip,
511         'vserver-id': v_server['vserver-id'],
512         'vserver-name': v_server['vserver-name'],
513         'vnfc-type': demand.lower(),
514         'physical-location-id': has_result[demand]['locationId']
515     }
516     ansible_inventory_entry = "{} ansible_ssh_host={} ansible_ssh_user=ubuntu".format(config['vserver-name'], config['ip'])
517     if demand.lower() not in ansible_inventory:
518         ansible_inventory[demand.lower()] = {}
519     ansible_inventory[demand.lower()][config['vserver-name']] = ansible_inventory_entry
520     return config
521
522
523 def _extract_has_appc_dt_config(has_result, demand):
524     if demand == 'vPGN':
525         return {}
526     else:
527         config = {
528             "nf-type": has_result[demand]['attributes']['nf-type'],
529             "nf-name": has_result[demand]['attributes']['nf-name'],
530             "vf-module-name": has_result[demand]['attributes']['vf-module-name'],
531             "vnf-type": has_result[demand]['attributes']['vnf-type'],
532             "service_instance_id": "319e60ef-08b1-47aa-ae92-51b97f05e1bc",
533             "cloudClli": has_result[demand]['attributes']['physical-location-id'],
534             "nf-id": has_result[demand]['attributes']['nf-id'],
535             "vf-module-id": has_result[demand]['attributes']['vf-module-id'],
536             "aic_version": has_result[demand]['attributes']['aic_version'],
537             "ipv4-oam-address": has_result[demand]['attributes']['ipv4-oam-address'],
538             "vnfHostName": has_result[demand]['candidate']['host_id'],
539             "ipv6-oam-address": has_result[demand]['attributes']['ipv6-oam-address'],
540             "cloudOwner": has_result[demand]['candidate']['cloud_owner'],
541             "isRehome": has_result[demand]['candidate']['is_rehome'],
542             "locationId": has_result[demand]['candidate']['location_id'],
543             "locationType": has_result[demand]['candidate']['location_type'],
544             'vservers': has_result[demand]['attributes']['vservers']
545         }
546         return config
547
548
549 def _extract_osdf_appc_dt_config(osdf_result, demand):
550     if demand == 'vPGN':
551         return {}
552     else:
553         return osdf_result[demand]
554
555
556 def _build_config_from_has(has_result):
557     v_pgn_result = _extract_has_appc_identifiers(has_result, 'vPGN')
558     v_fw_result = _extract_has_appc_identifiers(has_result, 'vFW-SINK')
559     dt_config = _extract_has_appc_dt_config(has_result, 'vFW-SINK')
560
561     config = {
562         'vPGN': v_pgn_result,
563         'vFW-SINK': v_fw_result
564     }
565     #print(json.dumps(config, indent=4))
566     config['dt-config'] = {
567         'destinations': [dt_config]
568     }
569     return config
570
571
572 def _adapt_osdf_result(osdf_result):
573     result = {}
574     demand = _build_osdf_result_demand(osdf_result["solution"][0][0])
575     result[demand["name"]] = demand["value"]
576     demand = _build_osdf_result_demand(osdf_result["solution"][0][1])
577     result[demand["name"]] = demand["value"]
578     return result
579
580
581 def _build_osdf_result_demand(solution):
582     result = {}
583     result["name"] = solution["resourceModuleName"]
584     value = {"candidateId": solution["solution"]["identifiers"][0]}
585     for info in solution["assignmentInfo"]:
586         value[info["key"]] = info["value"]
587     result["value"] = value
588     return result
589
590
591 def _build_config_from_osdf(osdf_result):
592     osdf_result = _adapt_osdf_result(osdf_result)
593     v_pgn_result = _extract_osdf_appc_identifiers(osdf_result, 'vPGN')
594     v_fw_result = _extract_osdf_appc_identifiers(osdf_result, 'vFW-SINK')
595     dt_config = _extract_osdf_appc_dt_config(osdf_result, 'vFW-SINK')
596
597     config = {
598         'vPGN': v_pgn_result,
599         'vFW-SINK': v_fw_result
600     }
601     #print(json.dumps(config, indent=4))
602     config['dt-config'] = {
603         'destinations': [dt_config]
604     }
605     return config
606
607
608 def _build_appc_lcm_dt_payload(demand, oof_config, action, traffic_presence):
609     is_check = traffic_presence is not None
610     oof_config = copy.deepcopy(oof_config)
611     #if is_vpkg:
612     #    node_list = "[ {} ]".format(oof_config['vPGN']['vserver-id'])
613     #else:
614     #    node_list = "[ {} ]".format(oof_config['vFW-SINK']['vserver-id'])
615     book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
616     config = oof_config[demand]
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_upgrade_payload(demand, oof_config, action, old_version, new_version):
648     oof_config = copy.deepcopy(oof_config)
649     book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
650     config = oof_config[demand]
651
652     file_content = {}  #oof_config['dt-config']
653
654     config = {
655         "configuration-parameters": {
656             #"node_list": node_list,
657             "ne_id": config['vserver-name'],
658             "fixed_ip_address": config['ip'],
659             "file_parameter_content":  json.dumps(file_content),
660             "existing-software-version": old_version,
661             "new-software-version": new_version
662         }
663     }
664     if book_name != '':
665         config["configuration-parameters"]["book_name"] = book_name
666     payload = json.dumps(config)
667     return payload
668
669
670 def _build_appc_lcm_status_body(req):
671     payload = {
672         'request-id': req['input']['common-header']['request-id'],
673         'sub-request-id': req['input']['common-header']['sub-request-id'],
674         'originator-id': req['input']['common-header']['originator-id']
675     }
676     payload = json.dumps(payload)
677     template = json.loads(open('templates/appcRestconfLcm.json').read())
678     template['input']['action'] = 'ActionStatus'
679     template['input']['payload'] = payload
680     template['input']['common-header']['request-id'] = req['input']['common-header']['request-id']
681     template['input']['common-header']['sub-request-id'] = str(uuid.uuid4())
682     template['input']['action-identifiers']['vnf-id'] = req['input']['action-identifiers']['vnf-id']
683     return template
684
685
686 def _build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
687     if is_vpkg:
688         demand = 'vPGN'
689     else:
690         demand = 'vFW-SINK'
691     payload = _build_appc_lcm_dt_payload(demand, config, action, traffic_presence)
692     return _build_appc_lcm_request_body(payload, demand, config, req_id, action)
693
694
695 def _build_appc_lcm_upgrade_request_body(config, req_id, action, old_version, new_version):
696     demand = 'vFW-SINK'
697     payload = _build_appc_lcm_upgrade_payload(demand, config, action, old_version, new_version)
698     return _build_appc_lcm_request_body(payload, demand, config, req_id, action)
699
700
701 def _build_appc_lcm_request_body(payload, demand, config, req_id, action):
702     template = json.loads(open('templates/appcRestconfLcm.json').read())
703     template['input']['action'] = action
704     template['input']['payload'] = payload
705     template['input']['common-header']['request-id'] = req_id
706     template['input']['common-header']['sub-request-id'] = str(uuid.uuid4())
707     template['input']['action-identifiers']['vnf-id'] = config[demand]['vnf-id']
708     return template
709
710
711 def _set_appc_lcm_timestamp(body, timestamp=None):
712     if timestamp is None:
713         t = datetime.utcnow() + timedelta(seconds=-10)
714         timestamp = t.strftime('%Y-%m-%dT%H:%M:%S.244Z')
715     body['input']['common-header']['timestamp'] = timestamp
716
717
718 def build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw, new_version=None):
719     if_has = True
720
721     if if_has:
722         migrate_from = _has_request(onap_ip, aai_data, False, use_oof_cache)
723
724         if if_close_loop_vfw:
725             migrate_to = migrate_from
726         else:
727             migrate_to = _has_request(onap_ip, aai_data, True, use_oof_cache)
728
729         migrate_from = _build_config_from_has(migrate_from)
730         migrate_to = _build_config_from_has(migrate_to)
731     else:
732         migrate_from = _osdf_request(rancher_ip, onap_ip, aai_data, False, use_oof_cache)
733
734         if if_close_loop_vfw:
735             migrate_to = migrate_from
736         else:
737             migrate_to = _osdf_request(rancher_ip, onap_ip, aai_data, True, use_oof_cache)
738
739         migrate_from = _build_config_from_osdf(migrate_from)
740         migrate_to = _build_config_from_osdf(migrate_to)
741
742     #print(json.dumps(migrate_from, indent=4))
743     #print(json.dumps(migrate_to, indent=4))
744     req_id = str(uuid.uuid4())
745     result = list()
746     old_version = 2.0
747     if_dt_only = new_version is None
748     if new_version is not None and new_version != "1.0":
749         old_version = 1.0
750
751     if if_dt_only:
752         #_build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
753         payload_dt_check_vpkg = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
754         payload_dt_vpkg_to = _build_appc_lcm_dt_request_body(True, migrate_to, req_id, 'DistributeTraffic')
755         payload_dt_check_vfw_from = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck',
756                                                                  False)
757         payload_dt_check_vfw_to = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
758
759         requests = list()
760         requests.append({"payload": payload_dt_vpkg_to, "breakOnFailure": True, "description": "Migrating source vFW traffic to destination vFW"})
761         requests.append({"payload": payload_dt_check_vfw_from, "breakOnFailure": True, "description": "Checking traffic has been stopped on the source vFW"})
762         requests.append({"payload": payload_dt_check_vfw_to, "breakOnFailure": True, "description": "Checking traffic has appeared on the destination vFW"})
763         result.append({"payload": payload_dt_check_vpkg, "breakOnFailure": False, "description": "Check current traffic destination on vPGN",
764                       "workflow": {"requests": requests, "description": "Migrate Traffic and Verify"}})
765     else:
766         #_build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
767         payload_dt_check_vpkg = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
768         payload_dt_vpkg_to = _build_appc_lcm_dt_request_body(True, migrate_to, req_id, 'DistributeTraffic')
769         payload_dt_vpkg_from = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTraffic')
770
771         payload_dt_check_vfw_from_absent = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck', False)
772         payload_dt_check_vfw_to_present = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
773         payload_dt_check_vfw_to_absent = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', False)
774         payload_dt_check_vfw_from_present = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck', True)
775
776         payload_old_version_check_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradePreCheck', old_version, new_version)
777         payload_new_version_check_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradePostCheck', old_version, new_version)
778         payload_upgrade_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradeSoftware', old_version, new_version)
779
780         requests = list()
781         migrate_requests = list()
782         migrate_requests.append({"payload": payload_dt_vpkg_to, "breakOnFailure": True, "description": "Migrating source vFW traffic to destination vFW"})
783         migrate_requests.append({"payload": payload_dt_check_vfw_from_absent, "breakOnFailure": True, "description": "Checking traffic has been stopped on the source vFW"})
784         migrate_requests.append({"payload": payload_dt_check_vfw_to_present, "breakOnFailure": True, "description": "Checking traffic has appeared on the destination vFW"})
785
786         requests.append({"payload": payload_dt_check_vpkg, "breakOnFailure": False, "description": "Check current traffic destination on vPGN",
787                         "workflow": {"requests": migrate_requests, "description": "Migrate Traffic and Verify"}})
788         requests.append({"payload": payload_upgrade_vfw_from, "breakOnFailure": True, "description": "Upgrading Software on source vFW"})
789         requests.append({"payload": payload_new_version_check_vfw_from, "breakOnFailure": True, "description": "Check current software version on source vFW"})
790         requests.append({"payload": payload_dt_vpkg_from, "breakOnFailure": True, "description": "Migrating destination vFW traffic to source vFW"})
791         requests.append({"payload": payload_dt_check_vfw_to_absent, "breakOnFailure": True, "description": "Checking traffic has been stopped on the destination vFW"})
792         requests.append({"payload": payload_dt_check_vfw_from_present, "breakOnFailure": True, "description": "Checking traffic has appeared on the source vFW"})
793
794         result.append({"payload": payload_old_version_check_vfw_from, "breakOnFailure": False, "description": "Check current software version on source vFW",
795                       "workflow": {"requests": requests, "description": "Migrate Traffic and Upgrade Software"}})
796
797     return result
798
799
800 def appc_lcm_request(onap_ip, req):
801     api = _init_python_appc_lcm_api(onap_ip)
802     with _no_ssl_verification():
803     #print(json.dumps(req, indent=4))
804         if req['input']['action'] == "DistributeTraffic":
805             result = api.lcm.distribute_traffic(body=req, params={}, headers={})
806         elif req['input']['action'] == "DistributeTrafficCheck":
807             result = api.lcm.distribute_traffic_check(body=req, params={}, headers={})
808         elif req['input']['action'] == "UpgradeSoftware":
809             result = api.lcm.upgrade_software(body=req, params={}, headers={})
810         elif req['input']['action'] == "UpgradePreCheck":
811             result = api.lcm.upgrade_pre_check(body=req, params={}, headers={})
812         elif req['input']['action'] == "UpgradePostCheck":
813             result = api.lcm.upgrade_post_check(body=req, params={}, headers={})
814         else:
815             raise Exception("{} action not supported".format(req['input']['action']))
816
817     if result.body['output']['status']['code'] == 400:
818         print("SUCCESSFUL")
819     elif result.body['output']['status']['code'] == 100:
820         print("ACCEPTED")
821 #    elif result.body['output']['status']['code'] == 311:
822 #        timestamp = result.body['output']['common-header']['timestamp']
823 #        _set_appc_lcm_timestamp(req, timestamp)
824 #        appc_lcm_request(onap_ip, req)
825 #        return
826     else:
827         raise Exception("{} - {}".format(result.body['output']['status']['code'],
828                                          result.body['output']['status']['message']))
829     #print(result)
830     return result.body['output']['status']['code']
831
832
833 def appc_lcm_status_request(onap_ip, req):
834     api = _init_python_appc_lcm_api(onap_ip)
835     status_body = _build_appc_lcm_status_body(req)
836     _set_appc_lcm_timestamp(status_body)
837
838     with _no_ssl_verification():
839         result = api.lcm.action_status(body=status_body, params={}, headers={})
840
841     if result.body['output']['status']['code'] == 400:
842         status = json.loads(result.body['output']['payload'])
843         return status
844     else:
845         raise Exception("{} - {}".format(result.body['output']['status']['code'],
846                                          result.body['output']['status']['message']))
847
848
849 def confirm_appc_lcm_action(onap_ip, req, check_appc_result):
850     print("APPC LCM << {} >> [Status]".format(req['input']['action']))
851
852     while True:
853         time.sleep(2)
854         status = appc_lcm_status_request(onap_ip, req)
855         print(status['status'])
856         if status['status'] == 'SUCCESSFUL':
857             return True
858         elif status['status'] == 'IN_PROGRESS':
859             continue
860         elif check_appc_result:
861             print("APPC LCM <<{}>> [{} - {}]".format(req['input']['action'], status['status'], status['status-reason']))
862             return False
863         else:
864             return True
865
866
867 def _execute_lcm_requests(workflow, onap_ip, check_result):
868     lcm_requests = workflow["requests"]
869     print("WORKFLOW << {} >>".format(workflow["description"]))
870     for i in range(len(lcm_requests)):
871        req = lcm_requests[i]["payload"]
872        #print(json.dumps(req, indent=4))
873        print("APPC LCM << {} >> [{}]".format(req['input']['action'], lcm_requests[i]["description"]))
874        _set_appc_lcm_timestamp(req)
875        result = appc_lcm_request(onap_ip, req)
876        if result == 100:
877            conf_result = confirm_appc_lcm_action(onap_ip, req, check_result)
878            if not conf_result:
879                if lcm_requests[i]["breakOnFailure"]:
880                    raise Exception("APPC LCM << {} >> FAILED".format(req['input']['action']))
881                elif "workflow" in lcm_requests[i]:
882                    print("WORKFLOW << {} >> SKIP".format(lcm_requests[i]["workflow"]["description"]))
883            elif "workflow" in lcm_requests[i]:
884                _execute_lcm_requests(lcm_requests[i]["workflow"], onap_ip, check_result)
885             
886            #time.sleep(30)
887
888
889
890 def execute_workflow(vfw_vnf_id, rancher_ip, onap_ip, use_oof_cache, if_close_loop_vfw, info_only, check_result, new_version=None):
891     print("\nExecuting workflow for VNF ID '{}' on Rancher with IP {} and ONAP with IP {}".format(
892         vfw_vnf_id, rancher_ip, onap_ip))
893     print("\nOOF Cache {}, is CL vFW {}, only info {}, check LCM result {}".format(use_oof_cache, if_close_loop_vfw,
894                                                                                    info_only, check_result))
895     if new_version is not None:
896         print("\nNew vFW software version {}\n".format(new_version))
897
898     x = threading.Thread(target=_run_osdf_resp_server, daemon=True)
899     x.start()
900     aai_data = load_aai_data(vfw_vnf_id, onap_ip)
901     print("\nvFWDT Service Information:")
902     print(json.dumps(aai_data, indent=4))
903     lcm_requests = build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw, new_version)
904     print("\nAnsible Inventory:")
905     inventory = "[host]\nlocalhost   ansible_connection=local\n"
906     for key in ansible_inventory:
907         inventory += str("[{}]\n").format(key)
908         for host in ansible_inventory[key]:
909             inventory += str("{}\n").format(ansible_inventory[key][host])
910
911     print(inventory)
912     f = open("Ansible_inventory", 'w+')
913     f.write(inventory)
914     f.close()
915
916     if info_only:
917         return
918     print("\nDistribute Traffic Workflow Execution:")
919
920     _execute_lcm_requests({"requests": lcm_requests, "description": "Migrate vFW Traffic Conditionally"}, onap_ip, check_result)
921
922
923 help = """\npython3 workflow.py <VNF-ID> <RANCHER-NODE-IP> <K8S-NODE-IP> <IF-CACHE> <IF-VFWCL> <INITIAL-ONLY> <CHECK-STATUS> <VERSION>
924 \n<VNF-ID> - vnf-id of vFW VNF instance that traffic should be migrated out from
925 <RANCHER-NODE-IP> - External IP of ONAP Rancher Node i.e. 10.12.5.160 (If Rancher Node is missing this is NFS node)
926 <K8S-NODE-IP> - External IP of ONAP K8s Worker Node i.e. 10.12.5.212
927 <IF-CACHE> - If script should use and build OOF response cache (cache it speed-ups further executions of script)
928 <IF-VFWCL> - If instead of vFWDT service instance vFW or vFWCL one is used (should be False always)
929 <INITIAL-ONLY> - If only configuration information will be collected (True for initial phase and False for full execution of workflow)
930 <CHECK-STATUS> - If APPC LCM action status should be verified and FAILURE should stop workflow (when False FAILED status of LCM action does not stop execution of further LCM actions)
931 <VERSION> - New version of vFW - for tests '1.0' or '2.0'. Ommit when traffic distribution only\n"""
932
933 for key in sys.argv:
934     if key == "-h" or key == "--help":
935         print(help)
936         sys.exit()
937
938 new_version = None
939 if len(sys.argv) > 8:
940     new_version = sys.argv[8]
941
942 #vnf_id, Rancher node IP, K8s node IP, use OOF cache, if close loop vfw, if info_only, if check APPC result
943 execute_workflow(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4].lower() == 'true', sys.argv[5].lower() == 'true',
944                  sys.argv[6].lower() == 'true', sys.argv[7].lower() == 'true', new_version)