Creating a tmsh script with iControl REST and using it to restart HTTPD

Problem this snippet solves:

TMSH has the ability to create tcl scripts that can be used to run multiple commands and transactions. It is rare that you will want to create on with TMSH but there are a few cases where this may be desirable. One of these is to restart HTTPD, which is difficult to do from iControl REST because the REST API is running over HTTPD and the restart will not be clean. See K13292945.


This Python script creates a tmsh script, and then runs it to restart HTTPD.

How to use this snippet:

Syntax is <program_name.py> host user password


Sample output:

./rest_script_example.py 10.155.117.12 admin admin
Before
httpd (pid 3186) is running...

After
httpd (pid 3289) is running...


Code :

#!/usr/bin/python

#m.lloyd@f5.com
#Makes tmsh script to restart HTTP 

#Syntax:  host username password

import json

#allow python 2 and python 3 by loading  the correct libraries.
try:
    from http.client import BadStatusLine
    from urllib.parse import urlparse, urlencode
    from urllib.request import urlopen, Request
    from urllib.error import HTTPError
except ImportError:
    from httplib import BadStatusLine
    from urlparse import urlparse
    from urllib import urlencode
    from urllib2 import urlopen, Request, HTTPError

import ssl
import sys
import time

#Internal calls will not verify certs so disable cert verification.
ssl._create_default_https_context = ssl._create_unverified_context

#Create request for token based authentication. This is in Bigip 12 and later:
url = 'https://'+sys.argv[1]+'/mgmt/shared/authn/login'
values = {'username' : sys.argv[2],
          'password' : sys.argv[3],
          'loginProviderName' : 'tmos'}
values = json.dumps(values).encode('utf-8')
Request(url,data=values)
req = Request(url,data=values)
req.add_header('Content-Type' , 'application/json')

#Request authentication token.
response = urlopen(req)

#auth=result will be a json data structure.
auth_result = response.read()
#print (auth_result)

#Json.loads makes an internal python data structure that is easier to extract auth token from json.
#Now construct icontrol rest query for device-groups info.
auth=json.loads(auth_result)
token=(auth['token']['token'])
#print(token)

#Get current PID of HTTPD
url = 'https://'+sys.argv[1]+'/mgmt/tm/sys/service/httpd/stats'
req = Request(url)
req.add_header('X-F5-Auth-Token',auth['token']['token'])
response = urlopen(req,data=None)

json_response=(response.read())
python_response=json.loads(json_response)
print("Before")
print(python_response["apiRawValues"]["apiAnonymous"])


#look for script with name to make sure that the script does not already exist

url = 'https://'+sys.argv[1]+'/mgmt/tm/cli/script/example.tcl'

#urllib2 raises an  exception with an HTTP 404

req = Request(url)
req.add_header('X-F5-Auth-Token',auth['token']['token'])

try:
    response = urlopen(req,data=None)
except HTTPError as err:
    if err.code==404:
       
        #print (err.code)
        #print("\nCreate cli script\n")
        #request create here
        url = 'https://'+sys.argv[1]+'/mgmt/tm/cli/script'
        req = Request(url)
        req.add_header('X-F5-Auth-Token',auth['token']['token'])
        req.add_header('Content-Type' , 'application/json')

        values = {"name":"example.tcl",
                  "apiAnonymous": "proc script::init {} {\n}\n\nproc script::run {} {\n tmsh::run util bash -c 'killall -9 httpd' \n tmsh::start sys service httpd\n} \n\nproc script::help {} {\n}\n\nproc script::tabc {} {\n}\n"}
                                 
        values = json.dumps(values)

        response = urlopen(req,data=values)
        response_py=(json.load(response))
        #print(json.dumps(response_py,sort_keys=True,indent=4))

#Now run script
url = 'https://'+sys.argv[1]+'/mgmt/tm/cli/script/example.tcl'
req = Request(url)
req.add_header('X-F5-Auth-Token',auth['token']['token'])
req.add_header('Content-Type' , 'application/json')
values = {"kind":"tm:cli:script:runstate","command":"run"}
values = json.dumps(values).encode('utf-8')

# Killing HTTPD will abort the connection so catch the exception.


try:
    urlopen(req,data=values)
except BadStatusLine:
     pass 
     
#Wait for httpd to restart so you can query.
time.sleep(5)
#Get current PID of HTTPD after restart
url = 'https://'+sys.argv[1]+'/mgmt/tm/sys/service/httpd/stats'
req = Request(url)
req.add_header('X-F5-Auth-Token',auth['token']['token'])
response = urlopen(req,data=None)

json_response=(response.read())
python_response=json.loads(json_response)
print("After")
print(python_response["apiRawValues"]["apiAnonymous"])

Tested this on version:

13.0
Published Dec 29, 2020
Version 1.0
  • The only thing with such scripts is how to see the real output when using API:

     

    curl -sku admin:niki@111 https://10.1.1.130/mgmt/tm/cli/script/niki -H "Content-Type: application/json"

    Output API:


    {"kind":"tm:cli:script:scriptstate","name":"niki","fullPath":"niki","generation":7832,"selfLink":"https://localhost/mgmt/tm/cli/script/niki?ver=16.1.3.2","apiAnonymous":"proc script::init {} {\n}\n\nproc script::run {} {\nreturn [tmsh::list ltm pool]\n}\n\nproc script::help {} {\n}\n\nproc script::tabc {} {\n}\n","ignoreVerification":"false","totalSigningStatus":"not-all-signed","verificationStatus":"none"}

    Output CLI:

    root@(bigip2)(cfg-sync Standalone)(Active)(/Common)(tmos)# run cli script niki
    ltm pool Fake-ICAP-pool {
    members {
    1.1.1.1:icap {
    address 1.1.1.1
    }
    }

    Script:

     

    root@(bigip2)(cfg-sync Standalone)(Active)(/Common)(tmos)# edit cli script niki
    modify script niki {
    proc script::init {} {
    }

    proc script::run {} {
    puts [tmsh::list ltm pool]
    }

    proc script::help {} {
    }

    proc script::tabc {} {
    }