Python
ND Client
  • Introduction
  • Development Environment
  • NDFC
  • ND/NDFC REST API
  • NDFC Postman
  • NDFC Python
  • NDFC Ansible
  • NDFC CI/CD

Create ND Connector Client

For this section of the lab, you are going to create various Python files that pull together into a simple command line program for creating overlay constructs, VRFs and Networks, and for getting that data back from NDFC to verify the state of the controller. You will also define a YAML file that contains configuration data variables for provisioning the new VRFs and Networks along with the target switches for attaching and deploying the configuration.

To start your Python development for NDFC, you are first going to create a basic ND connector client. This basic client will handle the ND/NDFC login, session, and subsequent requests such as GETs or POSTs to API endpoints.

Step 1 - Create Directory for Python Scripts

To begin, return to your VSCode terminal pane. Create a new scripts directory in your ndfclab directory that was created earlier in the lab, then change directory into your newly created scripts directory.


mkdir scripts
cd scripts


Step 2 - Open File to Create Reusable ND Client Python Class

Since your development environment is using VSCode and the terminal window embedded, you can make use of VSCode's code keyword. Using the -r option tells VSCode to open a file in the existing VSCode window.


code-server -r ~/workspace/ndfclab/scripts/ndclient.py


Step 3 - Include Python Package Imports

The first thing you need to do is import the Python modules required for your code. You'll mainly be using the requests package that you pip installed in the setup of your development environment to perform operations against ND/NDFC.

    
import json
import urllib3
import requests
from requests.exceptions import ConnectionError
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

 


Step 4 - Add __init__ Method to Python Class

Next, define your connector client Python class object; this will be called NDClient. This creates a new type of object and following good practice in Python, you next define a "dunder" init or __init__ method. Functions that are part of classes are called methods. The __init__ method is called each time by default when the class object is instantiated. Upon calling, it initializes the class object's attributes for use by other methods in the class or directly within your own code.

For your ND client connector, you want the __init__ to take the required parameters to authenticate with ND. These include your ND URL, username, password, etc. When your ND client class is instantiated, you want to initialze things that are reusable such as base_url, the requests session that is kept after authenticating, request headers, etc. Make note that the underscore in front of the attribute names are reserved for internal use to the class.

    
class NDClient:
    def __init__(self, url: str, username: str, password: str, login_domain: str = "local", verify: bool = False):
        """

        Args:
            url (str): ND mgmt url, https://nd.example.com, https://192.168.1.2
            username (str): Username
            password (str): Password
            login_domain (str): login domain, default is local
            verify (bool): verify SSL ceritificate, default is False

        """
        self._base_url = url
        self._username = username
        self._password = password
        self._login_domain = login_domain
        self._verify = verify
        self._session = requests.session()
        self._headers = {
            "Content-Type": "application/json"
        }
            


Step 5 - Add Login Method to Python Class

The next method you need is one that can handle the login, authentication, and storing the session to ND. Like in the previous exercises with the API Docs and Postman, the data variable, which is a Python dictionary, is used to build the body (payload) that is sent to ND to authenticate and obtain a JWT (jwttoken as referred to earlier in the lab). Notice how the parameter attributes set and initialized by the __init__ are used to build the authentication payload.

With the payload to send to ND built, you need some code to send the request that takes the login API endpoint, sets the correct HTTP method, a POST, and passes the data payload. For this simple client, you will create a send method that will be defined in the next step.

    
    def login(self) -> bool:
        """
        login function should be called once client instance is initialized, client.login()
        Returns:
            bool: True if login success, False if login failed

        """
        data = {
            "userName": self._username,
            "userPasswd": self._password,
            "domain": self._login_domain
        }

        resp = self.send(endpoint='/login',
                            method="POST",
                            data=data)
        return resp
            


Step 6 - Add Generic Send Method to Python Class

This send method is the last required method that needs defining in your simple connector client. This method takes any ND/NDFC API endpoint as a parameter and combines it with your base_url from your __init__, the required HTTP method, optional data which would be the body/payload if required by the API endpoint, and request headers. Remember, you defined the default headers in your __init__ method, but if you needed different or additional headers, this provides the ability to update or pass new ones.

Lastly, the Python requests library is used to build and send the API request to ND/NDFC. Additional error checking could surely be added, but for the purposes of this lab, the code wraps the request attempt in a try/except block and if there are any connection issues will raise an exception.

    
    def send(self, endpoint: str, method: str, data: dict = None, headers: dict = {}):
        """

        Args:
            endpoint (str): API endpoint like "/version"
            method (str): Choose from "get", "post", "put", "delete"
            data (dict): payload in dict, default is None
            headers (dict): addtional headers need to be sent with API, default is {}

        Returns:
            requests.Response: REST API response

        Raises:
            ValueError: if any input is invalid

        """
        if method.lower() not in ["get", "put", "post", 'delete']:
            raise ValueError(f"Invalid method: {method}")

        request_url = self._base_url + endpoint
        request_headers = self._headers
        extra_headers = headers
        if headers == {}:
            extra_headers = {}  # normalize the headers

        request_headers.update(extra_headers)

        req = requests.Request(method=method.upper(),
                                url=request_url,
                                headers=request_headers,
                                data=json.dumps(data))
        prep_req = self._session.prepare_request(req)
        try:
            resp = self._session.send(request=prep_req, verify=self._verify)
            return resp
        except ConnectionError as e:
            raise e

Warning

Be sure to save your file! Not saving will result in your code not executing.

Continue to the next section to create your overlay onboarding script that will import and leverage this Python module to connect to and send requests to your NDFC instance.