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.
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.
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"}
]
})
});
});
});
There are likely better ways to accomplish this, but this way allows for simplifcation and saving of time during the lab.
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"}
]
})
});
});
});
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.