GettyImages-512833663-410x410

Network Automation with ALE Web Services

Alcatel-Lucent Enterprise regularly reminds us: “the objective is to eventually only have Ethernet switches running AOS8”. Indeed, all the models […]

Post on 24.02.2022 by Gilbert MOISIO
Automation Network management

Alcatel-Lucent Enterprise regularly reminds us: “the objective is to eventually only have Ethernet switches running AOS8”. Indeed, all the models that came out and were added to the catalog, sometimes replacing existing models, run version 8 of the AOS. In this context, it is interesting to see the possibilities offered in terms of external automation in a homogeneous environment.

As a reminder, when you are in a mixed AOS6 and AOS8 context, the possibilities are as follows:

  • Use the Netmiko library with Python, for which I wrote the AOS module in 2017 and which is still maintained by Kirk Byers during the various upgrades of the library.
  • Use the new gmoisio.ale Collection Network Module I wrote for Ansible in 2021 (an upgrade to the ale_aos module).

It is the Web Services function that allows you to customize and extend the management interface on AOS8 devices. In our case, it supports the use of a REST-based web interface that interacts with AOS management variables (MIBs) and CLI commands. So, it provides two configuration methods via direct management of MIB variables or using CLI commands and supports both XML and JSON response formats.

Do not confuse REST with the RESTCONF protocol which uses the YANG data model. REST is a set of guidelines for the software architecture of distributed systems and AOS8 supports the following verbs:

  • GET: to retrieve information. It is a coarse equivalent of SNMP/MIB GET but also, at a higher level, an equivalent of the show command. This verb is used exclusively for read-only commands and without any other side effects.
  • PUT: to create new information, such as, for example, a new VLAN. This is a write operation.
  • POST: Works the same as submitting web forms and is used, in a web service context, to update existing information.
  • DELETE: to delete existing information.

Security is maintained through the use of sessions in the backend and cookies in the frontend, which is equivalent to current HTTP security for thin clients.

  • Authentication – Adheres to a Web Service model, via its own REST domain and the use of the GET verb.
  • Authorization – Follows the authorization mechanism usually used in WebView, where WebView checks with Partition Manager which permission families a user belongs to, thus specifying which MIB tables can be accessed by that user.
  • Encryption – If unencrypted access (HTTP) is allowed, then the web service is allowed over the same transport. Likewise, if the HTTP/HTTPS listening ports are changed, the web service will be available through those ports.

The following are used to create the REST URL:

  • Protocol – The protocol can be HTTP (clear text) which defaults to port 80, or HTTPS (encrypted) which defaults to port 443.
  • Server address [:port] – The IP address is the one used to access the switch. If the listening port has been changed, the port number must be added. The addition of Protocol + Server address [: port] constitutes the “endpoint” of the Web Service.
  • Domain – This is the first item that the AOS REST web service will look at. It indicates in which domain the accessed resource is located:
    • AUTH – Used for authentication functions.
    • MIB – Used to designate access to MIB variables.
    • CLI – Used to instruct the web service to run CLI commands.
    • INFO – Used to return information about a MIB variable.
  • URN – Represents the resource to access. For example, when reading MIB domain information, the URNs are names of MIB variables (in most cases, tables). URNs are accessed using the following verbs: GET, PUT, POST, DELETE.
  • Variables – A list of variables that depend on the domain being accessed. When reading the MIB domain, it is a list of variables to be extracted from a MIB table.

The output format can be encoded using XML or JSON. The “Accept” header can be used to specify an output type:

  • application/vnd.alcatellucentaos+json
  • application/vnd.alcatellucentaos+xml

Due to the volatile nature of the returned content, the Web Service (producer) will instruct any system positioned between it and the “consumer” (included) not to cache its output. The following headers are returned by the “producer”:

  • Cache-Control: no-cache, no-store
  • Pragma: no-cache
  • Vary: Content-Type

    AOS8 MIB vlanTable
    AOS8 MIB vlanTable

The first two headers indicate that caching should not take place. The last header is for proxy servers, informing them that the Content-Type header is a variable not to be cached. If a proxy server decided not to respect this directive, it would be possible to have unexpected behaviors such as retrieving the JSON data format after specifically requesting the XML format.

For implementing REST on AOS8, I don’t find the official documentation very helpful. Indeed, the proposal is based on the consumer.py library which is not officially maintained and whose evolution is not guaranteed.

To make Python scripts, I prefer to use the requests library which is my favorite for HTTP support. Its use is simple to manage authentication, just create a session and generate all the requests within the framework of this session.

Before being able to use the MIB, it must be analyzed by downloading it and browsing the files using a suitable tool.

 

 

 

hosts.yaml


        ---
        - host: 192.168.199.193
          username: admin
          password: switch
    

 

rest.py


        #!/usr/bin/env python
        """
            Restconf python3 example by Gilbert MOÏSIO
            Installing python dependencies:
            > pip install requests pyyaml
        """
        import requests
        import yaml
        import json
        from pprint import pprint
        requests.packages.urllib3.disable_warnings()
        headers = {'Accept': 'application/vnd.alcatellucentaos+json'}
        if __name__ == '__main__':
            # get parameters from yaml file
            with open('hosts.yaml') as f:
                hosts = yaml.load(f, Loader=yaml.FullLoader)
            # iterate onto the list
            for host in hosts:
                # execute requests
                try:
                    # create a session for persistence
                    session = requests.Session()
                    # login into the switch
                    payload = {'username': host['username'],
                               'password': host['password']}
                    uri = f"https://{host['host']}/auth/"
                    session.get(uri, params=payload, verify=False)
                    # get MIB structure using info domain
                    uri = f"https://{host['host']}/info/vlanTable?"
                    response = session.get(uri, headers=headers, verify=False)
                    print('{0}{1} vlanTable MIB structure {1}{0}'.format('\n', '='*10))
                    pprint(json.loads(response.text)['result']['data'])
                    # add a new vlan using mib domain
                    payload = {'mibObject0': 'vlanNumber:666',
                               'mibObject1': 'vlanDescription:go_to_hell'}
                    uri = f"https://{host['host']}/mib/vlanTable"
                    response = session.put(
                        uri, data=payload, headers=headers, verify=False)
                    print('{0}{1} add vlan 666 {1}{0}'.format('\n', '='*10))
                    print(json.loads(response.text)['result']['error'])
                    # modify vlan description using mib domain
                    payload = {'mibObject0': 'vlanNumber:666',
                               'mibObject1': 'vlanDescription:this_one_is_better'}
                    uri = f"https://{host['host']}/mib/vlanTable"
                    response = session.post(
                        uri, data=payload, headers=headers, verify=False)
                    print('{0}{1} change vlan 666 description {1}{0}'.format('\n', '='*10))
                    print(json.loads(response.text)['result']['error'])
                    # get vlan informations with CLI command
                    payload_str = '&cmd=show+vlan+666'
                    uri = f"https://{host['host']}/cli/aos"
                    response = session.get(uri, headers=headers,
                                           params=payload_str, verify=False)
                    print('{0}{1} vlan 666 informations {1}{0}'.format('\n', '='*10))
                    print(json.loads(response.text)['result']['output'])
                    # delete vlan using mib domain
                    payload = {'mibObject0': 'vlanNumber:666'}
                    uri = f"https://{host['host']}/mib/vlanTable"
                    response = session.delete(
                        uri, data=payload, headers=headers, verify=False)
                    print('{0}{1} delete vlan 666 {1}{0}'.format('\n', '='*10))
                    print(json.loads(response.text)['result']['error'])
                    # logout from the switch
                    uri = f"https://{host['host']}/auth/?"
                    session.get(uri, verify=False)
                except requests.exceptions.RequestException as e:
                    print(e._raw)
    

 

AOS8 Python requests REST

 

Whether in XML or JSON format, the response contains the following elements:

domain – Shows how the “producer” interpreted the domain parameter. In most cases, it will be the same as that passed by the “consumer”.
diag – This integer will be a standard HTTP diagnostic code:

o A 2xx value if the command was successful. In most cases, 200 will be used.
o A 3xx value if a resource has been moved (not implemented).
o A 4xx value if the request contained an error, such as 400 if authentication failed.
o A 5xx value if the server encountered an internal error such as a resource error.

error – Can be a string containing a plain text error message. It can also be an array of these channels in case the producer finds several problems on this request.
output – In some cases, the queried subsystem may want to return some text to this variable.
data – If a GET request is issued, this variable should contain the queried values in a structured form.

 

AOS8 response

 

hosts


        [ale]
        6465T ansible_host=192.168.199.193
        [ale:vars]
        username=admin
        password=switch
    

 

ansible.cfg


        [defaults]
        inventory = ./hosts
        host_key_checking = False
        roles_path = roles.galaxy:roles
        stdout_callback = yaml
    

rest.yml


        ---
        - name: This is a test to request AOS8 API
          hosts: ale
          vars:
            ansible_python_interpreter: "python"
          connection: local
          gather_facts: no
          tasks:
            - name: Login into the switch
              uri:
                url: "https://{{ ansible_host }}/auth/?username={{ username }}&password={{ password }}"
                validate_certs: no
                method: GET
              register: login
            - name: get MIB structure using info domain
              uri:
                url: "https://{{ ansible_host }}/info/vlanTable?"
                validate_certs: no
                method: GET
                return_content: yes
                headers:
                  Accept: "application/vnd.alcatellucentaos+json"
                  Cookie: "{{ login.cookies_string }}"
              register: result
            - name: Print body of the response
              debug:
                msg:
                  - "{{ result.json.result.data }}" 
              when: result.json.result.diag  == 200
            - name: Add a new vlan using mib domain
              uri:
                url: "https://{{ ansible_host }}/mib/vlanTable"
                validate_certs: no
                method: PUT
                headers:
                  Accept: "application/vnd.alcatellucentaos+json"
                  Cookie: "{{ login.cookies_string }}"
                body_format: form-urlencoded
                body:
                  mibObject0: "vlanNumber:666"
                  mibObject1: "vlanDescription:go_to_hell"
              register: result
            - name: Print body of the response
              debug:
                msg:
                  - "{{ result.json.result.error }}" 
              when: result.json.result.diag  == 200
            - name: Modify vlan description using mib domain
              uri:
                url: "https://{{ ansible_host }}/mib/vlanTable"
                validate_certs: no
                method: POST
                headers:
                  Accept: "application/vnd.alcatellucentaos+json"
                  Cookie: "{{ login.cookies_string }}"
                body_format: form-urlencoded
                body:
                  mibObject0: "vlanNumber:666"
                  mibObject1: "vlanDescription:this_one_is_better"
              register: result
            - name: Print body of the response
              debug:
                msg:
                  - "{{ result.json.result.error }}" 
              when: result.json.result.diag  == 200
            - name: Get vlan informations with CLI command
              uri:
                url: "https://{{ ansible_host }}/cli/aos?cmd=show+vlan+666"
                validate_certs: no
                method: GET
                return_content: yes
                headers:
                  Accept: "application/vnd.alcatellucentaos+json"
                  Cookie: "{{ login.cookies_string }}"
              register: result
            - name: Print body of the response
              debug:
                msg:
                  - "{{ result.json.result.output }}" 
              when: result.json.result.diag  == 200
            - name: Delete vlan using mib domain
              uri:
                url: "https://{{ ansible_host }}/mib/vlanTable"
                validate_certs: no
                method: DELETE
                headers:
                  Accept: "application/vnd.alcatellucentaos+json"
                  Cookie: "{{ login.cookies_string }}"
                body_format: form-urlencoded
                body:
                  mibObject0: "vlanNumber:666"
              register: result
            - name: Print body of the response
              debug:
                msg:
                  - "{{ result.json.result.error }}" 
              when: result.json.result.diag  == 200
            - name: Logout from the switch
              uri:
                url: "https://{{ ansible_host }}/auth/?"
                validate_certs: no
                method: GET
    

 

AOS8 Ansible uri REST
AOS8 Ansible uri REST

 

You can access the French version of this article here

 

 

Gilbert Moisio

Network Infrastructures and Methodologies Senior Consultant, Lecturer, Trainer, Member of boards of examiners. Involved in research and development projects, member of standards organizations.

Passionate about the acquisition of knowledge and its sharing which is a personal learning accelerator, whether in technologies, methodologies or in the subjects making up the basic foundation necessary to cross the different skill levels.

You can check out his website in French

Please wait...

Comment below to share your thoughts on this blog post

Notify me of followup comments via e-mail. You can also subscribe without commenting.

This site uses Akismet to reduce spam. Learn how your comment data is processed.