JavaScript
Expanding
  • Introduction
  • Ansible and ACI
  • Dev/Ops
  • Cisco ACI API
  • Python Basics
  • ReST w/ Python Requests
  • Javascript
  • Web Application
  • Finished
  • References

As you continue to develop the application example in JavaScript, we want to incorporate how to look at classes and their children. In the case of the fvCEp class, we can perform a class query and request the children of that object.

Looking at the children of fvCEp is useful, because this is where the location of the endpoint in the fabric can be found. Specifically, the child object we're looking for is fvRsCEpToPathEp. What we need to do perform a class query for fvCEp and requesting its children objects be included in the response.

If you remember this table from the API explanation section, we have various ways to query and filter the ACI fabric. In particular, we want the fvCEp object AND it's children. If you only use query-target=children, you would get the children of the object but not the object itself.


Filter Type Syntax Description
query-target {self | children | subtree} Define the scope of a query
target-subtree-class class name Respond-only elements including the specified class
query-target-filter filter expressions Respond-only elements matching conditions
rsp-subtree {no | children | full} Specifies child object level included in the response
rsp-subtree-class class name Respond only specified classes
rsp-subtree-filter filter expressions Respond only classes matching conditions
rsp-subtree-include {faults | health :stats :…} Request additional objects
order-by classname.property | {asc | desc} Sort the response based on the property values

Using the rsp-subtree query parameter returns the object and it's children.

You might be asking, "What objects are the children of the fvCEp class?" For this, we can reference the ACI API documentation (you will see this in the lab reference section) in the APIC. Or if you query the object it will show you all that are configured.

  • fv:CEp
    • dhcp:Ep
    • fault:Counts
    • fv:Ip
    • fv:PathEp
    • fv:PrimaryEncap
    • fv:RsCEpToPathEp
    • fv:RsHyper
    • fv:RsNic
    • fv:RsToNic
    • fv:RsToVm
    • fv:RsVm
    • fv:RtDestToVPort
    • fv:RtFromEp
    • fv:RtFromEpForEpToEpg
    • fv:RtSrcToVPort
    • fv:RtToEp
    • fv:RtToEpForEpToEp
    • fv:RtToEpForEpgToEp
    • fv:RtTrEpDst
    • fv:RtTrEpSrc
    • health:Inst

If you were to use POSTMAN to perform a query for the fvCEP class, you will notice not all these objects are returned back to you. This is because relationships are built as needed by the MIT based on the device types, interactions and other factors. For example one child of the fvCEp is fvIp, which only applies if this is a Layer 3 learned endpoint.

For your code to request the children of fvCEp we have to pass the correct parameter in the URL request within the classQuery()function.

Step 1 - Update classQuery() to add URL parameters

Add the filter argument to the classQuery()function, and build a conditional statement to determine if a query string should be used in the request's URL. Then, expand the URL to use the value of queryString.


var creds = {
  url: 'https://10.0.226.41',
  name: 'aciproglab01',
  pwd: 'cisco.123',
  token: '',
  urlToken: ''
}

var endpoints = {};

function classQuery(classname, filter="") {
  queryString = "";
  if (filter == "children") {
    queryString = "?rsp-subtree=children";
  }

  return $.ajax({
    url: creds.url + '/api/node/class/' + classname + '.json' + queryString,
    headers: {
      'DevCookie': creds.token,
      'APIC-challenge': creds.urlToken,
      'Content-Type': 'application/json'
    }
  });
};

function login() {
  return $.ajax({
    type: 'POST',
    url: creds.url + '/api/aaaLogin.json?gui-token-request=yes',
    data: JSON.stringify({
      aaaUser: {
        attributes: {
          name: creds.name,
          pwd: creds.pwd
        }
      }
    }),
  });
};

function buildTableData(aci_endpoint_data) {
  table_data = []

  $.each(aci_endpoint_data['imdata'], function (i, endpoint) {
    row = [ endpoint.fvCEp.attributes.mac, endpoint.fvCEp.attributes.ip ];
    table_data.push(row);
  });

  return table_data;
}

$( document ).ready(function() {
  login().then(function (data) {
    var attrs = data['imdata'][0]['aaaLogin']['attributes'];
    creds.token = attrs['token'];
    creds.urlToken = attrs['urlToken'];

    classQuery('fvCEp').then(buildTableData).then(function(table_data){
      $("#endpoint-table").DataTable({
        data: table_data,
        columns: [
          {title: "MAC Address"},
          {title: "IP Address"}
        ]
      })
    });
  });
});

Alert

There are likely better ways to accomplish this, but this way allows for simplifcation and saving of time during the lab.

Step 2 - Updating buildTableData() to include Endpoint Location

The original buildTableData() function needs to be updated. We have to iterate through the returned child objects of the original object fvCEp to find a specific object which contains the location (path). This object is called fvRsCEpToPathEp, and is the Endpoint Path object associated with the endpoint.

This is another area where optimization could be accomplished if there was more time in the lab. The request to the APIC could be constructed to return only specific children of an object chidren. Meaning the query could be constructed as /api/node/class/fvCEp.json?rsp-subtree=children&rsp-subtree-class=RsCEpToPathEp. This will cause the APIC to return only the children of fvCEp which are of type RsCEpToPathEp.

Below is an example of iterative code which extracts a particular class from returned object data.


var creds = {
  url: 'https://10.0.226.41',
  name: 'aciproglab01',
  pwd: 'cisco.123',
  token: '',
  urlToken: ''
}

var endpoints = {};

function classQuery(classname, filter="") {
  queryString = "";
  if (filter == "children") {
    queryString = "?rsp-subtree=children";
  }

  return $.ajax({
    url: creds.url + '/api/node/class/' + classname + '.json' + queryString,
    headers: {
      'DevCookie': creds.token,
      'APIC-challenge': creds.urlToken,
      'Content-Type': 'application/json'
    }
  });
};

function login() {
  return $.ajax({
    type: 'POST',
    url: creds.url + '/api/aaaLogin.json?gui-token-request=yes',
    data: JSON.stringify({
      aaaUser: {
        attributes: {
          name: creds.name,
          pwd: creds.pwd
        }
      }
    }),
  });
};

function buildTableData(aci_endpoint_data) {
  regex =  /tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)/
  table_data = []
  $.each(aci_endpoint_data['imdata'], function (i, endpoint) {  // for each endpoint in the data
    var line_dn = regex.exec(endpoint.fvCEp.attributes.dn)
    if (line_dn) {
      var fabric_location = ""
      var ip_addr = ""
      var tenant = line_dn[1]
      var app_profile = line_dn[2]
      var epg = line_dn[3]
      $.each(endpoint.fvCEp.children, function (x, endpoint_child){
        // This JavaScript code simply takes each object of the return data from the APIC and is grabing the 
        // children objects and processing them as the variable endpoint_child
        if (endpoint_child.fvRsCEpToPathEp) {  // check if there's a path attribute
          fabric_location = endpoint_child.fvRsCEpToPathEp.attributes.tDn;  // if so, set the location
        }                                                                   // from the child object (fvRsCEpToPathEp)
        if (endpoint_child.fvIp) { // check if there is an IP address object associated to the endpoint
          ip_addr = endpoint_child.fvIp.attributes.addr;
        }
      });
      row = [ endpoint.fvCEp.attributes.mac, ip_addr, tenant, app_profile, epg, fabric_location ];  // build a row containing the endpoint's IP and MAC
      table_data.push(row);  // then add this entry to the end of the array
    };
    });

  return table_data;
}

$( document ).ready(function() {
  login().then(function (data) {
    var attrs = data['imdata'][0]['aaaLogin']['attributes'];
    creds.token = attrs['token'];
    creds.urlToken = attrs['urlToken'];

    classQuery('fvCEp').then(buildTableData).then(function(table_data){
      $("#endpoint-table").DataTable({
        data: table_data,
        columns: [
          {title: "MAC Address"},
          {title: "IP Address"}
        ]
      })
    });
  });
});

Step 3 - Update classQuery() call and DataTable column fields

We can't forget to modify the classQuery() call in document.ready() to send the children parameter and update the DataTable column format with our third column.


var creds = {
  url: 'https://10.0.226.41',
  name: 'aciproglab01',
  pwd: 'cisco.123',
  token: '',
  urlToken: ''
}

var endpoints = {};

function classQuery(classname, filter="") {
  queryString = "";
  if (filter == "children") {
    queryString = "?rsp-subtree=children";
  }

  return $.ajax({
    url: creds.url + '/api/node/class/' + classname + '.json' + queryString,
    headers: {
      'DevCookie': creds.token,
      'APIC-challenge': creds.urlToken,
      'Content-Type': 'application/json'
    }
  });
};

function login() {
  return $.ajax({
    type: 'POST',
    url: creds.url + '/api/aaaLogin.json?gui-token-request=yes',
    data: JSON.stringify({
      aaaUser: {
        attributes: {
          name: creds.name,
          pwd: creds.pwd
        }
      }
    }),
  });
};

function buildTableData(aci_endpoint_data) {
  regex =  /tn-(\w*)\/ap-(\w*)\/epg-(\w*)\/cep-(\w*:\w*:\w*:\w*:\w*:\w*)/
  table_data = []
  $.each(aci_endpoint_data['imdata'], function (i, endpoint) {  // for each endpoint in the data
    var line_dn = regex.exec(endpoint.fvCEp.attributes.dn)
    if (line_dn) {
      var fabric_location = ""
      var ip_addr = ""
      var tenant = line_dn[1]
      var app_profile = line_dn[2]
      var epg = line_dn[3]
      $.each(endpoint.fvCEp.children, function (x, endpoint_child){
        // This JavaScript code simply takes each object of the return data from the APIC and is grabing the 
        // children objects and processing them as the variable endpoint_child
        if (endpoint_child.fvRsCEpToPathEp) {  // check if there's a path attribute
          fabric_location = endpoint_child.fvRsCEpToPathEp.attributes.tDn;  // if so, set the location
        }                                                                   // from the child object (fvRsCEpToPathEp)
        if (endpoint_child.fvIp) { // check if there is an IP address object associated to the endpoint
          ip_addr = endpoint_child.fvIp.attributes.addr;
        }
      });
      row = [ endpoint.fvCEp.attributes.mac, ip_addr, tenant, app_profile, epg, fabric_location ];  // build a row containing the endpoint's IP and MAC
      table_data.push(row);  // then add this entry to the end of the array
    };
    });

  return table_data;
}

$( document ).ready(function() {
  login().then(function (data) {
    var attrs = data['imdata'][0]['aaaLogin']['attributes'];
    creds.token = attrs['token'];
    creds.urlToken = attrs['urlToken'];

    classQuery('fvCEp','children').then(buildTableData).then(function(table_data){
      $("#endpoint-table").DataTable({
        data: table_data,
        columns: [
          {title: "MAC Address"},
          {title: "IP Address"},
          {title: "Tenant"},
          {title: "App Profile"},
          {title: "EPG"},
          {title: "Location"}
        ]
      })
    });
  });
});

The updated table should now contain location of the endpoints.