Using Resource and Collection API in commands

contrail-api-cli provides a minimal API to make common REST operations (GET/PUT/POST/DELETE) on API resources.

To illustrate the usage of this API we will write a command to provision vrouters in the API. Contrail already provides a provisioning script for that but it is a good example for playing with contrail-api-cli API.

In this tutorial we will create three commands. Of course we would like to provision new vrouters but we also would like to list them easily and to delete them if needed.

Resource and Collection objects

Resource and Collection objects are available in the contrail_api_cli.resource module. A collection represents a list of resources in the API server. Collection is an iterable and Resource is a dict that can seemlessly be converted to the JSON representation of the resource.

For more details check the API page.

virtual-router resource

contrail-api-cli API is a thin wrapper around the JSON representation of resources or collections. Therefore we have to know a little about the resource we would like to create. With the CLI we can inspect an existing virtual router to see what the JSON representation look like:

$ contrail-api-cli shell
admin@localhost /> ls virtual-router
virtual-router/7c5d76a8-7ca3-43a6-bee6-154f84db977e
virtual-router/37fc1054-0d25-4a2d-aa42-9e202b5dfa3a
admin@localhost /> cat virtual-router/37fc1054-0d25-4a2d-aa42-9e202b5dfa3a
{
  "display_name": "node-2",
  "fq_name": [
    "default-global-system-config",
    "node-2"
  ],
  "href": "http://localhost:8082/virtual-router/37fc1054-0d25-4a2d-aa42-9e202b5dfa3a",
  "id_perms": {
    [...]
  },
  "name": "node-2",
  "parent_href": "http://localhost:8082/global-system-config/69e8ab08-f1d3-474d-8c7f-c540410fa025",
  "parent_type": "global-system-config",
  "parent_uuid": "69e8ab08-f1d3-474d-8c7f-c540410fa025",
  "uuid": "37fc1054-0d25-4a2d-aa42-9e202b5dfa3a",
  "virtual_machine_refs": [
    [...]
  ],
  "virtual_router_ip_address": "10.11.0.56"
}

So some useful information would be the virtual_router_ip_address property. The virtual router hostname can be found in the fq_name.

List command

The list command make use of the Collection object which represent the collection of resources of a specific type.

Basically doing ls / in the CLI gives you all collections:

from contrail_api_cli.command import Command
from contrail_api_cli.resource import Collection
from contrail_api_cli.utils import printo

class ListVRouter(Command):
    description = 'List vrouters'

    def __call__(self):
        vrouters = Collection('virtual-router', fetch=True)
        for vrouter in vrouters:
            vrouter.fetch()
            printo('%s: %s', (vrouter.fq_name[-1],
                              vrouter['virtual_router_ip_address']))

We instanciate a Collection of type ‘virtual-router’ to get all the vrouters from the API. The fetch argument will actually fetch the collection data from the API server immediately. The method Collection.fetch() can be used to sync the object later with the server.

The Collection object is iterable like a list so we iterate the collection and fetch the details of each resource to get the details. For each vrouter we print the name and the IP of the vrouter. Resource is basically a dict wrapper so its properties are accessible directly.

printo() is used instead of print() to handle properly terminal encoding with python2 and python3.

Note

With Contrail >= 3.0 we can make use of the fields API on Collection objects. Instead of making a GET request for each resource to get its details we can specify the supplementary fields to get in the Collection:

vrouter = Collection('virtual-router',
                     fields=['virtual_router_ip_address'],
                     fetch=True)
for vrouter in vrouters:
    printo(vrouter['virtual_router_ip_address'])

In this case only one GET request is made.

Add command

To add a virtual-router we need at least a name and an IP address. The type is optionnal and is usually not defined but we add an option for it just in case:

from contrail_api_cli.command import Command, Arg, Option
from contrail_api_cli.resource import Resource

class AddVRouter(Command):
    description = 'Add vrouter'
    vrouter_name = Arg(help='Hostname of compute node')
    vrouter_ip = Option(help='IP of compute node',
                        required=True)
    vrouter_type = Option(help='vrouter type',
                          choices=['tor-service-mode', 'embedded'],
                          default=None)

    def __call__(self, vrouter_ip=None, vrouter_name=None, vrouter_type=None):
        global_config = Resource('global-system-config',
                                 fq_name='default-global-system-config')
        vrouter = Resource('virtual-router',
                           fq_name='default-global-system-config:%s' % vrouter_name,
                           parent=global_config,
                           virtual_router_ip_address=vrouter_ip)
        if vrouter_type:
            vrouter['virtual_router_type'] = [vrouter_type]
        vrouter.save()

To create the vrouter resource we are making use of the Resource class. To create a Resource we need to pass the type (‘virtual-router’), an fq_name, and a parent resource.

Note

Resource is a subclass of python UserDict. Any supplementary kwarg passed to the constructor is added in the dict. In our example passing virtual_router_ip_address to the constructor is the same as:

vrouter = Resource('virtual-router',
                   fq_name='default-global-system-config:%s' % vrouter_name,
                   parent=global_config)
vouter['virtual_router_ip_address'] = vrouter_ip

An existing parent resource must be defined in order to create the resource. In our case the parent is the ‘default-global-system-config’. Passing a parent resource will populate the parent_type and parent_uuid keys of the Resource.

Finally we save the resource to the API server using the Resource.save() method. This method convert the object to JSON and send the data to the API server in a POST request since the resource doesn’t exists on the server. It is possible to update an existing resource using the same method. In the update case a PUT request is made.

Del command

The del command is straith forward. We need to get the resource by it’s name and try to delete it with the Resource.delete() method:

class DelVRouter(Command):
    description = 'Remove vrouter'
    vrouter_name = Arg(help='Hostname of compute node')

    def __call__(self, vrouter_name=None):
        vrouter = Resource('virtual-router',
                           fq_name='default-global-system-config:%s' % vrouter_name,
                           check=True)
        vrouter.delete()

The check param makes sure that the resource exists on the API server. If not ResourceNotFound is raised and catched automatically by the cli.

Note

Resource.check() only validate the fq_name of the resource whereas Resource.fetch() will try to get all the details of the resource. Both methods can raise ResourceNotFound. Using check=True or fetch=True when initializing a Collection is the same as using theses methods.