Loading...
right-arrow (6)Back
Blog

Network Automation with ALE Web Services

Groupe_887Feb. 24, 2022
Network Automation ALE Web Services

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
Network Automation ALE Web Services

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.

        ---
        - host: 192.168.199.193
          username: admin
          password: switch
        #!/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)
Network automation ALE Web services

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:
    • A 2xx value if the command was successful. In most cases, 200 will be used.
    • A 3xx value if a resource has been moved (not implemented).
    • A 4xx value if the request contained an error, such as 400 if authentication failed.
    • 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.
Network automation ALE Web services
        [ale]
        6465T ansible_host=192.168.199.193
        [ale:vars]
        username=admin
        password=switch
        [defaults]
        inventory = ./hosts
        host_key_checking = False
        roles_path = roles.galaxy:roles
        stdout_callback = yaml
        ---
        - 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
  
network automation ale web services
Loading