Interacting with the NSX-T Policy API

For an overview of the NSX-T Policy API available in version 2.4 and how it relates to the older management plane API, you can take a look at my previous posts. In this post, I will try to provide some insight on how to leverage the NSX-T policy API at its full potential.

The Data Model

When using a declarative API such as the NSX-T policy API, we expect to be able to express our desire configuration in one go, without worrying about how the system will realize it. The desired state will include multiple objects (segments, gateways, load balancers, etc..). NSX  needs to be aware of the relationship between those objects to realize the desired state. In a parent-child relationship, the parent object will have to be created before the child object (i.e., a routing configuration cannot be created before the gateway to which it will be applied). In case of an association between objects, the objects will need to be created before the association can be made (i.e., the source and destination groups in a security policy must be created before the security policy itself).

The NSX-T policy API data model has a hierarchical structure that can be represented as a tree. This data model allows us to specify the relationship between the different objects. The root node is named “Infra”, and any other object is represented as a child of the “Infra” node or a lower-level object.  The diagram below presents a high-level overview of the NSX-T API Hierarchy, to all relationships are shown.

Screen Shot 2019-07-17 at 2.39.54 AM

Representing the data model in JSON

Each node has a property named “resource_type” that specifies the node type (Gateway, Segment, etc..).  It also has a property called “children” that lists all the children object of that node. When describing the configuration in JSON, an additional layer is also used. Let’s take a look at the snippet below, describing the hierarchy for the Infra node and two children of type Service and Domain respectively.

{
  "resource_type": "Infra",
  "children": [
    {
      "resource_type": "ChildService",
      "marked_for_delete": "false",
      "Service": {
        ............................................
        ............................................
        "resource_type": "Service",
        "display_name": "NEW_SERVICE"
        "id": "NEW_SERVICE"
      }
    },
    {
      "resource_type": "ChildDomain",
      "marked_for_delete": false,
      "Domain": {
        "id": "default",
        "resource_type": "Domain",
        ...........................................

The children objects for the Infra nodes are not the actual Service and Domain nodes. Those are nested one level deeper. Directly under Infra, we see some intermediary nodes of type ChildService and ChildDomain. Those wrappers then have a property named like the resource_type (Service and Domain in this example) containing the actual object configuration. Beware, those resource_type properties are case sensitive.

Please note the IDs in the previous snippet such as “NEW_SERVICE” for the service object:

{
  "resource_type": "Infra",
  "children": [
    {
      "resource_type": "ChildService",
      "marked_for_delete": "false",
      "Service": {
        ............................................
        ............................................
        "resource_type": "Service",
        "display_name": "NEW_SERVICE"
        "id": "NEW_SERVICE"
      }

Those IDs are user-defined and can be referenced in other objects to create associations. When referencing an object, the full path in the hierarchy is required. See example below:

............................................
                  "resource_type": "Rule",
                  "id": "webapp-rule-03",
                  "description": "webapp-rule-03",
                  "display_name": "LB to WEB",
                  "sequence_number": 70,
                  "source_groups": [
                    "/infra/domains/default/groups/LB"
                  ],
                  "destination_groups": [
                    "/infra/domains/default/groups/WEB"
                  ],
                  "services": [
                    "/infra/services/NEW_SERVICE"

All snippets are taken from the micro-segmentation example in this post.

One of the challenges getting started with the NSX-T Policy API hierarchy is mapping all the parent-child relationships you need. You have two options: doing it the hard way following the API documentation or hacking your way to it by configuring a test object via the UI and then retrieving the corresponding configuration tree via the API. I  use a combination of both, but if you are just starting, I would recommend reversing engineering a UI configuration as it leads to fewer frustrations.

Making the calls

When interacting with the NSX Policy API hierarchically, two methods are available: GET and PATCH. POST, PUT, and DELETE are supported but only when creating, modifying, and deleting an individual object, never when acting on multiple objects in the hierarchical tree.

GET API Call

To retrieve the configuration hierarchically, a special parameter must be added to the URL. A hierarchical query can only be performed against the root node Infra. If you try to pass the parameter to any other base URL, the query will fail.

~$curl --user admin  --request GET  https://nsxmgr-01a/policy/api/v1/infra?filter=Type- -k > temp
Enter host password for user 'admin':
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1863k    0 1863k    0     0  74175      0 --:--:--  0:00:25 --:--:--  227k
~$
~$ curl --user admin  --request GET  https://nsxmgr-01a/policy/api/v1/infra/domains/default?filter=Type- -k
Enter host password for user 'admin':
{
  "httpStatus" : "BAD_REQUEST",
  "error_code" : 268,
  "module_name" : "common-services",
  "error_message" : "Request has unknown parameters: filter."

When querying without the special parameter “filter”, only the individual object referenced in the URL will be retrieved.

I redirected the output of the hierarchical query to a temporary file because of its size. The “filter” parameter allows us to retrieve only a subset of the tree, but you must be able to specify the resource types you are interested in. Below an example where I query only for T1 routers.

~$ curl --user admin  --request GET  'https://nsxmgr-01a/policy/api/v1/infra?filter=Type-Tier1' -k
Enter host password for user 'admin':
{
  "resource_type" : "Infra",
  "id" : "infra",
  "display_name" : "infra",
  "path" : "/infra",
  "relative_path" : "infra",
  "children" : [ {
    "Tier1" : {
      "tier0_path" : "/infra/tier-0s/t0-sitea",
      "failover_mode" : "NON_PREEMPTIVE",
      "route_advertisement_types" : [ "TIER1_CONNECTED" ],
      "force_whitelisting" : false,
      "default_rule_logging" : false,
      "disable_firewall" : false,
      "resource_type" : "Tier1",
      "id" : "t1-tenant1",
      "display_name" : "t1-tenant1",
      "description" : "tenant-1 gateway",
      "path" : "/infra/tier-1s/t1-tenant1",
      "relative_path" : "t1-tenant1",
      "parent_path" : "/infra/tier-1s/t1-tenant1",
      "children" : [ ],
      "marked_for_delete" : false,
      "_create_user" : "admin",
      "_create_time" : 1560188737945,
      "_last_modified_user" : "admin",
      "_last_modified_time" : 1560188738579,
      "_system_owned" : false,
      "_protection" : "NOT_PROTECTED",
      "_revision" : 1
    },
    "resource_type" : "ChildTier1",
    "marked_for_delete" : false,
    "_protection" : "NOT_PROTECTED"
  } ],
  "marked_for_delete" : false,
  "connectivity_strategy" : "BLACKLIST",
  "_create_user" : "system",
  "_create_time" : 1558467133368,
  "_last_modified_user" : "system",
  "_last_modified_time" : 1558467133368,
  "_system_owned" : false,
  "_protection" : "NOT_PROTECTED",
  "_revision" : 0

If the entire hierarchy must be retrieved, it is enough to set the parameter to “filter=Type-” as shown two snippets above.

PATCH API Call

Similarly to the GET API Call, when updating a configuration tree that the only URL supported is the one representing the Infra root node. If you try to send a PATCH to a lower-level node, you might receive a 200 OK back, but the configuration will not be updated. Below an example showing how to create then updating a segment. I will start building it:

~$ curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":false,
         "Segment":{
            "type":"DISCONNECTED",
            "transport_zone_path":"/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
            "resource_type":"Segment",
            "id":"test-segment",
            "display_name":"test-segment"
         }
      }
   ]
}'
Enter host password for user 'admin':
~$
~$ curl --user admin  --request GET  'https://nsxmgr-01a/policy/api/v1/infra/segments/test-segment' -k
Enter host password for user 'admin':
{
  "type" : "DISCONNECTED",
  "transport_zone_path" : "/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
  "resource_type" : "Segment",
  "id" : "test-segment",
  "display_name" : "test-segment",
  "path" : "/infra/segments/test-segment",
  "relative_path" : "test-segment",
  "parent_path" : "/infra/segments/test-segment",
  "marked_for_delete" : false,
  "_create_user" : "admin",
  "_create_time" : 1563378579272,
  "_last_modified_user" : "admin",
  "_last_modified_time" : 1563378579272,
  "_system_owned" : false,
  "_protection" : "NOT_PROTECTED",
  "_revision" : 0
}~$

Let’s now modify it. For instance, we could change the display_name property:

curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":false,
         "Segment":{
            "type":"DISCONNECTED",
            "transport_zone_path":"/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
            "resource_type":"Segment",
            "id":"test-segment",
            "display_name":"test-segment-changed"
         }
      }
   ]
}'
Enter host password for user 'admin':
~$
~$ curl --user admin  --request GET  'https://nsxmgr-01a/policy/api/v1/infra/segments/test-segment' -k
Enter host password for user 'admin':
{
  "type" : "DISCONNECTED",
  "transport_zone_path" : "/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
  "resource_type" : "Segment",
  "id" : "test-segment",
  "display_name" : "test-segment-changed",
  "path" : "/infra/segments/test-segment",
  "relative_path" : "test-segment",
  "parent_path" : "/infra/segments/test-segment",
  "marked_for_delete" : false,
  "_create_user" : "admin",
  "_create_time" : 1563378579272,
  "_last_modified_user" : "admin",
  "_last_modified_time" : 1563378890747,
  "_system_owned" : false,
  "_protection" : "NOT_PROTECTED",
  "_revision" : 1

Note that we did not specify the revision number for the object, but our change was accepted. If we want to enforce the revision check we need to add it as a parameter in the URL. Let’s try again, enabling the revision check.

~$ curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra?enforce_revision_check=true' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":false,
         "Segment":{
            "type":"DISCONNECTED",
            "transport_zone_path":"/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
            "resource_type":"Segment",
            "id":"test-segment",
            "display_name":"test-segment-changed-again"
         }
      }
   ]
}'
Enter host password for user 'admin':
{
  "httpStatus" : "BAD_REQUEST",
  "error_code" : 500157,
  "module_name" : "Policy",
  "error_message" : "Error during creating objects of type:Segment",
  "related_errors" : [ {
    "httpStatus" : "BAD_REQUEST",
    "error_code" : 500127,
    "module_name" : "Policy",
    "error_message" : "Cannot create an object with path=[/infra/segments/test-segment] as it already exists."
  } ]
}

We receive an error as the API is now expecting to receive the correct revision number for the object. Let’s try to add the “_revision” property now:

~$ curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra?enforce_revision_check=true' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":false,
         "Segment":{
            "type":"DISCONNECTED",
            "transport_zone_path":"/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
            "resource_type":"Segment",
            "id":"test-segment",
            "display_name":"test-segment-changed-again",
            "_revision":"1"
         }
      }
   ]
}'
Enter host password for user 'admin':
~$
~$ curl --user admin  --request GET  'https://nsxmgr-01a/policy/api/v1/infra/segments/test-segment' -k
Enter host password for user 'admin':
{
  "type" : "DISCONNECTED",
  "transport_zone_path" : "/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
  "resource_type" : "Segment",
  "id" : "test-segment",
  "display_name" : "test-segment-changed-again",
  "path" : "/infra/segments/test-segment",
  "relative_path" : "test-segment",
  "parent_path" : "/infra/segments/test-segment",
  "marked_for_delete" : false,
  "_create_user" : "admin",
  "_create_time" : 1563378579272,
  "_last_modified_user" : "admin",
  "_last_modified_time" : 1563379491470,
  "_system_owned" : false,
  "_protection" : "NOT_PROTECTED",
  "_revision" : 2
}

This worked. The last test will be passing the wrong revision number.

curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra?enforce_revision_check=true' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":false,
         "Segment":{
            "type":"DISCONNECTED",
            "transport_zone_path":"/infra/sites/default/enforcement-points/default/transport-zones/605ea381-c34b-42f8-84dc-a1bf561e81d4",
            "resource_type":"Segment",
            "id":"test-segment",
            "display_name":"test-segment-changed-again-andagain",
            "_revision":"100"
         }
      }
   ]
}'
Enter host password for user 'admin':
{
  "httpStatus" : "BAD_REQUEST",
  "error_code" : 500157,
  "module_name" : "Policy",
  "error_message" : "Error during creating objects of type:Segment",
  "related_errors" : [ {
    "httpStatus" : "PRECONDITION_FAILED",
    "error_code" : 500071,
    "module_name" : "Policy",
    "error_message" : "The policy object path=[/infra/segments/test-segment], used in this operation, has different version (100) than the current system version (2). Fetch the latest copy of the object and retry operation."
  } ]

We received the error:

“The policy object path=[/infra/segments/test-segment], used in this operation, has different version (100) than the current system version (2). Fetch the latest copy of the object and retry operation”

That’s good, the revision check works.

The last thing to review is the removal of an object through the PATCH API. To delete an object, it is necessary to leverage the property “marked_for_delete” and set it to “true”.  The marked_for_delete” property must be configured in the wrapper object, in our case of type “ChildSegement” and not in the object itself. The object and all its children will be deleted. Let’s see how it works in our segment example.

~$ curl --user admin -H 'Content-Type: application/json' --request PATCH  'https://nsxmgr-01a/policy/api/v1/infra' -k -d '{
   "resource_type":"Infra",
   "children":[
      {
         "resource_type":"ChildSegment",
         "marked_for_delete":true,
         "Segment":{
            "resource_type":"Segment",
            "id":"test-segment"
         }
      }
   ]
}'
Enter host password for user 'admin':
~$
~$ curl --user admin  --request GET  'https://nsxmgr-01a/policy/api/v1/infra/segments/test-segment' -k
Enter host password for user 'admin':
{
  "httpStatus" : "NOT_FOUND",
  "error_code" : 600,
  "module_name" : "common-services",
  "error_message" : "The requested object : /infra/segments/test-segment could not be found. Object identifiers are case sensitive."
}~$

Our test segment has been successfully removed.

Conclusion

We reviewed the basics of interacting with the NSX-T Policy API hierarchically. I hope this will help you kick start your journey to automate NSX via this new tool. The NSX Policy API is extremely powerful, but it also has some quirks to be aware of to leverage it at its full potentials.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s