Forum Discussion

johnramzf5's avatar
johnramzf5
Icon for Altocumulus rankAltocumulus
Apr 25, 2023
Solved

Issue when deploying F5-Analytics app using f5.analytics.v3.7.1 template to send logs to SPLUNK

I followed the steps in these F5 links:

https://clouddocs.f5.com/training/community/analytics/html/class2/modules/task1.html

https://clouddocs.f5.com/training/community/analytics/html/class2/modules/task2.html

https://clouddocs.f5.com/training/community/analytics/html/class2/modules/module4.html

After STEP 17 (Under Application Mapping, leave all settings at their default values, except in the Mapping Table, enter the following:) I CLICK "Finished" I receive this error:

“script did not successfully complete: (01070734:3: Configuration error: Invalid mcpd context, folder not found (/Common/F5-Analytics.app)”

I noticed the template created several objects in configurations such as nodes, pool, virtual servers, etc. After about 10 hours and I still do not see any data in the Virtual Servers statistics and analytics, perhaps because of the error already mentioned.  

I did a search and found this F5 article that seems to address the issue:

https://my.f5.com/manage/s/article/K42390368

But the links provided in the article are not working. It says the recommended action is:

"Redeploy the ASO-Application Service Object- that failed before deploying additional ASOs. You can use the deploy_iapp_bigip.py Python script on a Linux host. The Python script and iApp templates are available for download at the following locations:"

If the answer is to redeploy it using script, questions:

- Where to find that script? and instructions in how to run it

Any other suggestion to solve this issue is appreciated.

John

 

 

 

 

 

 

  • Hi John,

    I don't know much about the splunk integration. I found the Python script and readme files on a different github link.

    https://github.com/plcharbonneau/f5-application-services-integration-iApp/tree/release/v2.0.002/scripts

    I've added them here as link can change/remove.

    deploy_iapp_bigip.py

    #!/usr/bin/python
    # Copyright (c) 2017 F5 Networks, Inc.
    #
    # 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.
    #
    # deploy_iapp_bigip.py -- Deploy an iApp to a BIG-IP system using the iControl-REST API
    # Documentation: see README.deploy_iapp_bigip
    import requests
    
    try:
    	requests.packages.urllib3.disable_warnings()
    except:
    	pass
    
    import json
    import argparse
    import os
    import sys
    import pprint
    import time
    pp = pprint.PrettyPrinter(indent=2)
    
    '''
    Recursively process a JSON object.
    Parent files are specified by the 'parent' key in the JSON object
    Values in the 'child' file take precedence
    '''
    def process_file(parent, child, indent):
    	print "[info] %sprocessing parent file \"%s\"" % (indent, parent)
    
    	try:
    		parent_file = open(parent)
    	except IOError as error:
    		print "[error] Open of parent JSON template \"%s\" failed: %s" % (parent, error)
    		sys.exit(1)
    
    	try:
    		parentdict = json.load(parent_file)
    	except (ValueError, NameError) as error:
    		print "[error] JSON format error in template \"%s\": %s" % (parent, error)
    		sys.exit(1)
    
    	parent_file.close()
    
    	# Recursion happens here
    	if 'parent' in parentdict:
    		parentdict = process_file(parentdict["parent"], parentdict, indent + " ")
    
    	# Process the child objects 'strings' and 'tables' keys.
    	child_strings = {}
    	child_tables = {}
    	debug("[%s] starting merge" % (parent))
    	if 'strings' in child:
    		for string in child["strings"]:
    			k, v = string.popitem()
    			debug("[%s] child: %s" % (parent, k))
    			child_strings[k] = v
    
    	if 'tables' in child:
    		i = 0
    		for table in child["tables"]:
    			debug("[%s] iapptable %s" % (parent, table["name"]))
    			child_tables[table["name"]] = i
    			i += 1
    
    	# Merge with the parent dictionary giving precedence to the child's values
    	if 'strings' in parentdict:
    		for string in parentdict["strings"]:
    			k, v = string.popitem()
    			if k in child_strings.keys():
    				string[k] = child_strings[k]
    			 	debug("[%s] OVERRIDE: %s: %s" % (parent, k, string[k]))
    			else:
    			 	string[k] = v
    
    	if 'tables' in parentdict:
    		i = 0
    		for table in parentdict["tables"]:
    			if table["name"] in child_tables.keys():
    				debug("[%s] OVERRIDE TABLE: %s" % (parent, table["name"]))
    				parentdict["tables"][i] = child["tables"][child_tables[table["name"]]]
    			i += 1
    
    	if 'lists' in parentdict:
    		i = 0
    		for alist in parentdict["lists"]:
    			if alist["name"] in child_tables.keys():
    				debug("[%s] OVERRIDE LIST: %s" % (parent, alist["name"]))
    				parentdict["lists"][i] = child["lists"][child_tables[alist["name"]]]
    			i += 1
    
    	# Inherit any other top level keys
    	for topitem in child.keys():
    		debug("topitem=%s" % topitem)
    		if not topitem in ["tables", "strings"]:
    			parentdict[topitem] = child[topitem]
    
    	return parentdict
    
    def debug(msg):
    	if args.debug:
    		print "DEBUG: %s" % (msg)
    
    def check_final_deploy(istat_key):
    	if args.nocheck:
    		return(1)
    
    	current_time = int(time.time())
    	bashurl   = "https://%s/mgmt/tm/util/bash" % (args.host)
    	istat_payload = { "command":"run",
    					 "utilCmdArgs":"-c 'tmsh run cli script appsvcs_get_istat \"%s\"'" % (istat_key)
    	               }
    
    	for i in range(args.checknum):
    		print "[info] checking for deployment completion (%s/%s)..." % ((i+1), args.checknum)
    		resp = s.post(bashurl, data=json.dumps(istat_payload))
    		if resp.status_code != requests.codes.ok:
    			print "ERROR: %s" % (resp.json())
    			sys.exit(1)
    
    		respdict = json.loads(resp.text)
    
    		result = respdict.get('commandResult')
    		result = result.replace('\n','')
    		debug("[check_deploy] current_time=%s result=%s" % (current_time, result))
    		if result.startswith("FINISHED_"):
    			parts = result.split('_')
    			fin_time = int(parts[1])
    			if fin_time > current_time:
    				return(1)
    		time.sleep(args.checkwait)
    
    	return(0)
    
    # Setup and process arguments
    parser = argparse.ArgumentParser(description='Script to deploy an iApp to a BIG-IP device')
    parser.add_argument("host",             help="The IP/Hostname of the BIG-IP device")
    parser.add_argument("json_template",    help="The JSON iApp definition file")
    parser.add_argument("-u", "--username", help="The BIG-IP username")
    parser.add_argument("-p", "--password", help="The BIG-IP password")
    parser.add_argument("-d", "--dontsave", help="Don't automatically save the config", action="store_true")
    parser.add_argument("-r", "--redeploy", help="Redeploy an existing iApp", action="store_true")
    parser.add_argument("-D", "--debug",    help="Enable debug output", action="store_true")
    parser.add_argument("-n", "--nocheck",  help="Don't check for deployment completion", action="store_true")
    parser.add_argument("-c", "--checknum", help="Number of times to check for deployment completion", default=10, type=int)
    parser.add_argument("-w", "--checkwait",help="Delay in seconds between each deployment completion check", default=6, type=int)
    
    args = parser.parse_args()
    
    print "[info] processing template \"%s\"" % (args.json_template)
    
    try:
    	iapp_file = open(args.json_template)
    except IOError as error:
    	print "[error] Open of JSON template \"%s\" failed: %s" % (args.json_template, error)
    	sys.exit(1)
    
    try:
    	iapp = json.load(iapp_file)
    except (ValueError, NameError) as error:
    	print "[error] JSON format error in template \"%s\": %s" % (args.json_template, error)
    	sys.exit(1)
    
    iapp_file.close()
    
    if 'parent' in iapp:
    	final = process_file(iapp["parent"], iapp, " ")
    else:
    	final = iapp
    
    if args.username:
    	if 'username' in final:
    		print "[info] Username found in JSON but specified on CLI, using CLI value"
    	final["username"] = args.username
    
    if args.password:
    	if 'password' in final:
    		print "[info] Password found in JSON but specified on CLI, using CLI value"
    	final["password"] = args.password
    
    # Required fields
    required = ['name','template_name','partition','username','password','inheritedDevicegroup','inheritedTrafficGroup','deviceGroup','trafficGroup']
    
    for item in required:
    	if not item in final:
    		print "[error] The required key \"%s\" was not found in the JSON template (or it's parent(s))" % (item)
    		sys.exit(1)
    
    debug("final=%s" % pp.pformat(final))
    
    # Set our REST urls
    iapp_url       = "https://%s/mgmt/tm/sys/application/service" % (args.host)
    save_url       = "https://%s/mgmt/tm/sys/config" % (args.host)
    template_url   = "https://%s/mgmt/tm/sys/application/template?$select=name" % (args.host)
    iapp_exist_url = "%s/~%s~%s.app~%s" % (iapp_url, final["partition"], final["name"], final["name"])
    bash_url       = "https://%s/mgmt/tm/util/bash" % (args.host)
    
    # Create request session, set credentials, allow self-signed SSL cert
    s = requests.session()
    s.auth = (final["username"], final["password"])
    s.verify = False
    
    time_payload = {
        "command":"run",
        "utilCmdArgs":"-c 'date +%s'"
    }
    
    resp = s.post(bash_url, data=json.dumps(time_payload))
    
    if resp.status_code == 401:
        print "[error] Authentication to %s failed" % (args.host)
        sys.exit(1)
    
    
    systimejson = json.loads(resp.text)
    systime = systimejson.get('commandResult')
    systime = systime.replace('\n','')
    
    debug("[check_time] %s" % systime)
    
    delta = time.time() - int(systime)
    debug("[check_time] delta=%s" % delta)
    
    if delta > 10:
        print "[error] Time delta between local system and BIG-IP is %s.  Limit is 10 seconds.  Please ensure time is synced" % delta
        sys.exit(1)
    	
    resp = s.get(template_url)
    templates = resp.json();
    
    tmpllist = []
    for item in templates["items"]:
    	if item["name"].startswith("appsvcs_integration_"):
    		debug("[template_list] found template named %s" % (item["name"]))
    		tmpllist.append(item["name"])
    
    debug("[template_select] specified=%s" % (final["template_name"]))
    if final["template_name"] == "latest":
    	tmpllist.sort()
    	final["template_name"] = tmpllist.pop()
    	debug("[template_select] selected=%s" % (final["template_name"]))
    else:
    	if not final["template_name"] in tmpllist:
    		print "[error] iApp template \"%s\" is not installed on BIG-IP host %s" % (final["template_name"], args.host)
    		sys.exit(1)
    
    deploy_payload = {
        "inheritedDevicegroup": final["inheritedDevicegroup"],
        "inheritedTrafficGroup": final["inheritedTrafficGroup"],
        "deviceGroup": final["deviceGroup"],
        "trafficGroup": final["trafficGroup"],
        "template": final["template_name"],
        "partition": final["partition"],
        "name": final["name"],
        "variables": [],
        "tables": [],
        "lists":[]
    }
    
    for string in final["strings"]:
    	k, v = string.popitem()
    	deploy_payload["variables"].append({"name":k, "value":v})
    
    deploy_payload["tables"] = final["tables"]
    deploy_payload["lists"] = final["lists"]
    
    # Check to see if the template with the name specified in the arguments exists on the BIG-IP device
    debug("exist_url=%s" % iapp_exist_url)
    resp = s.get(iapp_exist_url)
    
    # The template exists and the -o argument (overwrite) was not specified.  Print error and exit
    if resp.status_code == 200 and not args.redeploy:
    	print "[error] An iApp deployment named \"%s\" already exists on BIG-IP \"%s\".  To redeploy please specify the '-r' flag" % (final["name"], args.host)
    	sys.exit(1)
    
    istat_key = "sys.application.service /%s/%s.app/%s string deploy.postdeploy_final" % (deploy_payload.get('partition'), deploy_payload.get('name'), deploy_payload.get('name'))
    # iApp deployment does not already exist, create it
    if resp.status_code == 404:
     	# Send the REST call to create the template and print outcome
    	debug("deploy_payload=%s" % json.dumps(deploy_payload))
    	resp = s.post(iapp_url, data=json.dumps(deploy_payload))
    	debug("deploy resp=%s" % (pp.pformat(json.loads(resp.text))))
    	if resp.status_code != requests.codes.ok:
    		print "[error] iApp deployment failed: %s" % (resp.json())
    		sys.exit(1)
    	if check_final_deploy(istat_key):
    		print "[success] iApp \"%s\" deployed on BIG-IP \"%s\"" % (final["name"], args.host)
    	else:
    		print "[error] iApp deployment might have failed.  Please check /var/tmp/scriptd.out on the device"
    		sys.exit(1)
    
    # iApp deployment exists and args.redeploy (-r) is TRUE so we will redeploy
    else:
    	del deploy_payload["inheritedDevicegroup"]
    	del deploy_payload["inheritedTrafficGroup"]
    	del deploy_payload["deviceGroup"]
    	del deploy_payload["trafficGroup"]
    	deploy_payload["execute-action"] = "definition"
    
    	debug("redeploy_payload=%s" % json.dumps(deploy_payload))
    	resp = s.put(iapp_exist_url, data=json.dumps(deploy_payload))
    	debug("redeploy resp=%s" % (pp.pformat(json.loads(resp.text))))
    	if resp.status_code != requests.codes.ok:
    		print "[error] iApp re-deployment failed: %s" % (resp.json())
    		sys.exit(1)
    
    	if check_final_deploy(istat_key):
    		print "[success] iApp \"%s\" re-deployed on BIG-IP \"%s\"" % (final["name"], args.host)
    	else:
    		print "[error] iApp deployment might have failed.  Please check /var/tmp/scriptd.out on the device"
    		sys.exit(1)
    
    # Save the config (unless -d option was specified)
    save_payload = { "command":"save" }
    if not args.dontsave:
    	resp = s.post(save_url, data=json.dumps(save_payload))
    	if resp.status_code != requests.codes.ok:
    		print "[error] save failed: %s" % (resp.json())
    		sys.exit(1)
    	else:
    		print "[success] Config saved"
    
    sys.exit(0)

     README.deploy_iapp_bigip:

    deploy_iapp_bigip.py
    	Deploy an iApp to a BIG-IP system using the iControl-REST API
    
    This script uses the F5 BIG-IP iControl REST API to create a specific
    instance of an iApp deployment.
    
    The script supports:
     - Deployment/Redeployment of an iApp using JSON template files
     - Hierarchical definition of a deployment using multiple JSON files
       - A JSON template can specify a 'parent' file to inherit properties from
       - No limit to the number of levels of inheritence
     - Automatic selection of the latest version of the appsvcs_integration_iapp 
     - Specification of partition, traffic-group, device-group and other global items
    
    Sample template files are included in the 'deploy_iapp_samples' directory
    that implement a three-level hierarchy and deploy a HTTPS or HTTP virtual 
    server using the appsvcs_integration_iapp.  The following table describes 
    the contents of the sample files:
    
     sample_defaults.json: Default values for all the fields contained in the iApp
     sample_https.json:    Default values for a HTTPS service (parent: sample_defaults.json)
     sample_myhttps.json:  Top level definition of the service (parent: sample_https.json)
     sample_http.json:     Default values for a HTTP service (parent: sample_defaults.json)
     sample_myhttp.json:   Top level definition of the service (parent: sample_http.json)
    
    To deploy the sample_myhttps.json template a command like this can be used:
     
     cd deploy_iapp_samples
     python ../deploy_iapp_bigip.py -i <BIG-IP mgmt IP> -u <username> -p <password> sample_myhttps.json
    
    By default the script will automatically save the system config.  This 
    behaviour can be disabled by using the '-d' option.
    
    For further options please run the script with the --help argument

     

     

1 Reply

  • Hi John,

    I don't know much about the splunk integration. I found the Python script and readme files on a different github link.

    https://github.com/plcharbonneau/f5-application-services-integration-iApp/tree/release/v2.0.002/scripts

    I've added them here as link can change/remove.

    deploy_iapp_bigip.py

    #!/usr/bin/python
    # Copyright (c) 2017 F5 Networks, Inc.
    #
    # 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.
    #
    # deploy_iapp_bigip.py -- Deploy an iApp to a BIG-IP system using the iControl-REST API
    # Documentation: see README.deploy_iapp_bigip
    import requests
    
    try:
    	requests.packages.urllib3.disable_warnings()
    except:
    	pass
    
    import json
    import argparse
    import os
    import sys
    import pprint
    import time
    pp = pprint.PrettyPrinter(indent=2)
    
    '''
    Recursively process a JSON object.
    Parent files are specified by the 'parent' key in the JSON object
    Values in the 'child' file take precedence
    '''
    def process_file(parent, child, indent):
    	print "[info] %sprocessing parent file \"%s\"" % (indent, parent)
    
    	try:
    		parent_file = open(parent)
    	except IOError as error:
    		print "[error] Open of parent JSON template \"%s\" failed: %s" % (parent, error)
    		sys.exit(1)
    
    	try:
    		parentdict = json.load(parent_file)
    	except (ValueError, NameError) as error:
    		print "[error] JSON format error in template \"%s\": %s" % (parent, error)
    		sys.exit(1)
    
    	parent_file.close()
    
    	# Recursion happens here
    	if 'parent' in parentdict:
    		parentdict = process_file(parentdict["parent"], parentdict, indent + " ")
    
    	# Process the child objects 'strings' and 'tables' keys.
    	child_strings = {}
    	child_tables = {}
    	debug("[%s] starting merge" % (parent))
    	if 'strings' in child:
    		for string in child["strings"]:
    			k, v = string.popitem()
    			debug("[%s] child: %s" % (parent, k))
    			child_strings[k] = v
    
    	if 'tables' in child:
    		i = 0
    		for table in child["tables"]:
    			debug("[%s] iapptable %s" % (parent, table["name"]))
    			child_tables[table["name"]] = i
    			i += 1
    
    	# Merge with the parent dictionary giving precedence to the child's values
    	if 'strings' in parentdict:
    		for string in parentdict["strings"]:
    			k, v = string.popitem()
    			if k in child_strings.keys():
    				string[k] = child_strings[k]
    			 	debug("[%s] OVERRIDE: %s: %s" % (parent, k, string[k]))
    			else:
    			 	string[k] = v
    
    	if 'tables' in parentdict:
    		i = 0
    		for table in parentdict["tables"]:
    			if table["name"] in child_tables.keys():
    				debug("[%s] OVERRIDE TABLE: %s" % (parent, table["name"]))
    				parentdict["tables"][i] = child["tables"][child_tables[table["name"]]]
    			i += 1
    
    	if 'lists' in parentdict:
    		i = 0
    		for alist in parentdict["lists"]:
    			if alist["name"] in child_tables.keys():
    				debug("[%s] OVERRIDE LIST: %s" % (parent, alist["name"]))
    				parentdict["lists"][i] = child["lists"][child_tables[alist["name"]]]
    			i += 1
    
    	# Inherit any other top level keys
    	for topitem in child.keys():
    		debug("topitem=%s" % topitem)
    		if not topitem in ["tables", "strings"]:
    			parentdict[topitem] = child[topitem]
    
    	return parentdict
    
    def debug(msg):
    	if args.debug:
    		print "DEBUG: %s" % (msg)
    
    def check_final_deploy(istat_key):
    	if args.nocheck:
    		return(1)
    
    	current_time = int(time.time())
    	bashurl   = "https://%s/mgmt/tm/util/bash" % (args.host)
    	istat_payload = { "command":"run",
    					 "utilCmdArgs":"-c 'tmsh run cli script appsvcs_get_istat \"%s\"'" % (istat_key)
    	               }
    
    	for i in range(args.checknum):
    		print "[info] checking for deployment completion (%s/%s)..." % ((i+1), args.checknum)
    		resp = s.post(bashurl, data=json.dumps(istat_payload))
    		if resp.status_code != requests.codes.ok:
    			print "ERROR: %s" % (resp.json())
    			sys.exit(1)
    
    		respdict = json.loads(resp.text)
    
    		result = respdict.get('commandResult')
    		result = result.replace('\n','')
    		debug("[check_deploy] current_time=%s result=%s" % (current_time, result))
    		if result.startswith("FINISHED_"):
    			parts = result.split('_')
    			fin_time = int(parts[1])
    			if fin_time > current_time:
    				return(1)
    		time.sleep(args.checkwait)
    
    	return(0)
    
    # Setup and process arguments
    parser = argparse.ArgumentParser(description='Script to deploy an iApp to a BIG-IP device')
    parser.add_argument("host",             help="The IP/Hostname of the BIG-IP device")
    parser.add_argument("json_template",    help="The JSON iApp definition file")
    parser.add_argument("-u", "--username", help="The BIG-IP username")
    parser.add_argument("-p", "--password", help="The BIG-IP password")
    parser.add_argument("-d", "--dontsave", help="Don't automatically save the config", action="store_true")
    parser.add_argument("-r", "--redeploy", help="Redeploy an existing iApp", action="store_true")
    parser.add_argument("-D", "--debug",    help="Enable debug output", action="store_true")
    parser.add_argument("-n", "--nocheck",  help="Don't check for deployment completion", action="store_true")
    parser.add_argument("-c", "--checknum", help="Number of times to check for deployment completion", default=10, type=int)
    parser.add_argument("-w", "--checkwait",help="Delay in seconds between each deployment completion check", default=6, type=int)
    
    args = parser.parse_args()
    
    print "[info] processing template \"%s\"" % (args.json_template)
    
    try:
    	iapp_file = open(args.json_template)
    except IOError as error:
    	print "[error] Open of JSON template \"%s\" failed: %s" % (args.json_template, error)
    	sys.exit(1)
    
    try:
    	iapp = json.load(iapp_file)
    except (ValueError, NameError) as error:
    	print "[error] JSON format error in template \"%s\": %s" % (args.json_template, error)
    	sys.exit(1)
    
    iapp_file.close()
    
    if 'parent' in iapp:
    	final = process_file(iapp["parent"], iapp, " ")
    else:
    	final = iapp
    
    if args.username:
    	if 'username' in final:
    		print "[info] Username found in JSON but specified on CLI, using CLI value"
    	final["username"] = args.username
    
    if args.password:
    	if 'password' in final:
    		print "[info] Password found in JSON but specified on CLI, using CLI value"
    	final["password"] = args.password
    
    # Required fields
    required = ['name','template_name','partition','username','password','inheritedDevicegroup','inheritedTrafficGroup','deviceGroup','trafficGroup']
    
    for item in required:
    	if not item in final:
    		print "[error] The required key \"%s\" was not found in the JSON template (or it's parent(s))" % (item)
    		sys.exit(1)
    
    debug("final=%s" % pp.pformat(final))
    
    # Set our REST urls
    iapp_url       = "https://%s/mgmt/tm/sys/application/service" % (args.host)
    save_url       = "https://%s/mgmt/tm/sys/config" % (args.host)
    template_url   = "https://%s/mgmt/tm/sys/application/template?$select=name" % (args.host)
    iapp_exist_url = "%s/~%s~%s.app~%s" % (iapp_url, final["partition"], final["name"], final["name"])
    bash_url       = "https://%s/mgmt/tm/util/bash" % (args.host)
    
    # Create request session, set credentials, allow self-signed SSL cert
    s = requests.session()
    s.auth = (final["username"], final["password"])
    s.verify = False
    
    time_payload = {
        "command":"run",
        "utilCmdArgs":"-c 'date +%s'"
    }
    
    resp = s.post(bash_url, data=json.dumps(time_payload))
    
    if resp.status_code == 401:
        print "[error] Authentication to %s failed" % (args.host)
        sys.exit(1)
    
    
    systimejson = json.loads(resp.text)
    systime = systimejson.get('commandResult')
    systime = systime.replace('\n','')
    
    debug("[check_time] %s" % systime)
    
    delta = time.time() - int(systime)
    debug("[check_time] delta=%s" % delta)
    
    if delta > 10:
        print "[error] Time delta between local system and BIG-IP is %s.  Limit is 10 seconds.  Please ensure time is synced" % delta
        sys.exit(1)
    	
    resp = s.get(template_url)
    templates = resp.json();
    
    tmpllist = []
    for item in templates["items"]:
    	if item["name"].startswith("appsvcs_integration_"):
    		debug("[template_list] found template named %s" % (item["name"]))
    		tmpllist.append(item["name"])
    
    debug("[template_select] specified=%s" % (final["template_name"]))
    if final["template_name"] == "latest":
    	tmpllist.sort()
    	final["template_name"] = tmpllist.pop()
    	debug("[template_select] selected=%s" % (final["template_name"]))
    else:
    	if not final["template_name"] in tmpllist:
    		print "[error] iApp template \"%s\" is not installed on BIG-IP host %s" % (final["template_name"], args.host)
    		sys.exit(1)
    
    deploy_payload = {
        "inheritedDevicegroup": final["inheritedDevicegroup"],
        "inheritedTrafficGroup": final["inheritedTrafficGroup"],
        "deviceGroup": final["deviceGroup"],
        "trafficGroup": final["trafficGroup"],
        "template": final["template_name"],
        "partition": final["partition"],
        "name": final["name"],
        "variables": [],
        "tables": [],
        "lists":[]
    }
    
    for string in final["strings"]:
    	k, v = string.popitem()
    	deploy_payload["variables"].append({"name":k, "value":v})
    
    deploy_payload["tables"] = final["tables"]
    deploy_payload["lists"] = final["lists"]
    
    # Check to see if the template with the name specified in the arguments exists on the BIG-IP device
    debug("exist_url=%s" % iapp_exist_url)
    resp = s.get(iapp_exist_url)
    
    # The template exists and the -o argument (overwrite) was not specified.  Print error and exit
    if resp.status_code == 200 and not args.redeploy:
    	print "[error] An iApp deployment named \"%s\" already exists on BIG-IP \"%s\".  To redeploy please specify the '-r' flag" % (final["name"], args.host)
    	sys.exit(1)
    
    istat_key = "sys.application.service /%s/%s.app/%s string deploy.postdeploy_final" % (deploy_payload.get('partition'), deploy_payload.get('name'), deploy_payload.get('name'))
    # iApp deployment does not already exist, create it
    if resp.status_code == 404:
     	# Send the REST call to create the template and print outcome
    	debug("deploy_payload=%s" % json.dumps(deploy_payload))
    	resp = s.post(iapp_url, data=json.dumps(deploy_payload))
    	debug("deploy resp=%s" % (pp.pformat(json.loads(resp.text))))
    	if resp.status_code != requests.codes.ok:
    		print "[error] iApp deployment failed: %s" % (resp.json())
    		sys.exit(1)
    	if check_final_deploy(istat_key):
    		print "[success] iApp \"%s\" deployed on BIG-IP \"%s\"" % (final["name"], args.host)
    	else:
    		print "[error] iApp deployment might have failed.  Please check /var/tmp/scriptd.out on the device"
    		sys.exit(1)
    
    # iApp deployment exists and args.redeploy (-r) is TRUE so we will redeploy
    else:
    	del deploy_payload["inheritedDevicegroup"]
    	del deploy_payload["inheritedTrafficGroup"]
    	del deploy_payload["deviceGroup"]
    	del deploy_payload["trafficGroup"]
    	deploy_payload["execute-action"] = "definition"
    
    	debug("redeploy_payload=%s" % json.dumps(deploy_payload))
    	resp = s.put(iapp_exist_url, data=json.dumps(deploy_payload))
    	debug("redeploy resp=%s" % (pp.pformat(json.loads(resp.text))))
    	if resp.status_code != requests.codes.ok:
    		print "[error] iApp re-deployment failed: %s" % (resp.json())
    		sys.exit(1)
    
    	if check_final_deploy(istat_key):
    		print "[success] iApp \"%s\" re-deployed on BIG-IP \"%s\"" % (final["name"], args.host)
    	else:
    		print "[error] iApp deployment might have failed.  Please check /var/tmp/scriptd.out on the device"
    		sys.exit(1)
    
    # Save the config (unless -d option was specified)
    save_payload = { "command":"save" }
    if not args.dontsave:
    	resp = s.post(save_url, data=json.dumps(save_payload))
    	if resp.status_code != requests.codes.ok:
    		print "[error] save failed: %s" % (resp.json())
    		sys.exit(1)
    	else:
    		print "[success] Config saved"
    
    sys.exit(0)

     README.deploy_iapp_bigip:

    deploy_iapp_bigip.py
    	Deploy an iApp to a BIG-IP system using the iControl-REST API
    
    This script uses the F5 BIG-IP iControl REST API to create a specific
    instance of an iApp deployment.
    
    The script supports:
     - Deployment/Redeployment of an iApp using JSON template files
     - Hierarchical definition of a deployment using multiple JSON files
       - A JSON template can specify a 'parent' file to inherit properties from
       - No limit to the number of levels of inheritence
     - Automatic selection of the latest version of the appsvcs_integration_iapp 
     - Specification of partition, traffic-group, device-group and other global items
    
    Sample template files are included in the 'deploy_iapp_samples' directory
    that implement a three-level hierarchy and deploy a HTTPS or HTTP virtual 
    server using the appsvcs_integration_iapp.  The following table describes 
    the contents of the sample files:
    
     sample_defaults.json: Default values for all the fields contained in the iApp
     sample_https.json:    Default values for a HTTPS service (parent: sample_defaults.json)
     sample_myhttps.json:  Top level definition of the service (parent: sample_https.json)
     sample_http.json:     Default values for a HTTP service (parent: sample_defaults.json)
     sample_myhttp.json:   Top level definition of the service (parent: sample_http.json)
    
    To deploy the sample_myhttps.json template a command like this can be used:
     
     cd deploy_iapp_samples
     python ../deploy_iapp_bigip.py -i <BIG-IP mgmt IP> -u <username> -p <password> sample_myhttps.json
    
    By default the script will automatically save the system config.  This 
    behaviour can be disabled by using the '-d' option.
    
    For further options please run the script with the --help argument