7 months, 2 weeks

RESTCONF and requests




One major drawback of NETCONF is its rather unmodern reliance on Extensible Markup Language (XML) as the language of choice for describing the data as well as the operations you want to carry out. To solve this, the Internet Engineering Task Force (IETF) came up with the concept of Representational State Transfer (REST) configuration (RESTCONF). Built to use the same concepts as NETCONF, RESTCONF allows you to use the HyperText Transfer Protocol (HTTP) to interact with your device instead of the Remote Procedure Calls (RPCs) used by NETCONF. And, instead of forcing the user to use XML, RESTCONF let us describe the data in either XML or JavaScript Object Notation (JSON).

When Tim Berners-Lee, a scientist at the European Organization for Nuclear Research (CERN) in Geneva, published the first drafts for HTTP, the protocol that would become the cornerstone of the World Wide Web (WWW). Started as a way for scientists to exchange their findings and collaborate on particle physics, life today could not be envisioned without the web. 

HTTP(HyperText Transfer Protocol)
HTTP is a plain text request-response protocol. A client sends an HTTP request to a server and receives an HTTP response back from the server.
The header fields of our HTTP request are used to specify  meta-information that we want to give to our server when processing the request. An HTTP header is a key-value pair that is sent by the client to the server. The most common types of information passed inside of a header are outlined here:
• Content-Type: This header field specifies the type of data (such as JSON or XML) that is being sent as part of the request body. A POST request that contains JSON-encoded data in it would set the Content-Type: application/json header. This line tells the server that the body should be interpreted as JSONformatted data.
• Accept: This header field specifies the way we would like to receive content back from the server. When we request information from our device and want to retrieve this information formatted as JSON, we would set an Accept: application/json header in our request header.
• Authorization: This header field is used to provide authentication credentials such as username/password or API tokens with our request.
• Content-Length: This specifies the size of our request body in octets. 
• Host: This specifies the host (and, optionally, the port) that the web server is listening on.

An HTTP response always has the following components:
• A status line with the protocol version and status code that is sent by the server to indicate whether a request was successful or not. An example for a status line could be HTTP/1.1 200 OK, which specifies that the HTTP protocol version is version 1.1, the status code is 200, and the reason phrase is OK.
• Response header fields that, as with request header fields, allow the server to pass meta-information such as the type of content being sent to the client. A server sending JSON-encoded information back to the client would, for example, specify this to the client by sending a Content-Type: Application/json; charset=utf-8 header that indicates to the client that the message body should be interpreted as JSON-encoded information and be encoded using utf-8.
• A (possibly empty) message body that contains the information we have requested from the server and that is separated from the header fields by a blank line.
The most important status codes that we will encounter when dealing with RESTCONF and REST APIs are listed next. For the 2XX range:
• 200 – OK: A successful HTTP response. This is usually sent when doing GET requests to retrieve information but also when using PATCH/PUT requests to replace or update existing information.
• 201 – Created: A resource was successfully created by the request that was sent. This response is mainly sent when sending information to a server via a POST request.
• 204 – No Content: A response with this status code is returned when the request was successful but no data is returned. This code is mainly encountered when dealing with DELETE requests.

For the 3XX range:
• 301 – Moved permanently: This indicates that the request should be redirected. 

For the 4XX range:
• 400 – Bad request: The server was unable to understand the request that was sent. This is usually due to a wrongly formatted request body or missing data in the request body.
• 401 – Unauthorized: The server was expecting authorization information—for example, an Authorization header with a username/password combination according to the HTTP Basic Auth method, but the request did not supply that information.
• 403 – Forbidden: The request included required authorization information, but the credentials provided do not have the required permissions.
• 404 – Not Found: The requested resource could not be found on the server.
• 405 – Method Not Allowed: The request used a method that is not allowed for this specific resource. 

For the 5XX range:
• 500 – Internal Server error: The server has encountered some sort of error. A response with this error code means that the request could not be carried 
out correctly due to an issue on the server side.
• 502 – Bad Gateway: The server had issues reaching another upstream server.
• 503 – Service Unavailable: The server currently cannot handle the request—for example, because the web service you are interacting with is currently rolling out some updates.

So, how does RESTCONF use the concepts of HTTP,the idea behind RESTCONF was to build a REST-like interface that can be used similarly to NETCONF, but instead of using RPCs and XML, we use HTTP requests and either XML or JSON to format the data we are retrieving or submitting to the device.

 Each operation in NETCONF is mapped to an HTTP request method, just as REST APIs map the Create, Read, Update, and Delete (CRUD) operations of a database system to HTTP methods, as specified in the following list:
• <get> and <get-config> are represented by GET requests.
• <edit-config> with create operation types is represented by POST requests.
• <copy-config> with create/replace operation types is represented by PUT requests.
• <edit-config>, where the goal of the operation is to change information, is represented by PATCH requests.
• <edit-config>, where the goal of the operation is to delete information, is represented by DELETE requests.

Making HTTP requests using the requests module in Python
Follow these steps to make your first HTTP request using Python and the requests module:
1. Import the requests module:
import requests
2. Create a requests session:
s = requests.Session()
3. Specify the connection information for your device:
host = "<insert your host here>"
rest_path = "restconf"
port = <insert your port here>
user = "<insert your username here>"
password = "<insert your password here>"
4. Pass the previously specified authentication information into the session and disable certificate verification if your device uses self-signed certificates when serving the RESTCONF endpoint via HTTP Secure (HTTPS):
s.auth = (user, password)
s.verify = False
5. Update the headers to specify that we are sending JSON-formatted YANG data and will accept JSON-formatted YANG data:
s.headers.update({
 "Content-Type": "application/yang-data+json",
 "Accept": "application/yang-data+json"
})
6. Put together the Uniform Resource Locator (URL) for our RESTCONF endpoint:
url = f"https://{host}:{port}/{rest_path}/"
7. Send the request:
resp = s.get(url)
8. Check the status code and, if the request returned a 200 status code and was thus successful, print out the returned information:
if resp.status_code == 200:
   print(resp)
else:
   print(f"Failed to retrieve data. Status code: {resp.status_code}")

The following code snippet shows how you can specify these additional headers when making a GET request using the get() function.
import requests
s = requests.Session()
s.headers.update({ "Content-Type": "application/json"})
url = f"https://{host}:{port}/{rest_path}/"
addt_headers = { "Accept": "application/yang-data+json"}
resp_one = s.get(url, headers=addt_headers)
resp_two = s.get(url)

Here is a list of the important HTTP methods and their corresponding requests functions:
• POST requests can be performed by using the post() function. This function importantly has a json keyword argument that allows us to specify a Python dictionary that should be sent as a JSON-formatted payload on the resulting request. A POST request that sends a dictionary called payload to the server would thus look like this:
payload = {
 "some": "data"
}
resp = s.post(url, json=payload)
• DELETE requests can be performed using the delete() function.
• PATCH requests can be performed using the patch() function. Similar to the post() function, they support the json keyword argument to specify JSONformatted data to be sent.
• PUT requests can be performed using the put() function. Just as with the post()and patch() functions, this function also supports the json keyword argument to specify a payload.

The next code snippet shows how you can send an HTTP GET request without creating a sessionobject first: 
import requests
resp = requests.get("https://athaenas.com")
print(resp.text)
This works equally for POST/PATCH/PUT/DELETE requests by using the post()/patch()/put()/delete() functions. 
We have seen that web servers might send redirect instructions (using 3XX status codes) as a response. By default, requests will follow these redirects for all types of requests except for HEAD requests. We can go ahead and change that behavior if we desire by using an allow_redirects flag, as shown in the following code snippet:
url = "https://athaenas.com"
resp = requests.get(url, allow_redirects=False)

Retrieving all interfaces of a device using RESTCONF and requests
Follow these steps to retrieve the interfaces from your RESTCONF-enabled network device:
1. Import the required modules and set up a requests session that can interact with the device. 
import requests
import pprint
s = requests.Session()
host = "<insert your host here>"
rest_path = "restconf"
port = <insert your port here>
user = "<insert your username here>"
password = "<insert your password here>"
s.auth = (user, password)
s.verify = False
s.headers.update({
 "Content-Type": "application/yang-data+json",
 "Accept": "application/yang-data+json"
})
2. Put together the URL that we want to send our request to. In this example, we want to query the interfaces that are described by the ietf-interfaces YANG module:
url = f"https://{host}:{port}/{rest_path}/ietfinterfaces:interfaces"
3. Send the request using our session setup:
resp = s.get(url)
4. Check the status code of the returned response, and if the response was successful, use prettyprint to print out the resulting JSON-formatted data:
if resp.status_code == 200:
   pprint.pprint(resp.json())
else:
   print(f"Failed to retrieve data from device. 
Status code: {resp.status_code}")

In YANG, we have the concept of leaves. In our previous example, every interface is a leaf of the container interfaces. We can specify one of these leaves by specifying the parameter—for example, the interface name, that we want to check for. You can do that by changing the URL, as follows:
url = f"https://{host}:{port}/{rest_path}/ietf-interfaces:interfaces/?interface=GigabitEthernet2
resp = s.get(url)
if resp.status_code == 200:
   pprint.pprint(resp.json())
else:
 print(f"Failed to retrieve data from device. Status code: {resp.status_code}") 

This base URL contains the following:
• The protocol—Usually, this will be https://.
• The host—In our recipes, we are always including them from our host variable.
• The port that your device is serving RESTCONF on. In our recipes, we are always including this information from the port variable.
• The entry point for your RESTCONF endpoints. In our recipes, we are specifying this using the rest_path variable. This entry point can vary depending on the vendor.

The following code snippet will print a list of the names of all available modules on the command line. You'll have to have a session created for your device:
url = f"https://{host}:{port}/{rest_path}/ietf-yanglibrary:modules-state"
resp = s.get(url)
if resp.status_code == 200:
  data = resp.json()
  for m in data['ietf-yang-library:modules-state']['module']:
    print(f"- {m['name']}")

Creating a VLAN using RESTCONF and requests
Follow these steps to use RESTCONF and requests to create a new VLAN:
1. Import the required modules and set up a requests session that can interact with the device.
import requests
import pprint
s = requests.Session()
host = "<insert your host here>"
rest_path = "restconf"
port = <insert your port here>
user = "<insert your username here>"
password = "<insert your password here>"
s.auth = (user, password)
s.verify = False
s.headers.update({
"Content-Type": "application/yang-data+json",
 "Accept": "application/yang-data+json"
})
2. With our session connected and set up, we can now go ahead and specify the URL that we want to use to configure it:
intf_name = "<insert your interface name here>"
url = f"https://{host}:{port}/{rest_path}/ios-xenative:native/interface/{intf_name}"
3. Next, we need to specify the data that we want to send to our RESTCONF server on the device:
payload = {
 "name": "3.10",
 "encapsulation": {
 'dot1Q": {
 'vlan-id': 10
 }
 }
}
4. With our data and URL specified, we can use a POST request from our session to carry out the operation:
resp = s.post(url, json=payload)
5. Check the status code of the returned response, and if the response was successful, use prettyprint to print out the resulting JSON-formatted data:
if resp.status_code == 200:
    pprint.pprint(resp.json())
else:
    print(f"Failed to retrieve data from device. Status code: {resp.status_code}")

Updating a VLAN using RESTCONF and requests
Follow these steps to define the necessary information to change a previously created VLAN:
1. Import the required modules and set up a requests session that can interact with the device. 
import requests
import pprint
s = requests.Session()
host = "<insert your host here>"
rest_path = "restconf"
port = <insert your port here>
user = "<insert your username here>"
password = "<insert your password here>"
s.auth = (user, password)
s.verify = False
s.headers.update({
 "Content-Type": "application/yang-data+json",
 "Accept": "application/yang-data+json"
})
2. Our session connected and set up: 
intf_name = "<insert your interface name here>"
url = f"https://{host}:{port}/{rest_path}/ios-xenative:native/interface/{intf_name}"
3. Next, we need to specify the data that we want to change on our interface. We need to send that data to our RESTCONF server on the device:
payload = {
 "name": "3.15",
 "encapsulation": {
 'dot1Q": {
 'vlan-id': 15
}
 }
}
4. With our data and URL specified, we can use a POST request from our session to carry out the operation:
resp = s.patch(url, json=payload)
5. Check the status code of the returned response, and if the response was successful, use prettyprint to print out the resulting JSON-formatted data:
if resp.status_code == 200:
   pprint.pprint(resp.json())
else:
   print(f"Failed to retrieve data from device. 
Status code: {resp.status_code}")

PATCH should be used when you are only changing part of the configuration.HTTP PATCH is basically said to be non-idempotent. So if you retry the request N times, you will end up having N resources with N different URIs created on the server. It has Low Bandwidth. PUT, on the other hand, is used to replace an entire object with a new object. HTTP PUT is said to be idempotent, So if you send retry a request multiple times, that should be equivalent to a single request modification.It has High Bandwidth.


An HTTP method is idempotent if an identical request can be made once or several times in a row with the same effect while leaving the server in the same state. In other words, an idempotent method should not have any side-effects. Implemented correctly, the GET, HEAD, PUT, and DELETE methods are idempotent, but not the POST method.

Deleting a VLAN using RESTCONF and requests
Follow these steps to delete a VLAN using RESTCONF and requests:
1. Import the required modules and set up a requests session that can interact with the device.
import requests
import pprint
s = requests.Session()
host = "<insert your host here>"
rest_path = "restconf"
port = <insert your port here>
user = "<insert your username here>"
password = "<insert your password here>"
s.auth = (user, password)
s.verify = False
s.headers.update({
 "Content-Type": "application/yang-data+json",
 "Accept": "application/yang-data+json"
})
2. This URL will define the target device: 
intf_name = "<insert your interface name here>"
url = f"https://{host}:{port}/{rest_path}/ios-xenative:native/interface/{intf_name}"
3. In addition to the target interface, we also need to specify the target VLAN that we want to delete.
target_vlan = "<insert target vlan here>"
url = f"{url}/{target_vlan}"
4. With our data and URL specified, we can use a DELETE request from our session to invoke a delete operation on our device.
resp = s.delete(url)
5. Check the status code of the returned response and, if the response was successful, use prettyprint to print out the resulting JSON-formatted data:
if resp.status_code == 200:
  pprint.pprint(resp.json())
else:
  print(f"Failed to retrieve data from device. Status code: {resp.status_code}")


 


Responses(0)