Python Library for SOLIDserver

Python Library for EfficientIP SOLIDserver

In the infrastructure as code and orchestration spaces, engineers frequently use programmatic languages such as Python. When using higher level automation tools like Ansible or Puppet the underlying language used is mostly one which is well known Python, Ruby or Golang. Communication with the network and system components needs to be performed with simple methods, REST APIs are very well designed for this purpose, they are easy to manipulate, actions are atomic, require no complex session to set up and all languages are able to easily call web services. This is why EfficientIP has released its Python library for its SOLIDserver DDI solution, offering an easy way to integrate into the enterprise ecosystem mostly all the actions concerned with IP addressing plan, DNS, DHCP, devices and application traffic routing management.

The SOLIDserver DDI product is a complete repository of the IP information contained in the network, on-premise and in the cloud. It not only manages the IP plan, but also associated mandatory services like DNS and DHCP. As a central repository, it offers an easy way to obtain valid and up-to-date information without requiring access to a manually maintained database or complex querying of multiple services or devices. For real time or batch processes, using the DDI as the IP Golden Record is the key to powerful operations on an accurate inventory.

Access to the SOLIDserver can be performed through the graphical interface, but for automation purposes, REST API is the best method. The full set of SOLIDserver APIs can be used by any external tool having authorizations attached to the account. Communication is secured with TLS, a server certificate and basic authentication in the HTTP flow. The full information set stored in the database can be queried through the API on various types of objects like subnets, address, or DNS records. Any meta-data associated with objects are also available for performing more powerful actions and leverage embedded/specialized sub-orchestrations for maintaining consistency between IPAM/DNS and DHCP components.

In a previous blog, we presented an integration proposed by EfficientIP with a Terraform provider. Now let’s focus on the Python library allowing easy access to the SOLIDserver REST API. This project is offered via EfficientIP’s global open source approach. In addition to contributing widely to large projects, we like to propose ecosystem integration and smaller projects for partners and customers to develop their knowledge and bring value to the network integration.

The Python library is available at the EfficientIP GitLab source repository, for anyone wishing to automate their solution or environment with a leading DDI solution. It is also open to community contribution, bug fix and ideas for improvements. As we use it massively at EfficientIP for test, demo and automation, we maintain it on a regular basis. Contributions from the community are open, branching model follows gitflow principles and merge request should be proposed on the dev branch, a quality check is performed directly on GitLab using the CI/CD, mainly for Python style and coverage test using Python version 3.6 and 3.7. In addition to static tests, dynamic tests with an active SDS (SOLIDserver) are performed internally prior to merging with the master branch. We plan to automate this dynamic bench with a set of cloud instances that will be powered up only when necessary, thanks to our temporary license engine.

The library is composed of two flavors: the first one maps all the APIs in order to get a unified and simple way to call them, and the second more advanced (and still being improved) maps objects to Python classes in order to ease manipulation.

The following examples are really a starter kit for you to bootstrap your first Python script with SOLIDserver REST API. Many usages are possible, so don’t hesitate to participate within the community and tell us how it works for you.

Example 0 – set up the environment

from SOLIDserverRest import SOLIDserverRest

SDS_CON = SOLIDserverRest('192.168.56.254')
SDS_CON.set_ssl_verify(False)
SDS_CON.use_basicauth_sds(user='api', password='apipwd')

del(SDS_CON)

Here we do a simple and basic init with the standard library, specifying the IP address of the SOLIDserver management appliance and providing the credentials. The TLS certificate is not validated at connection time, avoiding having to provide the intermediate or CA certificate file (which is not recommended anyway).
The last line (deleting the object) is not mandatory, but allows a clean close of all network connections if required in the middle of the script.

Example 1 – get existing space from the SOLIDserver IPAM

def get_space(name):
    """get a space by its name from the SDS"""
    parameters = {
        "WHERE": "site_name='{}'".format(name),
        "limit": "1"
    }

    rest_answer = SDS_CON.query("ip_site_list", parameters)

    if rest_answer.status_code != 200:
        logging.error("cannot find space %s", name)
        return None

    rjson = json.loads(rest_answer.content)

    return {
        'type': 'space',
        'name': name,
        'is_default': rjson[0]['site_is_default'],
        'id': rjson[0]['site_id']
    }

space = get_space("Local")
print(space)

Here we introduce a simple way of interacting with the SOLIDserver through the API, a call to the API using the mapping interface. The function sets the parameters for the API call, in this case specifying the filter on the name of the space, and limiting the number of returns from the API call. Normally, we should not have more than one result since names are unique in the space list, but it is good practice to limit the return size and use pagination if large number of results are expected.

Then we call the ip_site_list API in order to query from the list of the spaces in the IPAM the one with the provided name. The result, if the status is correct, is converted from the JSON format in a Python dictionary format, easier to manipulate. Finally, we return a dictionary with specific values, here the name, the fact that this space is the default one (which is the case for the Local one) and its internal id. This id will be used as a context to limit further operations to this space only.

Result can be something like:

{'type': 'space', 'name': 'Local', 'is_default': '1', 'id': '2'}

Example 2 – get existing subnet from the SOLIDserver IPAM

def get_subnet_v4(name, space_id=None):
	"""get a subnet by its name from the SDS"""
	parameters = {
    	"WHERE": "subnet_name='{}' and is_terminal='1'".format(name),
    	"TAGS": "network.gateway”
	}

	if space_id is not None:
    	parameters['WHERE'] = parameters['WHERE'] + " and site_id='{}'".format(int(space_id))

	rest_answer = SDS_CON.query("ip_subnet_list", parameters)

	if rest_answer.status_code != 200:
    	logging.error("cannot find subnet %s", name)
    	return None

	rjson = json.loads(rest_answer.content)

	return {
    	  'type': 'terminal_subnet',
    	  'name': name,
    	  'addr': rjson[0]['start_hostaddr'],
    	  'cidr': 32-int(math.log(int(rjson[0]['subnet_size']), 2)),
    	  'gw': rjson[0]['tag_network_gateway'],
    	  'used_addresses': rjson[0]['subnet_ip_used_size'],
    	  'free_addresses': rjson[0]['subnet_ip_free_size'],
    	  'space': rjson[0]['site_id'],
    	  'id': rjson[0]['subnet_id']
	}

space = get_space("Local")
subnet = get_subnet_v4("lab42", space_id=space['id'])
print(subnet)

Using the same principle as for the space, here we look in the IPAM for a specific IPv4 subnet by its name. The result could look like:

{
 'type': 'terminal_subnet',
 'name': 'home',
 'addr': '192.168.16.0',
 'cidr': 24,
 'gw': '192.168.16.254',
 'used_addresses': '1',
 'free_addresses': '253',
 'space': '2',
 'id': '20'
}

Example 3 – get next free address in a subnet

In orchestration, we obviously need IP addresses to build new resources in the cloud like servers and VMs. With SOLIDserver, we start asking for a free address in a specific subnet or address pool, during resource creation this IP address could be affected. In this example, we ask for the next 5 addresses which are free in a subnet starting at the 100th position of the subnet we previously chose. In order to validate availability of the addresses, we have already assigned the address in the middle of the result (192.168.16.102).

def get_next_free_address(subnet_id, number=1, start_address=None):
	"""get a set of free address, by default one is returned"""

	parameters = {
    	  "subnet_id": str(subnet_id),
    	  "max_find": str(number),
    	  "begin_addr": "192.168.16.100",
	}

	if start_address is not None:
    	parameters['begin_addr'] = str(ipaddress.IPv4Address(start_address))

	rest_answer = SDS_CON.query("ip_address_find_free", parameters)

	if rest_answer.status_code != 200:
    	logging.error("cannot find subnet %s", name)
    	return None

	rjson = json.loads(rest_answer.content)

	result = {
    	   'type': 'free_ip_address',
    	   'available': len(rjson),
    	   'address': []
	}

	for address in rjson:
    	result['address'].append(address['hostaddr'])

	return result

ipstart = ipaddress.IPv4Address(subnet['addr'])+100
free_address = get_next_free_address(subnet['id'], 5, ipstart)
pprint.pprint(free_address)

Note: for IP address manipulation, the Python library ip address is really interesting

The result can look like:

{
 'type': 'free_ip_address',
 'available': 5,
 'address': ['192.168.16.100',
             '192.168.16.101',
             '192.168.16.103',
             '192.168.16.104',
             '192.168.16.105']
}

Example 4 – add a node in the IPAM

Now that we have found some free IP addresses, let’s reserve one and add a node in the IPAM.

def add_ip_address(ip, name, space_id):
	"""add an IP address and its name in the IPAM"""

	parameters = {
    	   "site_id": str(space_id),
    	   "hostaddr": str(ipaddress.IPv4Address(ip)),
    	   "name": str(name)
	}

	rest_answer = SDS_CON.query("ip_address_create", parameters)

	if rest_answer.status_code != 201:
    	logging.error("cannot add IP node %s", name)
    	return None

	rjson = json.loads(rest_answer.content)
    
	return {
    	   'type': 'add_ipv4_address',
    	   'name': str(name),
    	   'id': rjson[0]['ret_oid'],
	}

node = add_ip_address(free_address['address'][2],
                  	uuid.uuid4(),
                  	space['id'])
print(node)

The node is created using one of the addresses returned previously and provided with a random name (thanks to the uuid library). Note here that the subnet is not provided, only the space. The SOLIDserver will automatically find the appropriate subnet to store this node based on the IP address provided, which we think is pretty cool! The result of the command can look like:

{
 'type': 'add_ipv4_address',
 'name': '6951e073-6fa6-47e6-8cdb-dc1ae251f0b6',
 'id': '39'
}

The id returned here allows further modification if required.

Want to learn more or get a demo?

We're here to help! Let us know if you have questions on SOLIDserver DDI.

CONTACT US
Posted in:
5 December 2019 In the infrastructure as code and orchestration spaces, engineers frequently use programmatic langua...

EfficientIP