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.
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
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
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)
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"
}
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
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
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.