vFW DT tutorial improvements 06/96506/2
authorLukasz Rajewski <lukasz.rajewski@orange.com>
Wed, 2 Oct 2019 11:01:54 +0000 (13:01 +0200)
committerBrian Freeman <bf1936@att.com>
Wed, 2 Oct 2019 14:38:42 +0000 (14:38 +0000)
- use of TLS for APPC
- vFW Upgrade workflow
- Help information for workflow
- script for configuration of workflow
- script for upgrade of vFW VMs to Upgrade demo

Change-Id: I26136ec20569c9c02abb1644c6e40b4733d8f5ea
Signed-off-by: Lukasz Rajewski <lukasz.rajewski@orange.com>
Issue-ID: INT-751

tutorials/vFWDT/playbooks/configure_ansible.sh [new file with mode: 0755]
tutorials/vFWDT/playbooks/server.py [new file with mode: 0755]
tutorials/vFWDT/playbooks/upgrade.sh [new file with mode: 0755]
tutorials/vFWDT/workflow/workflow.py

diff --git a/tutorials/vFWDT/playbooks/configure_ansible.sh b/tutorials/vFWDT/playbooks/configure_ansible.sh
new file mode 100755 (executable)
index 0000000..5c19796
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+# ============LICENSE_START=======================================================
+# Copyright (C) 2019 Orange
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ============LICENSE_END=========================================================
+
+
+ANSIBLE=`kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}' | grep appc-ansible`
+echo $ANSIBLE
+
+kubectl cp playbooks/onap.pem onap/$ANSIBLE:/opt/ansible-server/Playbooks/
+echo "Key file uploaded"
+
+cp workflow/Ansible_inventory playbooks/
+kubectl cp playbooks/Ansible_inventory onap/$ANSIBLE:/opt/ansible-server/Playbooks/
+echo "Ansible_inventory file uploaded"
+
+kubectl exec -n onap $ANSIBLE -- chmod 400 /opt/ansible-server/Playbooks/onap.pem 
+echo "Key file configured"
+
+#kubectl exec -n onap $ANSIBLE -- sed -i 's#\(private_key_file *= *\).*#\1/opt/ansible-server/Playbooks/onap.pem#'  /etc/ansible/ansible.cfg
+printf '[defaults]\nhost_key_checking = False\nprivate_key_file = /opt/ansible-server/Playbooks/onap.pem\n' > playbooks/ansible.cfg
+kubectl cp playbooks/ansible.cfg onap/$ANSIBLE:/etc/ansible/
+echo "Ansible conf modified"
+
+kubectl exec -n onap $ANSIBLE -- ansible -i /opt/ansible-server/Playbooks/Ansible_inventory vpgn,vfw-sink -m ping
+echo "Hosts PING test completed"
+
+sudo kubectl cp playbooks/vfw-sink onap/$ANSIBLE:/opt/ansible-server/Playbooks/
+echo "vFW-SINK Playbooks uploaded"
+
+sudo kubectl cp playbooks/vpgn onap/$ANSIBLE:/opt/ansible-server/Playbooks/
+echo "vPGN Playbooks uploaded"
+
+APPCDB=`kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}' | grep appc-db-0`
+echo $APPCDB
+
+kubectl exec -n onap $APPCDB -- mysql -u sdnctl -pgamma sdnctl -e'SELECT * FROM DEVICE_AUTHENTICATION WHERE PROTOCOL LIKE "ANSIBLE";'
+kubectl exec -n onap $APPCDB -- mysql -u sdnctl -pgamma sdnctl -e'UPDATE DEVICE_AUTHENTICATION SET URL = "http://appc-ansible-server:8000/Dispatch" WHERE PROTOCOL LIKE "ANSIBLE" AND PASSWORD IS NULL;'
+kubectl exec -n onap $APPCDB -- mysql -u sdnctl -pgamma sdnctl -e'UPDATE DEVICE_AUTHENTICATION SET PASSWORD = "admin" WHERE PROTOCOL LIKE "ANSIBLE" AND PASSWORD IS NULL;'
+kubectl exec -n onap $APPCDB -- mysql -u sdnctl -pgamma sdnctl -e'SELECT * FROM DEVICE_AUTHENTICATION WHERE PROTOCOL LIKE "ANSIBLE";'
+echo "APPC database configured for LCM commands"
diff --git a/tutorials/vFWDT/playbooks/server.py b/tutorials/vFWDT/playbooks/server.py
new file mode 100755 (executable)
index 0000000..7caa161
--- /dev/null
@@ -0,0 +1,32 @@
+'''
+/*-
+* ============LICENSE_START=======================================================
+* Copyright (C) 2019 Orange
+* ================================================================================
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*
+* ============LICENSE_END=========================================================
+*/
+'''
+
+from http.server import HTTPServer, SimpleHTTPRequestHandler, test
+import sys
+
+class CORSRequestHandler (SimpleHTTPRequestHandler):
+    def end_headers (self):
+        self.send_header('Access-Control-Allow-Origin', '*')
+        SimpleHTTPRequestHandler.end_headers(self)
+
+if __name__ == '__main__':
+    test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
+
diff --git a/tutorials/vFWDT/playbooks/upgrade.sh b/tutorials/vFWDT/playbooks/upgrade.sh
new file mode 100755 (executable)
index 0000000..0dd27d0
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+# ============LICENSE_START=======================================================
+# Copyright (C) 2019 Orange
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ============LICENSE_END=========================================================
+
+#change IP addresses and upload to playbooks folder darkstat and server.py before
+
+SINK1=10.254.184.217
+SINK2=10.254.184.210
+VFW1=10.254.184.208
+VFW2=10.254.184.216
+
+echo $VFW1 > vfw_mgt_ip.txt
+scp -oStrictHostKeyChecking=no -i onap.pem vfw_mgt_ip.txt root@$SINK1:/opt/config/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$SINK1 /etc/init.d/darkstat stop
+scp -oStrictHostKeyChecking=no -i onap.pem darkstat root@$SINK1:/usr/sbin/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$SINK1 /etc/init.d/darkstat start
+
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW1 "hostname > /opt/config/hostname.txt"
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW1 "echo '1.0' > /opt/config/version.txt"
+scp -oStrictHostKeyChecking=no -i onap.pem server.py root@$VFW1:/opt/config/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW1 "screen -d -m bash -c 'cd /opt/config && python3 /opt/config/server.py 80 > /dev/null 2>&1'"
+
+echo $VFW2 > vfw_mgt_ip.txt
+scp -oStrictHostKeyChecking=no -i onap.pem vfw_mgt_ip.txt root@$SINK2:/opt/config/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$SINK2 /etc/init.d/darkstat stop
+scp -oStrictHostKeyChecking=no -i onap.pem darkstat root@$SINK2:/usr/sbin/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$SINK2 /etc/init.d/darkstat start
+
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW2 "hostname > /opt/config/hostname.txt"
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW2 "echo '1.0' > /opt/config/version.txt"
+scp -oStrictHostKeyChecking=no -i onap.pem server.py root@$VFW2:/opt/config/
+ssh -oStrictHostKeyChecking=no -i onap.pem root@$VFW2 "screen -d -m bash -c 'cd /opt/config && python3 /opt/config/server.py 80 > /dev/null 2>&1'"
+
index ef3700a..413fa4a 100755 (executable)
@@ -194,6 +194,9 @@ class APPCLcmApiResource(Resource):
     actions = {
         'distribute_traffic': {'method': 'POST', 'url': 'appc-provider-lcm:distribute-traffic/'},
         'distribute_traffic_check': {'method': 'POST', 'url': 'appc-provider-lcm:distribute-traffic-check/'},
+        'upgrade_software': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-software/'},
+        'upgrade_pre_check': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-pre-check/'},
+        'upgrade_post_check': {'method': 'POST', 'url': 'appc-provider-lcm:upgrade-post-check/'},
         'action_status': {'method': 'POST', 'url': 'appc-provider-lcm:action-status/'},
     }
 
@@ -257,7 +260,7 @@ def _init_python_osdf_api(onap_ip):
 
 def _init_python_appc_lcm_api(onap_ip):
     api = API(
-        api_root_url="http://{}:30230/restconf/operations/".format(onap_ip),
+        api_root_url="https://{}:30230/restconf/operations/".format(onap_ip),
         params={},
         headers={
             'Authorization': encode("admin", "Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U"),
@@ -602,18 +605,15 @@ def _build_config_from_osdf(osdf_result):
     return config
 
 
-def _build_appc_lcm_dt_payload(is_vpkg, oof_config, book_name, traffic_presence):
+def _build_appc_lcm_dt_payload(demand, oof_config, action, traffic_presence):
     is_check = traffic_presence is not None
     oof_config = copy.deepcopy(oof_config)
     #if is_vpkg:
     #    node_list = "[ {} ]".format(oof_config['vPGN']['vserver-id'])
     #else:
     #    node_list = "[ {} ]".format(oof_config['vFW-SINK']['vserver-id'])
-
-    if is_vpkg:
-        config = oof_config['vPGN']
-    else:
-        config = oof_config['vFW-SINK']
+    book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
+    config = oof_config[demand]
     #node = {
     #    'site': config['physical-location-id'],
     #    'vnfc_type': config['vnfc-type'],
@@ -644,6 +644,29 @@ def _build_appc_lcm_dt_payload(is_vpkg, oof_config, book_name, traffic_presence)
     return payload
 
 
+def _build_appc_lcm_upgrade_payload(demand, oof_config, action, old_version, new_version):
+    oof_config = copy.deepcopy(oof_config)
+    book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
+    config = oof_config[demand]
+
+    file_content = {}  #oof_config['dt-config']
+
+    config = {
+        "configuration-parameters": {
+            #"node_list": node_list,
+            "ne_id": config['vserver-name'],
+            "fixed_ip_address": config['ip'],
+            "file_parameter_content":  json.dumps(file_content),
+            "existing-software-version": old_version,
+            "new-software-version": new_version
+        }
+    }
+    if book_name != '':
+        config["configuration-parameters"]["book_name"] = book_name
+    payload = json.dumps(config)
+    return payload
+
+
 def _build_appc_lcm_status_body(req):
     payload = {
         'request-id': req['input']['common-header']['request-id'],
@@ -660,14 +683,22 @@ def _build_appc_lcm_status_body(req):
     return template
 
 
-def _build_appc_lcm_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
+def _build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
     if is_vpkg:
         demand = 'vPGN'
     else:
         demand = 'vFW-SINK'
+    payload = _build_appc_lcm_dt_payload(demand, config, action, traffic_presence)
+    return _build_appc_lcm_request_body(payload, demand, config, req_id, action)
 
-    book_name = "{}/latest/ansible/{}/site.yml".format(demand.lower(), action.lower())
-    payload = _build_appc_lcm_dt_payload(is_vpkg, config, book_name, traffic_presence)
+
+def _build_appc_lcm_upgrade_request_body(config, req_id, action, old_version, new_version):
+    demand = 'vFW-SINK'
+    payload = _build_appc_lcm_upgrade_payload(demand, config, action, old_version, new_version)
+    return _build_appc_lcm_request_body(payload, demand, config, req_id, action)
+
+
+def _build_appc_lcm_request_body(payload, demand, config, req_id, action):
     template = json.loads(open('templates/appcRestconfLcm.json').read())
     template['input']['action'] = action
     template['input']['payload'] = payload
@@ -684,8 +715,8 @@ def _set_appc_lcm_timestamp(body, timestamp=None):
     body['input']['common-header']['timestamp'] = timestamp
 
 
-def build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw):
-    if_has = False
+def build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw, new_version=None):
+    if_has = True
 
     if if_has:
         migrate_from = _has_request(onap_ip, aai_data, False, use_oof_cache)
@@ -711,34 +742,82 @@ def build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache,
     #print(json.dumps(migrate_from, indent=4))
     #print(json.dumps(migrate_to, indent=4))
     req_id = str(uuid.uuid4())
-    payload_dt_check_vpkg = _build_appc_lcm_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
-    payload_dt_vpkg_to = _build_appc_lcm_request_body(True, migrate_to, req_id, 'DistributeTraffic')
-    payload_dt_check_vfw_from = _build_appc_lcm_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck',
-                                                             False)
-    payload_dt_check_vfw_to = _build_appc_lcm_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
-
     result = list()
-    result.append(payload_dt_check_vpkg)
-    result.append(payload_dt_vpkg_to)
-    result.append(payload_dt_check_vfw_from)
-    result.append(payload_dt_check_vfw_to)
+    old_version = 2.0
+    if_dt_only = new_version is None
+    if new_version is not None and new_version != "1.0":
+        old_version = 1.0
+
+    if if_dt_only:
+        #_build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
+        payload_dt_check_vpkg = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
+        payload_dt_vpkg_to = _build_appc_lcm_dt_request_body(True, migrate_to, req_id, 'DistributeTraffic')
+        payload_dt_check_vfw_from = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck',
+                                                                 False)
+        payload_dt_check_vfw_to = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
+
+        requests = list()
+        requests.append({"payload": payload_dt_vpkg_to, "breakOnFailure": True, "description": "Migrating source vFW traffic to destination vFW"})
+        requests.append({"payload": payload_dt_check_vfw_from, "breakOnFailure": True, "description": "Checking traffic has been stopped on the source vFW"})
+        requests.append({"payload": payload_dt_check_vfw_to, "breakOnFailure": True, "description": "Checking traffic has appeared on the destination vFW"})
+        result.append({"payload": payload_dt_check_vpkg, "breakOnFailure": False, "description": "Check current traffic destination on vPGN",
+                      "workflow": {"requests": requests, "description": "Migrate Traffic and Verify"}})
+    else:
+        #_build_appc_lcm_dt_request_body(is_vpkg, config, req_id, action, traffic_presence=None):
+        payload_dt_check_vpkg = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTrafficCheck', True)
+        payload_dt_vpkg_to = _build_appc_lcm_dt_request_body(True, migrate_to, req_id, 'DistributeTraffic')
+        payload_dt_vpkg_from = _build_appc_lcm_dt_request_body(True, migrate_from, req_id, 'DistributeTraffic')
+
+        payload_dt_check_vfw_from_absent = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck', False)
+        payload_dt_check_vfw_to_present = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', True)
+        payload_dt_check_vfw_to_absent = _build_appc_lcm_dt_request_body(False, migrate_to, req_id, 'DistributeTrafficCheck', False)
+        payload_dt_check_vfw_from_present = _build_appc_lcm_dt_request_body(False, migrate_from, req_id, 'DistributeTrafficCheck', True)
+
+        payload_old_version_check_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradePreCheck', old_version, new_version)
+        payload_new_version_check_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradePostCheck', old_version, new_version)
+        payload_upgrade_vfw_from =  _build_appc_lcm_upgrade_request_body(migrate_from, req_id, 'UpgradeSoftware', old_version, new_version)
+
+        requests = list()
+        migrate_requests = list()
+        migrate_requests.append({"payload": payload_dt_vpkg_to, "breakOnFailure": True, "description": "Migrating source vFW traffic to destination vFW"})
+        migrate_requests.append({"payload": payload_dt_check_vfw_from_absent, "breakOnFailure": True, "description": "Checking traffic has been stopped on the source vFW"})
+        migrate_requests.append({"payload": payload_dt_check_vfw_to_present, "breakOnFailure": True, "description": "Checking traffic has appeared on the destination vFW"})
+
+        requests.append({"payload": payload_dt_check_vpkg, "breakOnFailure": False, "description": "Check current traffic destination on vPGN",
+                        "workflow": {"requests": migrate_requests, "description": "Migrate Traffic and Verify"}})
+        requests.append({"payload": payload_upgrade_vfw_from, "breakOnFailure": True, "description": "Upgrading Software on source vFW"})
+        requests.append({"payload": payload_new_version_check_vfw_from, "breakOnFailure": True, "description": "Check current software version on source vFW"})
+        requests.append({"payload": payload_dt_vpkg_from, "breakOnFailure": True, "description": "Migrating destination vFW traffic to source vFW"})
+        requests.append({"payload": payload_dt_check_vfw_to_absent, "breakOnFailure": True, "description": "Checking traffic has been stopped on the destination vFW"})
+        requests.append({"payload": payload_dt_check_vfw_from_present, "breakOnFailure": True, "description": "Checking traffic has appeared on the source vFW"})
+
+        result.append({"payload": payload_old_version_check_vfw_from, "breakOnFailure": False, "description": "Check current software version on source vFW",
+                      "workflow": {"requests": requests, "description": "Migrate Traffic and Upgrade Software"}})
+
     return result
 
 
 def appc_lcm_request(onap_ip, req):
     api = _init_python_appc_lcm_api(onap_ip)
+    with _no_ssl_verification():
     #print(json.dumps(req, indent=4))
-    if req['input']['action'] == "DistributeTraffic":
-        result = api.lcm.distribute_traffic(body=req, params={}, headers={})
-    elif req['input']['action'] == "DistributeTrafficCheck":
-        result = api.lcm.distribute_traffic_check(body=req, params={}, headers={})
-    else:
-        raise Exception("{} action not supported".format(req['input']['action']))
+        if req['input']['action'] == "DistributeTraffic":
+            result = api.lcm.distribute_traffic(body=req, params={}, headers={})
+        elif req['input']['action'] == "DistributeTrafficCheck":
+            result = api.lcm.distribute_traffic_check(body=req, params={}, headers={})
+        elif req['input']['action'] == "UpgradeSoftware":
+            result = api.lcm.upgrade_software(body=req, params={}, headers={})
+        elif req['input']['action'] == "UpgradePreCheck":
+            result = api.lcm.upgrade_pre_check(body=req, params={}, headers={})
+        elif req['input']['action'] == "UpgradePostCheck":
+            result = api.lcm.upgrade_post_check(body=req, params={}, headers={})
+        else:
+            raise Exception("{} action not supported".format(req['input']['action']))
 
     if result.body['output']['status']['code'] == 400:
-        print("Request Completed")
+        print("SUCCESSFUL")
     elif result.body['output']['status']['code'] == 100:
-        print("Request Accepted. Receiving result status...")
+        print("ACCEPTED")
 #    elif result.body['output']['status']['code'] == 311:
 #        timestamp = result.body['output']['common-header']['timestamp']
 #        _set_appc_lcm_timestamp(req, timestamp)
@@ -756,7 +835,8 @@ def appc_lcm_status_request(onap_ip, req):
     status_body = _build_appc_lcm_status_body(req)
     _set_appc_lcm_timestamp(status_body)
 
-    result = api.lcm.action_status(body=status_body, params={}, headers={})
+    with _no_ssl_verification():
+        result = api.lcm.action_status(body=status_body, params={}, headers={})
 
     if result.body['output']['status']['code'] == 400:
         status = json.loads(result.body['output']['payload'])
@@ -767,33 +847,60 @@ def appc_lcm_status_request(onap_ip, req):
 
 
 def confirm_appc_lcm_action(onap_ip, req, check_appc_result):
-    print("Checking LCM {} Status".format(req['input']['action']))
+    print("APPC LCM << {} >> [Status]".format(req['input']['action']))
 
     while True:
         time.sleep(2)
         status = appc_lcm_status_request(onap_ip, req)
         print(status['status'])
         if status['status'] == 'SUCCESSFUL':
-            return
+            return True
         elif status['status'] == 'IN_PROGRESS':
             continue
         elif check_appc_result:
-            raise Exception("LCM {} {} - {}".format(req['input']['action'], status['status'], status['status-reason']))
+            print("APPC LCM <<{}>> [{} - {}]".format(req['input']['action'], status['status'], status['status-reason']))
+            return False
         else:
-            return
+            return True
 
 
-def execute_workflow(vfw_vnf_id, rancher_ip, onap_ip, use_oof_cache, if_close_loop_vfw, info_only, check_result):
+def _execute_lcm_requests(workflow, onap_ip, check_result):
+    lcm_requests = workflow["requests"]
+    print("WORKFLOW << {} >>".format(workflow["description"]))
+    for i in range(len(lcm_requests)):
+       req = lcm_requests[i]["payload"]
+       #print(json.dumps(req, indent=4))
+       print("APPC LCM << {} >> [{}]".format(req['input']['action'], lcm_requests[i]["description"]))
+       _set_appc_lcm_timestamp(req)
+       result = appc_lcm_request(onap_ip, req)
+       if result == 100:
+           conf_result = confirm_appc_lcm_action(onap_ip, req, check_result)
+           if not conf_result:
+               if lcm_requests[i]["breakOnFailure"]:
+                   raise Exception("APPC LCM << {} >> FAILED".format(req['input']['action']))
+               elif "workflow" in lcm_requests[i]:
+                   print("WORKFLOW << {} >> SKIP".format(lcm_requests[i]["workflow"]["description"]))
+           elif "workflow" in lcm_requests[i]:
+               _execute_lcm_requests(lcm_requests[i]["workflow"], onap_ip, check_result)
+            
+           #time.sleep(30)
+
+
+
+def execute_workflow(vfw_vnf_id, rancher_ip, onap_ip, use_oof_cache, if_close_loop_vfw, info_only, check_result, new_version=None):
     print("\nExecuting workflow for VNF ID '{}' on Rancher with IP {} and ONAP with IP {}".format(
         vfw_vnf_id, rancher_ip, onap_ip))
     print("\nOOF Cache {}, is CL vFW {}, only info {}, check LCM result {}".format(use_oof_cache, if_close_loop_vfw,
                                                                                    info_only, check_result))
+    if new_version is not None:
+        print("\nNew vFW software version {}\n".format(new_version))
+
     x = threading.Thread(target=_run_osdf_resp_server, daemon=True)
     x.start()
     aai_data = load_aai_data(vfw_vnf_id, onap_ip)
     print("\nvFWDT Service Information:")
     print(json.dumps(aai_data, indent=4))
-    lcm_requests = build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw)
+    lcm_requests = build_appc_lcms_requests_body(rancher_ip, onap_ip, aai_data, use_oof_cache, if_close_loop_vfw, new_version)
     print("\nAnsible Inventory:")
     inventory = "[host]\nlocalhost   ansible_connection=local\n"
     for key in ansible_inventory:
@@ -809,16 +916,29 @@ def execute_workflow(vfw_vnf_id, rancher_ip, onap_ip, use_oof_cache, if_close_lo
     if info_only:
         return
     print("\nDistribute Traffic Workflow Execution:")
-    for i in range(len(lcm_requests)):
-        req = lcm_requests[i]
-        print("APPC REQ {} - {}".format(i, req['input']['action']))
-        _set_appc_lcm_timestamp(req)
-        result = appc_lcm_request(onap_ip, req)
-        if result == 100:
-            confirm_appc_lcm_action(onap_ip, req, check_result)
-            #time.sleep(30)
 
+    _execute_lcm_requests({"requests": lcm_requests, "description": "Migrate vFW Traffic Conditionally"}, onap_ip, check_result)
+
+
+help = """\npython3 workflow.py <VNF-ID> <RANCHER-NODE-IP> <K8S-NODE-IP> <IF-CACHE> <IF-VFWCL> <INITIAL-ONLY> <CHECK-STATUS> <VERSION>
+\n<VNF-ID> - vnf-id of vFW VNF instance that traffic should be migrated out from
+<RANCHER-NODE-IP> - External IP of ONAP Rancher Node i.e. 10.12.5.160 (If Rancher Node is missing this is NFS node)
+<K8S-NODE-IP> - External IP of ONAP K8s Worker Node i.e. 10.12.5.212
+<IF-CACHE> - If script should use and build OOF response cache (cache it speed-ups further executions of script)
+<IF-VFWCL> - If instead of vFWDT service instance vFW or vFWCL one is used (should be False always)
+<INITIAL-ONLY> - If only configuration information will be collected (True for initial phase and False for full execution of workflow)
+<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)
+<VERSION> - New version of vFW - for tests '1.0' or '2.0'. Ommit when traffic distribution only\n"""
+
+for key in sys.argv:
+    if key == "-h" or key == "--help":
+        print(help)
+        sys.exit()
+
+new_version = None
+if len(sys.argv) > 8:
+    new_version = sys.argv[8]
 
 #vnf_id, Rancher node IP, K8s node IP, use OOF cache, if close loop vfw, if info_only, if check APPC result
 execute_workflow(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4].lower() == 'true', sys.argv[5].lower() == 'true',
-                 sys.argv[6].lower() == 'true', sys.argv[7].lower() == 'true')
+                 sys.argv[6].lower() == 'true', sys.argv[7].lower() == 'true', new_version)