Code to create unreachable ELA license files from BIG-IQ

Problem this snippet solves:

*NOTE* if you are upgrading your BIG-IP,please refer to F5 solution: https://support.f5.com/csp/article/K13540950

BIG-IQ traditionally expects to be able to reach any BIG-IP devices it is going to license.

This code helps create a license file from the ELA SKU offerings which can be applied on an Unreachable BIG-IP.

I've added some troubleshoting steps at the end of the article, Dossier errors seen on the BIG-IP, just in case!

How to use this snippet:

SSH into the BIG-IP device and run the following command to gain the MAC address of the management interface

tmsh show sys mac-address | grep -i interface

 

[root@bigip1:Active:Standalone] config # tmsh show sys mac-address | grep -i interface
ll:50:56:xx:xx:36 net interface         mgmt                              mac-address
xxxxxxxxxxxxxxxxx net interface         1.3                               mac-address
xxxxxxxxxxxxxxxxx net interface         1.1                               mac-address
xxxxxxxxxxxxxxxxx net interface         1.2                               mac-address

 

In the example above the MAC address we need is “ll:50:56:xx:xx:36”

Now SSH into the BIG-IQ

Move into the /shared directory (cd /shared)

Copy over the Create-license.PY python script and run it by typing

python Create-license.py

The script runs and will prompt you for the following information

 

[root@Preece-bigiq-cm1:Active:Standalone] shared # python Create-license.py
Enter BIG-IQ user ID: admin
Enter BIG-IQ Password:
Enter Management IP address of BIG-IQ: 44.131.176.101
Enter Management IP address of BIG-IP to be licensed: 44.131.176.22
Enter Management MAC address of BIG-IP to be licensed: ll:50:56:xx:kk:36
Enter the name of the License Pool from which to take BIG-IP license: Load-18
Enter the license name to be assigned to the BIG-IP: F5-BIG-MSP-BT-1GIPIF-LIC-DEV                                                                                                                                                                 
Enter hypervisor used, valid options are: aws, azure, gce, hyperv, kvm, vmware,                                                                                                                                                                             xen: vmware
Optional: Enter chargeback tag if required: Department-A
Optional: Enter tenant name if required: Customer-B

 

 

Once the details have been filled in the script authenticates to the BIG-IQ and generates the license (30 seconds)

If everything went well, you will be presented with a success message.

The license file is saved as IP-address_bigip.license in the same directory as you run the script

Using SCP copy the new license file from the BIG-IQ to your desktop.

Copy the license file into the /config directory of the BIG-IP device.

Rename the file, copy ip-address.bigip.license bigip.license

Reload the license by typing reloadlic

Observe the BIG-IP device restart its services and show as active.

You can review in the GUI (System—License) and provision modules as needed.

Code :

 

 

 

import getpass # used to hide the users password input
import json
import os
import requests
from time import sleep
"""
This script uses the BIG-IQ API to license an unreachable (dark site) BIG-IP.
The BIG-IQ licensing API needs certain details provided in order to license an appliance, these details can either be provided in a file call lic-data.json or
if that file does not exist you will be prompted to enter them.
The minimum contents of lic-data.json should be:
{
    "licensePoolName": " -- Enter License Pool Name here. License Pool name can be found in BIG-IQ GUI -- ",
    "command": "assign",
    "address": " -- Enter MGMT IP Address of BIG-IP here -- ",
    "assignmentType": "UNREACHABLE",
    "macAddress": " -- Enter MAC address of MGMT IP for the BIG-IP here -- ",
    "hypervisor": " -- Enter hypervisor value here options are; aws, azure, gce, hyperv, kvm, vmware, xen: --",
    "unitOfMeasure": "yearly",
    "skuKeyword1": "-- Enter License Name here. License Name (or Offering name) can be found in the BIG-IQ GUI  -- "
}  

Additional Optional key:value pairs can be added to the JSON file to afix useful tags to the license. The json file with optional key:value pairs looks like:
{
    "licensePoolName": " -- Enter License Pool Name here. License Pool name can be found in BIG-IQ GUI -- ",
    "command": "assign",
    "address": " -- Enter MGMT IP Address of BIG-IP here -- ",
    "assignmentType": "UNREACHABLE",
    "macAddress": " -- Enter MAC address of MGMT IP for the BIG-IP here -- ",
    "hypervisor": " -- Enter hypervisor value here options are; aws, azure, gce, hyperv, kvm, vmware, xen: --",
    "unitOfMeasure": "yearly",
    "skuKeyword1": "-- Enter License Name here. License Name (or Offering name) can be found in the BIG-IQ GUI  -- ",
    "chargebackTag": "OPTIONAL: Remove this line if you are not going to use it",
    "tenant": "OPTIONAL: Remove this line if you are not going to use it"
} 

A completed minimal lic-data.json file will look like this:
{
    "licensePoolName": "byol-pool-utility",
    "command": "assign",
    "address": "10.1.1.10",
    "assignmentType": "UNREACHABLE",
    "macAddress": "06:ce:c2:43:b3:05",
    "hypervisor": "kvm",
    "unitOfMeasure": "yearly",
    "skuKeyword1": "F5-BIG-MSP-BT-P3-3GF-LIC-DEV"
}

lic-data.json must reside in the directory from which you execute this python script.

"""

def bigiqAuth(_bigiqAuthUrl, _bigiqCredentials):
    """
    This function authenticates with BIG-IQ and collects the authentication token provided.
    Theo token will be used for subsequent calls to BIG-IQ
    """
    _errFlag=0
    try:
        _bigiqAuthInfo=_bigiq_session.post(_bigiqAuthUrl, data=json.dumps(_bigiqCredentials), verify=False)
        print(_bigiqAuthUrl)
        _bigiqAuthInfo.raise_for_status()
        print("Response code: %s" %_bigiqAuthInfo.status_code)
    except requests.exceptions.HTTPError as err:
        print(err)
        _errFlag=1
    #end try
    if _errFlag==0:
        _bigiqResponse=_bigiqAuthInfo.json()
        _bigiqToken=_bigiqResponse['token']
        for _token in _bigiqToken:
            if (_token == 'token'):
                _bigiqAuthToken=(_bigiqToken[_token])
            # End if
        # Next
        _authHeaders={
            "X-F5-Auth-Token": "{_authToken}".format(_authToken=_bigiqAuthToken)
            }
    else:
        _authHeaders=0
    #end if
    print("** Completed Authentication ***")
    return(_authHeaders);
#End Def

def extractLicense(_rawLicenseJSON):
    """
    This function pulls the generated license from BIG-IQ
    """
    for _license in _rawLicenseJSON:
        if (_license=='licenseText'):
            _extractedLicense=_rawLicenseJSON[_license]
        #end if
        if (_license=='status'):
            if (_rawLicenseJSON[_license]=="FINISHED"):
                print("***** License has been assigned *****")
            else:
                _extractedLicense="FAILED"
            #end if
        #end if
     #next
    return(_extractedLicense);
#End def

def licenseData():
    """
    This function read the lic-data.json file. If it does not exist you will be prompted to enter the necessary values.
    """
    if os.path.exists('lic-data.json'):
        with open('./lic-data.json') as licfile:
            _licdata = json.load(licfile)
    else:
        _bigipAddress=raw_input("Enter Management IP address of BIG-IP to be licensed: ")
        _bigipMACaddress=raw_input("Enter Management MAC address of BIG-IP to be licensed: ")
        _licensePoolName=raw_input("Enter the name of the License Pool from which to take BIG-IP license: ")
        _licenseSKU=raw_input("Enter the license name to be assigned to the BIG-IP: ")
        _hypervisorType=raw_input("Enter hypervisor used, valid options are: aws, azure, gce, hyperv, kvm, vmware, xen: ")
        _chargebackTag=raw_input("Optional: Enter chargeback tag if required: ")
        _tenantTag=raw_input("Optional: Enter tenant name if required: ")
        _licdata={
            "licensePoolName": "{_licensePool}".format(_licensePool=_licensePoolName),
            "command": "assign",
            "address": "{_bigipIP}".format(_bigipIP=_bigipAddress),
            "assignmentType": "UNREACHABLE",
            "macAddress": "{_bigipMAC}".format(_bigipMAC=_bigipMACaddress),
            "hypervisor": "{_hypervisor}".format(_hypervisor=_hypervisorType),
            "unitOfMeasure": "yearly",
            "skuKeyword1": "{_license}".format(_license=_licenseSKU),
            "chargebackTag": "{_chargeback}".format(_chargeback=_chargebackTag),
            "tenant": "{_tenant}".format(_tenant=_tenantTag)
        }
    # End if
    return(_licdata);

def urlConstruction(_bigiqUrl, _bigiqIP):
    """
    This function rewrites the selflink URL returned by BIG-IQ to reflect BIG-IQ management IP address rather than localhost
    """
    count=0
    _urlDeConstruct=_bigiqUrl.split("/")
    _urlReConstruct=""
    for _urlElement in _urlDeConstruct:
        #print("%d %s" %(count,_urlElement))
        if (_urlElement=="https:"):
                _urlReConstruct=_urlReConstruct+_urlElement+"//"
        elif (_urlElement=="localhost"):
                _urlReConstruct=_urlReConstruct+_bigiqIP
        else:
               if (_urlElement!=""):
                    _urlReConstruct=_urlReConstruct+"/"+_urlElement
               #end if
        #end if
        count+=1
    #Next
    return(_urlReConstruct);
#End Def

_userID=raw_input("Enter BIG-IQ user ID: ")
_password=getpass.getpass(prompt="Enter BIG-IQ Password: ")
_bigiqAddress=raw_input("Enter Management IP address of BIG-IQ: ")

_credPostBody={
        "username": "{_uname}".format(_uname=_userID),
        "password": "{_pword}".format(_pword=_password),
        "loginProvideriName": "RadiusServer"
}

_deviceToBeLicensed=licenseData()
_bigipAddress=_deviceToBeLicensed['address']
print("BIG-IP Address is:  %s" %_bigipAddress)
_bigiq_session=requests.session()

_bigiq_auth_url="https://{_bigiqIP}/mgmt/shared/authn/login".format(_bigiqIP=_bigiqAddress)
# Authenticates with BIG-IQ
_bigiqAuthHeader=bigiqAuth(_bigiq_auth_url, _credPostBody)
#
if _bigiqAuthHeader==0:
    print("Unable to authenticate with BIG-IQ. Check BIG-IQ reachability and credentials")
else:
    _bigiq_url1="https://{_bigiqIP}/mgmt/cm/device/tasks/licensing/pool/member-management".format(_bigiqIP=_bigiqAddress)
    #
    # --- This section requests the license from BIG-IQ. Posting the criteria as laid out in the _deviceToBeLicensed JSON blob
    #
    _errFlag=0
    try:
        _bigiqLicenseDevice=_bigiq_session.post(_bigiq_url1, headers=_bigiqAuthHeader, data=json.dumps(_deviceToBeLicensed), verify=False)
        _bigiqLicenseDevice.raise_for_status()
        print("Response code: %s" %_bigiqLicenseDevice.status_code)
    except requests.exceptions.HTTPError as err:
        print("Issue received, check rquest and or check connectivity %s" %err)
        _errFlag=1
    #end try
    if _errFlag==0:
        #print(_bigiqLicenseDevice.status_code)
        _bigiqResponse=_bigiqLicenseDevice.json()
        print(_bigiqResponse)
        print(_bigiqResponse['selfLink'])
        _bigiqLicenseStatus_url=_bigiqResponse['selfLink']
        _bigiqLicenseStatus_url=urlConstruction(_bigiqLicenseStatus_url, _bigiqAddress)
        print(_bigiqLicenseStatus_url)
        print("--- Standby for 30 seconds whilst BIG-IQ generates license ---")
        sleep(30)
        _errFlag1=0
        try:
            _licenseStatus=_bigiq_session.get(_bigiqLicenseStatus_url, headers=_bigiqAuthHeader, verify=False)
            _licenseStatus.raise_for_status()
            print("Response code: %s" %_licenseStatus.status_code)
        except requests.exceptions.HTTPError as err:
            print("Issue received, check rquest and or check connectivity %s" %err)
            _errFlag=1
        #end try
        if _errFlag==0:
            print(_licenseStatus.content)
            _licenseStatusDetail=_licenseStatus.json()
            _licenseOutput=extractLicense(_licenseStatusDetail)
            if (_licenseOutput=="FAILED"):
                print("***** License Assignment Failed. Most likely a valid license already exists for device, revoke it before applying a new license *****")
            else:
                _licenseFname=(_bigipAddress+"_bigip.license")
                _licensefile=open(_licenseFname, "w")
                _licensefile.write("%s" %_licenseOutput)
                _licensefile.close()
                print(_licenseOutput)
                print("***** SUCCESS, the license is stored here %s *****" %_licenseFname)
            #end if
        #end if
    #end if
#end if

 

 

 

Tested this on version:

13.x, 14.x, 15.x and 16.x

Troubleshooting 

When you apply the license to the BIG-IP you may see an error similar to:

License is not operational (expired or digital signature does not match contents)

This could simply be that you copy and paste the license file badly, please use MD5SUM on the BIG-IQ to the output license file and compare to the same file on the BIG-IP

Example: md5sum 10.2.3.4_bigip.license

You can also review the /var/log/ltm file for "Dossier error" messages

Dossier error: 1 (MAC address is mismatched)

Dossier error: 12 (Hypervisor is mismatched)

If this does not help, please open a support case and attach a recent qkview file.

 

 

Updated Jun 02, 2023
Version 4.0

Was this article helpful?

4 Comments

  •  

    Hey Carine,

    Please refer to the following F5 solution article:  https://support.f5.com/csp/article/K13540950

    This steps through the process to re-activate the load / cat licenses on the BIG-IQ (update service check date) and then the steps to push that to each of the BIG-IP devices that you would wish to upgrade.

    I'll update the article above and the initial F5 solution https://support.f5.com/csp/article/K69187707

    Thanks for the feedback and hope the above helps you and others save time (next time)

    James....

     

     

  • James_Preece would you be able to verify and update this procedure for license reactivation in the same context? BIG-IP unreachable from BIg-IQ and licensed from an ELA pool? 

    We start to have customers reaching this step in order to be able to upgrade their big-ip to a new major version (license check  needs to be performed before the upgrade can take place). thanks 🙂

     

  • Hi Carine,

    In the context of "off line" licensing, you would need to revoke the existing BIG-IP license from within the BIG-IQ LM.

    Then rerun the above script and issue a new license file to the BIG-IP, this would updated "service check date" 

    Given BIG-IQ and BIG-IP in this case are not talking to each other,  K13540950, will need the above additional steps to work, I'll get it updtated to reflect this scenario. 

    Thanks

    James...