Automating Avocent with REST API

Today a short script that shows how to get a authorization token from devices with a REST API and use it to change a config. The example device is an Avocent 8032.

import requests
from requests.structures import CaseInsensitiveDict

urls = ["https://1.1.1.1"]
for url in urls:
   headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
   r = requests.post(url + ':48048/api/v1/sessions/login', verify=False, json={"username": "<yourusername","password": "<yourpassword>"}, headers=headers)
   json_response = r.json()
   mytoken = json_response["token"]

   headers = CaseInsensitiveDict()
   tokenstring = str(mytoken)



#GETTING SYSTEM INFO
   result = requests.get(url + ':48048/api/v1/system/info', verify=False, headers={'Content-Type':'application/json', 'Authorization': 'Bearer {}'.format(tokenstring)})
   systeminfo_response = result.json()
   print(systeminfo_response)



#CHANGING_IDLE_TIMEOUT
   result = requests.put(url + ':48048/api/v1/security', verify=False, json={'idleTimeout': 3600}, headers={'Content-Type':'application/json', 'Authorization': 'Bearer {}'.format(tokenstring)})
   print(result)

…and Bob’s your uncle!

Prerequisites:
API needs to be enabled: SSH into your device and change the parameter
enable_api_https_access = yes

The path to the parameter is under path /system/security/security_profile
The code itself is pretty self-explanatory. Any json data you want to send (=modify values) must be inside the json body. verify=False is used to bypass self-signed certificate warning. Once you have the token, you need to include it in later requests as an auth header. Once the PUT request is sent, the expected response is 204 or 200.
API of Avocent is described at https://www.vertiv.com/4a7004/globalassets/shared/avocent-acs8008000-application-programming-interface_0.pdf

Automate any HTML GUI with Selenium and pyautogui

I’ve recently had to upgrade a large number of Avocent console servers using GUI and it was an awfully boring experience. Because i don’t like such repetitive work, I decided that it’s high time I learnt how to automate mindless clicking.

Prerequisites:

Install Python, pip, the selenium library, and the pyautogui library. Detailed steps depend on your OS.

Code:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import pyautogui

#this ensures that you don't need to deal with the https self-signed-certificate issue
options = webdriver.ChromeOptions()
options.add_argument('ignore-certificate-errors')
browser = webdriver.Chrome(chrome_options=options)

#here I go to my console server, find the username field (to find the ID, press F12 in your browser to open devtools and find the right GUI element in the html code. Mind you, it might take you a while to find the right element. ), and type in 'myadminusername'
browser.get('https://192.168.1.1')
username = browser.find_element_by_id("username")
username.send_keys("myadminusername")

#I find the password field (and then waste an hour because the password field is non-interactable so I can't send keys in any way)
password = browser.find_element_by_id("password").click()

#so i deal with this problem in a different way. The field is active because i clicked on it so i can just type away...
webdriver.ActionChains(browser).key_down(Keys.SHIFT).send_keys("m").perform()
webdriver.ActionChains(browser).key_up(Keys.SHIFT).send_keys("y").perform()
webdriver.ActionChains(browser).key_down(Keys.SHIFT).send_keys("p").perform()
webdriver.ActionChains(browser).key_up(Keys.SHIFT).send_keys("a").perform()
webdriver.ActionChains(browser).send_keys("s").perform()
webdriver.ActionChains(browser).send_keys("s").perform()
webdriver.ActionChains(browser).send_keys(Keys.RETURN).perform()

#now i'm in the actual GUI, I click on a menu element on the lefthandside. Here I use the find_by_css_selector method instead of find_element_by_id
browser.find_element_by_css_selector("a[onclick*=overview]").click();

#I execute some js code instead of clicking on an option
browser.execute_script("XML_request(\"units.overview\",\"upgradeFirmware\");")

#I find a radio button to select how i want to select the file used for the upgrade. Finding by xpath is yet another way of finding an element. 
browser.find_element_by_xpath("//input[@value='from-mycomputer']").click()

#I select the option "Choose from file" which will open the file explorer to locate the file
browser.find_element_by_id("fileXferForm").click()

#now i can't use selenium anymore because Selenium only works in the browser and now i have File Explorer window open and i need to find the upgrade file on my PC. 
#I need to use pyautogui to move the mouse and click

pyautogui.position()
pyautogui.FAILSAFE = False
#i move the mouse cursor to where I want it to be to click on firmware download (later)
pyautogui.moveTo(900, 300, duration =4) 

#i type in the path where my upgrade file is and hit ENTER
pyautogui.typewrite('F:\\my_upgrade_file\n', interval=0)

#now i click on the DOWNLOAD button (because the cursor is in the right place)
pyautogui.click(clicks=2, interval=2, button='left')

#now the file download will take a moment, after which I need to click again on the Install button. This is #TODO because i haven't worked out yet how to deal with the changing download time before I can click. Of course I can click 100 times with an interval of 3 seconds but... 

Azure part 2 – adding subnets to existing vnets

It’s now time to add subnets to the first vnet.

Let’s do the first two with Powershell:

PS /home/tode> $virtualNetwork = Get-AzVirtualNetwork -Name HUBVNET

PS /home/tode> Add-AzVirtualNetworkSubnetConfig -Name gatewaySubnet -VirtualNetwork $virtualNetwork -AddressPrefix "10.0.0.0/27"

PS /home/tode> Add-AzVirtualNetworkSubnetConfig -Name FirewallSubnet -VirtualNetwork $virtualNetwork -AddressPrefix "10.0.0.32/27"
PS /home/tode> $virtualNetwork | Set-AzVirtualNetwork 

Name                   : HUBVNET
ResourceGroupName      : MainRG
Location               : eastus
Id                     : /subscriptions/62dd0295-0094-443a-9b60-4c75dac248eb/resourceGroups/MainRG/providers/Microsoft.Network/virtualNetworks/HUBVNET
Etag                   : W/"e81a3b5f-a4e9-4986-8d2f-d7b79e3237df"
ResourceGuid           : 9ccba534-fa70-4e97-9901-ac19afe36051
ProvisioningState      : Succeeded
Tags                   : 
AddressSpace           : {
                           "AddressPrefixes": [
                             "10.0.0.0/16"
                           ]
                         }
DhcpOptions            : {}
FlowTimeoutInMinutes   : null
Subnets                : [
                           {
                             "Delegations": [],
                             "Name": "gatewaySubnet",
                             "Etag": "W/\"e81a3b5f-a4e9-4986-8d2f-d7b79e3237df\"",
                             "Id": "/subscriptions/62dd0295-0094-443a-9b60-4c75dac248eb/resourceGroups/MainRG/providers/Microsoft.Network/virtualNetworks/HUBVNET/subne
                         ts/gatewaySubnet",
                             "AddressPrefix": [
                               "10.0.0.0/27"
                             ],
                             "IpConfigurations": [],
                             "ServiceAssociationLinks": [],
                             "ResourceNavigationLinks": [],
                             "ServiceEndpoints": [],
                             "ServiceEndpointPolicies": [],
                             "PrivateEndpoints": [],
                             "ProvisioningState": "Succeeded",
                             "PrivateEndpointNetworkPolicies": "Enabled",
                             "PrivateLinkServiceNetworkPolicies": "Enabled",
                             "IpAllocations": []
                           },
                           {
                             "Delegations": [],
                             "Name": "FirewallSubnet",
                             "Etag": "W/\"e81a3b5f-a4e9-4986-8d2f-d7b79e3237df\"",
                             "Id": "/subscriptions/62dd0295-0094-443a-9b60-4c75dac248eb/resourceGroups/MainRG/providers/Microsoft.Network/virtualNetworks/HUBVNET/subne
                         ts/FirewallSubnet",
                             "AddressPrefix": [
                               "10.0.0.32/27"
                             ],
                             "IpConfigurations": [],
                             "ServiceAssociationLinks": [],
                             "ResourceNavigationLinks": [],
                             "ServiceEndpoints": [],
                             "ServiceEndpointPolicies": [],
                             "PrivateEndpoints": [],
                             "ProvisioningState": "Succeeded",
                             "PrivateEndpointNetworkPolicies": "Enabled",
                             "PrivateLinkServiceNetworkPolicies": "Enabled",
                             "IpAllocations": []
                           }
                         ]
VirtualNetworkPeerings : []
EnableDdosProtection   : false
DdosProtectionPlan     : null
ExtendedLocation       : null

Now let’s do the third subnet with a template. I exported the template for the vnet in the azure portal and added the third subnet accordingly:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "virtualNetworks_HUBVNET_name": {
            "defaultValue": "HUBVNET",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Network/virtualNetworks",
            "apiVersion": "2020-11-01",
            "name": "[parameters('virtualNetworks_HUBVNET_name')]",
            "location": "eastus",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/16"
                    ]
                },
                "subnets": [
                    {
                        "name": "gatewaySubnet",
                        "properties": {
                            "addressPrefix": "10.0.0.0/27",
                            "serviceEndpoints": [],
                            "delegations": [],
                            "privateEndpointNetworkPolicies": "Enabled",
                            "privateLinkServiceNetworkPolicies": "Enabled"
                        }
                    },
                    {
                        "name": "FirewallSubnet",
                        "properties": {
                            "addressPrefix": "10.0.0.32/27",
                            "serviceEndpoints": [],
                            "delegations": [],
                            "privateEndpointNetworkPolicies": "Enabled",
                            "privateLinkServiceNetworkPolicies": "Enabled"
                        }
                    },
					{
                        "name": "OtherSubnet",
                        "properties": {
                            "addressPrefix": "10.0.0.64/27",
                            "serviceEndpoints": [],
                            "delegations": [],
                            "privateEndpointNetworkPolicies": "Enabled",
                            "privateLinkServiceNetworkPolicies": "Enabled"
                        }
                    }
                ],
                "virtualNetworkPeerings": [],
                "enableDdosProtection": false
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2020-11-01",
            "name": "[concat(parameters('virtualNetworks_HUBVNET_name'), '/FirewallSubnet')]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_HUBVNET_name'))]"
            ],
            "properties": {
                "addressPrefix": "10.0.0.32/27",
                "serviceEndpoints": [],
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        },
        {
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2020-11-01",
            "name": "[concat(parameters('virtualNetworks_HUBVNET_name'), '/gatewaySubnet')]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_HUBVNET_name'))]"
            ],
            "properties": {
                "addressPrefix": "10.0.0.0/27",
                "serviceEndpoints": [],
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        },
		{
            "type": "Microsoft.Network/virtualNetworks/subnets",
            "apiVersion": "2020-11-01",
            "name": "[concat(parameters('virtualNetworks_HUBVNET_name'), '/OtherSubnet')]",
            "dependsOn": [
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_HUBVNET_name'))]"
            ],
            "properties": {
                "addressPrefix": "10.0.0.64/27",
                "serviceEndpoints": [],
                "delegations": [],
                "privateEndpointNetworkPolicies": "Enabled",
                "privateLinkServiceNetworkPolicies": "Enabled"
            }
        }
    ]
}

Now let’s deploy this json:

PS /home/tode> New-AzResourceGroupDeployment -ResourceGroupName MainRG -TemplateFile /home/tode/Dokumente/addsubnets.json

DeploymentName          : addsubnets
ResourceGroupName       : MainRG
ProvisioningState       : Succeeded
Timestamp               : 29.09.2021 13:41:59
Mode                    : Incremental
TemplateLink            : 
Parameters              : 
                          Name                            Type                       Value     
                          ==============================  =========================  ==========
                          virtualNetworks_HUBVNET_name    String                     HUBVNET   
                          
Outputs                 : 
DeploymentDebugLogLevel : 

Finally, I created a short script:

$vnetlist = Get-AzResource -ResourceGroupName MainRG
foreach ($item in $vnetlist) {
$vnet = Get-AzVirtualNetwork -Name $item.Name 
foreach ($subnet in $vnet.Subnets) {
	$vnet.Name
	$vnet.Location
	$subnet.Name
	$subnet.AddressPrefix
	}
}

Starting with Azure. Part 1 – Setting up the Azure environment and creating VNETs

Task: Create a resource group and three virtual networks in Azure using powershell or ARM templates.
Erase the environment afterwards to minimize costs.

Note!
I’m using Ubuntu 18.04 so i had to install powershell and Az module before doing this activity. Similarly, i created my Azure account before and this is not shown in this blog post. Follow this link to install Powershell and Az module if you also run Ubuntu.

If you use Windows you will only need to install Az module.

Step 1
Log in to your azure account.

Step 2

Create your resource group with a Powershell script:

Step 3

Create 3 VNETs. The first vnet with Powershell, the other ones with a template.

Now another VNET with an arm template: First, I created a json file that describes the new vnet.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "metadata": {
    "_generator": {
      "name": "bicep",
      "version": "0.4.412.5873",
      "templateHash": "17421496824516967129"
    }
  },
  "parameters": {
    "vnetName": {
      "type": "string",
      "defaultValue": "SpokeVNET",
      "metadata": {
        "description": "VNet name"
      }
    },
    "vnetAddressPrefix": {
      "type": "string",
      "defaultValue": "10.1.0.0/16",
      "metadata": {
        "description": "Address prefix"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location for all resources."
      }
    }
  },
  "functions": [],
  "resources": [
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2020-06-01",
      "name": "[parameters('vnetName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[parameters('vnetAddressPrefix')]"
          ]
        }
      }

Then I run the deployment on the main resource group.

Then the third vnet in the same way, after modifying the json file with new values:

Let’s now try to do something creative to retrieve all vnets and their values:

$vnetlist = Get-AzResource -ResourceGroupName MainRG
PS /home/tode> foreach ($item in $vnetlist) { Get-AzVirtualNetwork -Name $item.Name | Select-Object -Property Name, Location -ExpandProperty AddressSpace | Select-Object -ExcludeProperty AddressPrefixesText }

Generate configs in bulk using Ansible

You may need to generate a lot of config (like interface config) where only some parts change. How to do this quickly? with Ansible. Let’s create a simple playbook file confgenerator.yml:

---
- name: Generate config
  hosts: myhost ##this is not important because we will not be connecting to anything
  connection: local

  gather_facts: false

  tasks:
    - name: Generate config
      template: src=configtemplate.j2 dest=config.txt
      delegate_to: localhost
      run_once: true

Now we create the template:

{% set intno = 1 | int % }
{% set baseip = 11 | int % }
{% for i in range(50) %}
{% set intno = intno + i %}
{% set baseip = baseip + i %}
int fa0/{{intno}}
ip addr 192.168.0.{{baseip}}
no shut
{% endfor %}

Finally we execute the playbook and voila we have config for 50 interfaces ready to paste into a Cisco device.

Working around parser logic in Genie

Hello

Today I came across the following problem: I needed to export some Cisco device data from Prime to a CSV file using Ansible.
I had some success initially: I used the show inventory parser with a 2960x and was able to export the data. However, I had some 9xxx switches in my mix, too.
When I tried using the ”show inventory” Genie parser on a Cisco 9300, I got the ”rp_dict variable not initialised” error. So the parser works great on a 2960x but on any C9xxx switch it fails miserably. Where’s the problem?
Let’s have a look at the source code of the show inventory Genie parser.

if 'STACK' in pid:
                    main_dict = ret_dict.setdefault('main', {})
                    main_dict['swstack'] = True

                if ('ASR-9') in pid and ('PWR' not in pid) and ('FAN' not in pid):
                    rp_dict = ret_dict.setdefault('slot', {}).\
                        setdefault('0', {}).\
                        setdefault('rp', {}).\
                        setdefault(pid, {})
                    rp_dict['name'] = name
                    rp_dict['descr'] = descr
                    rp_dict['pid'] = pid
                    rp_dict['vid'] = vid
                    rp_dict['sn'] = sn
                    asr900_rp = True

                # Ensure name, slot have been previously parsed
                    if not name or not slot:
                    continue

                # PID: ASR1000-RP2       , VID: V02  , SN: JAE153408NJ
                # PID: ASR1000-RP2       , VID: V03  , SN: JAE1703094H
                # PID: WS-C3850-24P-E    , VID: V01  , SN: FCW1932D0LB
                       if ('RP' in pid) or ('WS-C' in pid) or ('R' in name):
                         rp_dict = slot_dict.setdefault('rp', {}).\
                         setdefault(pid, {})
                         rp_dict['name'] = name
                         rp_dict['descr'] = descr
                         rp_dict['pid'] = pid
                         rp_dict['vid'] = vid
                         rp_dict['sn'] = sn

                # PID: ASR1000-SIP40     , VID: V02  , SN: JAE200609WP
                # PID: ISR4331/K9        , VID:      , SN: FDO21520TGH
                # PID: ASR1002-X         , VID: V07, SN: FOX1111P1M1
                # PID: ASR1002-HX        , VID:      , SN:
                elif (('SIP' in pid)  or ('-X' in pid) or \
                     ('-HX' in pid) or ('-LC' in pid) or ('module' in name and not ('module F' in name))) and \
                     ('subslot' not in name):

                    lc_dict = slot_dict.setdefault('lc', {}).\
                        setdefault(pid, {})
                    lc_dict['name'] = name
                    lc_dict['descr'] = descr
                    lc_dict['pid'] = pid
                    lc_dict['vid'] = vid
                    lc_dict['sn'] = sn

                # PID: SP7041-E          , VID: E    , SN: MTC164204VE
                # PID: SFP-GE-T          , VID: V02  , SN: MTC2139029X
                # PID: EM7455/EM7430     , VID: 1.0  , SN: 355813070074072
                elif subslot:
                    if ('STACK' in pid) or asr900_rp:
                        subslot_dict = rp_dict.setdefault('subslot', {}).\
                            setdefault(subslot, {}).\
                            setdefault(pid, {})

The problem is that the rp_dict variable won’t get initialised unless the module name meets some conditions ( e.g. : if (‚RP’ in pid) or (‚WS-C’ in pid) or (‚R’ in name): ) but the problem is that for the C9xxx switches, PIDs no longer begin with „WS-C”.

The solution is to rewrite the script or modify the values before assigning the data output to the Genie parser in Ansible.

- name: show inventory
      block:
        - name: Run show inventory
          ios_command:
            commands: show inventory
          register: show_inventory

        - name: Print my data
          set_fact:
            int_data_modify: "{{ show_inventory['stdout'][0] | regex_replace('C9','WS-C9') }}"

        - name: Print new data
          set_fact:
            inventory_data_final: "{{ int_data_modify | clay584.genie.parse_genie(command='show inventory', os='ios') }}"

Ansible: Is a Cisco switch a member of a stack?

Today my task was to find out if a given switch is a member of a stack or not.

Starting with manual testing, in order to find out if it’s a stack, i used the command: show switch detail. If there is more than one member, it’s a stack.
In Ansible, I registered the show switch detail command and then parsed it with Genie using the clay584 Ansible plugin.
Finally, I used an assert task to find out how many elements (IDs) are under the stack.
Easy peasy japanese.

(...)

- name: show virtual chassis detail
      block:
        - name: run show switch detail
          ios_command:
            commands: show switch detail
          register: show_sw_detail

 

        - name: set fact show switch detail
          set_fact:
            switch_detail_data: "{{show_sw_detail['stdout'][0] | clay584.genie.parse_genie(command='show switch detail', os='ios') }}"
 
    - name: assert that there is more than one member in the stack
      assert:
       that:
         (switch_detail_data["switch"]["stack"]|length > 1)

Getting output from Cisco switches using Ansible and Jinja Replace filter

Hello

Today something fairly simple. My task was to retrieve the location of a switch using Ansible and write it to a file such that only the location is recorded rather than the whole output line with that location.

Unfortunately there is no Genie parser for snmp location, so i had to be a bit creative. I’ve added the following task to my playbook:

- name: show run | i snmp-server location
      block:
        - name: Get location info
          ios_command:
            commands: show run | i snmp-server location
          register: show_location

Now i had my variable that I could use in Jinja, but the were several problems.

Firstly, using that variable name only in a Jinja template will produce a lot of content on top of the whole output, so we need to use the .stdout_lines property of that variable.

Secondly, the variable is stored in a multidimensional array, so we need to extract content from it.

Thirdly, we need to use the Jinja replace filter to get rid of what we don’t want to finally have.

Finally, this is what we’ve produced in our jinja template. It’s ready to be used to generate a report (as a template task in Ansible)

{{ hostvars[host].show_location.stdout_lines[0][0]|replace('snmp-server location', '') }}

Blog restart || Powershell $errorActionPreference = „Stop”

After this 6 month hiatus I’ve decided to restart my blog, albeit in a slightly different form. The reason is that i’ve had major career (and life) changes the pace of which has been mindboggling even by my standards. The upshot is that i am now doing mostly cloud devops work: Powershell programming, Azure work etc. This move away from traditional network work was a bit challenging at first but i guess i’d been preparing for that for a while so this had to happen at some point anyway.

Consequently, I will be focusing mostly on programming (powershell, python) and Azure, although mostly in a broader context of networks.

Let’s get started, then.
I’ve been improving an azure connection script to create some private endpoints and the (small) challenge was to only proceed with some actions if the endpoint had been created.

The solution (partly borrowed from Powershell Cookbook ) can be threefold:

(Invoke-Command localhost { "Some output" }) -and ("Connection successful!")

Alternatively:

$ErrorActionPreference = "Stop"
(Invoke-Command localhost { "Some output" }) 
Write-Host "Connection successful!"

Or after upgrading to Powershell core:

(Invoke-Command localhost { "Some output" }) && ("Connection successful!")