Python Library API

RacksDB provides a Python library API that can be used to fully integrate the database with your tools and software stack. This document explains the overall logic of this library with some examples.

Module

RacksDB provides one main module racksdb with essentially one class RacksDB. This should be all you need to import to interact with RacksDB.

>>> from racksdb import RacksDB
>>> RacksDB
<class 'racksdb.RacksDB'>

Load database

The RacksDB class has one static method load() to load the database:

>>> from racksdb import RacksDB
>>> db = RacksDB.load()

This method returns an instanciated RacksDB object:

>>> type(db)
<class 'racksdb.RacksDB'>

This object can then be used to interact with database content.

Without more arguments, the load() method loads the database, schema and extensions at their defaults paths. Alternative paths can be specified in arguments:

>>> from racksdb import RacksDB
>>> db = RacksDB.load(
...    schema='/usr/share/racksdb/schema.yml',
...    ext='/etc/racksdb/extensions.yml',
...    db='/var/lib/racksdb',
... )

All arguments are optional and can be specified individually. The method accepts both strings and standards Paths objects as arguments.

The default paths are defined as class attributes:

>>> RacksDB.DEFAULT_DB
'/var/lib/racksdb'
>>> RacksDB.DEFAULT_EXT
'/etc/racksdb/extensions.yml'
>>> RacksDB.DEFAULT_SCHEMA
'/usr/share/racksdb/schema.yml'

Two exceptions can be raised by the load() method:

  • racksdb.generic.errors.DBSchemaError exception in case of error with the schema.

  • racksdb.generic.errors.DBFormatError exception when an error is encountered with the database content.

The exceptions are raised with a detailed description of the error.

Generic Object Model

The database is essentially composed of 3 types of Python objects, detailled in the following subsections.

DBObject

RacksDB database structure is composed of many objects. Each type of object is represented with its own specific class, inherited from the abstract DBObject class.

Example

The Types object is represented with the RacksDBTypes class which inherits from the DBObject class:

>>> type(db.types)
<class 'racksdb.generic.db.RacksDBTypes'>
>>> from racksdb.generic.db import DBObject
>>> isinstance(db.types, DBObject)
True

Every objects properties (including automatic back references) defined in database structure can be directly accessed through the corresponding attribute.

Example

The RacksDBTypes object has attributes nodes, network, storage and racks corresponding to the properties of Types object:

>>> for prop in ['nodes', 'network', 'storage', 'racks']:
...   hasattr(db.types, prop)
...
True
True
True
True

The type of the attributes depends on the attributes and the data type of the corresponding property:

  • For sequences, the attribute is either a DBList or a DBDict object, depending on the presence of a key property. Please refer to the following dedicated sections for more details.

  • For native types, the attribute has the corresponding Python native type (ex: str or float).

    Examples

    The type of model property of NodeType object is a string, this is converted to a native Python str:

    >>> type(db.types.nodes.first().model)
    <class 'str'>

    The type of height property of RackType object is an integer, this is converted to a native Python int:

    >>> type(db.types.racks.first().height)
    <class 'int'>
  • For references and back references, the attribute is a reference to the corresponding object in the database.

    Examples

    The type of type property of Node object is a reference to NodeType object, the attribute is a reference the corresponding RacksDBNodeType object in database:

    >>> type(db.infrastructures.first().layout.first().nodes.first().type)
    <class 'racksdb.generic.db.RacksDBNodeType'>

    There is a back reference named datacenter on DatacenterRoom object, the attribute is a reference to the parent RacksDBDatacenter object:

    >>> db.datacenters.first().rooms.first().datacenter
    <racksdb.generic.db.RacksDBDatacenter object at 0x7f0aaf13db10>
    >>> db.datacenters.first() == db.datacenters.first().rooms.first().datacenter
    True
  • For defined types, the attribute has the resulting type of the defined type.

    Examples

    The depth property of DatacenterRoomDimensions object is a ~dimension defined type whose resulting type is an integer. The attribute is a Python native int:

    >>> type(db.datacenters.first().rooms.first().dimensions.depth)
    <class 'int'>

    The width property of NetworkEquipmentType object is a ~rack_width defined type whose resulting type is a float. The attribute is a Python native float:

    >>> type(db.types.network.first().width)
    <class 'float'>

The RacksDB object, representing the root of the whole loaded database is also a DBObject. This can be verified with this:

>>> isinstance(db, DBObject)
True

For this reaison, it has the types, datacenters and infrastructures attributes corresponding to the properties of the database structure root.

DBList

The DBList class extends standard Python list type. It is generally used to represent values of properties with sequence attribute (except for objects with key property represented by DBDict).

Example

The list of NodeTypeNetif objects holded by the netifs property of NodeType objects is represented by a DBList:

>>> type(db.types.nodes.first().netifs)
<class 'racksdb.generic.db.DBList'>

Compared to standard Python list type, DBList class notably adds support of expandable objects. Typically, iterating over a DBList generates all objects in range of expandable objects.

Example

The list of Rack objects holded by the racks property of RacksRow is a DBList. Even if the list actually contains a folded range of racks, iterating over the DBList generates all expanded members of the range:

>>> racks = db.datacenters["paris"].rooms["noisy"].rows.first().racks
>>> type(racks)
<class 'racksdb.generic.db.DBList'>
>>> len(racks)
1
>>> racks[0].name
R1-A[01-10]
>>> for rack in racks:
...   print(rack.name)
...
R1-A01
R1-A02
R1-A03
R1-A04
R1-A05
R1-A06
R1-A07
R1-A08
R1-A09
R1-A10

Also, the len() function on a DBList returns the number of potentially expanded objects, not the number of actual members of the list.

Example

Considering the previous example with the racks property of RacksRow, the length of DBList as reported by len() is different of the actual number of values in the list:

>>> racks[0].name
R1-A[01-10]
>>> type(racks)
<class 'racksdb.generic.db.DBList'>
>>> purelist = racks.copy()
>>> type(purelist)
<class 'list'>
>>> len(purelist)
1
>>> len(racks)
10

Methods

The DBList objects provide 2 methods:

  • filter() method returns another DBList with a subset of all objects contained in the list that satisfy the criteria in arguments. This method must be supported by the specialized class to work properly or no filtering is performed. Please refer to the Classes Specializations section to discover the classes supporting filtering.

  • itervalues() method is a generator to iterate over folded values of the DBList without triggering automatic expansion.

    Example

    Considering the previous example with the list of Rack, iterating over the DBList with itervalues() method generates only one folded object:

    >>> racks = db.datacenters["paris"].rooms["noisy"].rows.first().racks
    >>> for rack in racks.itervalues():
    ...   print(rack.name)
    ...
    R1-A[01-10]

DBDict

The DBDict class extends standard Python dict. It is generally used to represent values of properties with sequence attribute whose contained objects have key property. The values of the key properties are the keys of the DBDict.

Example

The list of Datacenter objects holded by the datacenters property of database root object is represented by a DBDict:

>>> type(db.datacenters)
<class 'racksdb.generic.db.DBDict'>

Compared to standard Python dictionnaries, DBDict class notably adds support of expandable objects. Typically, it is possible to use the subscript operator (ie. []) on any member of a range, even when this member is not a key of the dictionnary.

Example

The list of Node objects holded by the nodes property of RacksDB class specialization is a DBDict. Even if mecn0002 is not in its keys, DBDict is capable to figure out it is a member the range mecn[0001-0040] and returns an instance of the appropriate object:

>>> type(db.nodes)
<class 'racksdb.generic.db.DBDict'>
>>> db.nodes.keys()
dict_keys([mecn[0001-0040], mecn0200, mecn[0041-0060], mecn[0061-0116], mesrv[0001-0004]])
>>> db.nodes['mecn0002']
<racksdb.generic.db.RacksDBNode object at 0x7fd763b40f7>
>>> db.nodes['mecn0002'].type.id
'sm220bt'

While standard Python dict iterates over the list of keys, DBDict class iterates over the list of potentially expanded values.

Example

Considering the previous example with the nodes property of RacksDB class specialization, iterations over this DBDict generate the list of all expanded objects:

>>> db.nodes.keys()
dict_keys([mecn[0001-0040], mecn0200, mecn[0041-0060], mecn[0061-0116], mesrv[0001-0004]])
>>> for node in db.nodes:
...   print(node.name)
...
mecn0001
mecn0002
mecn0003
mecn0004
mecn0005
…

Also, the len() function on a DBDict returns the number of potentially expanded objects, not the number of actual members of the dictionnary.

Example

Considering the previous example with the nodes property of RacksDB class specialization, the number of DBDict members as reported by len() is different of the number of keys:

>>> len(db.nodes.keys())
5
>>> len(db.nodes)
121

Methods

The DBDict objects provide 2 methods:

  • filter() method returns another DBDict with a subset of all objects contained in the dictionnary that satisfy the criteria in arguments. This method must be supported by the specialized class to work properly or no filtering is performed. Please refer to the Classes Specializations section to discover the classes supporting filtering.

  • first() method returns the first (potentially expanded) object contained in the DBDict object.

    Example

    Get the first node type:

    >>> type(db.types.nodes.first())
    <class 'racksdb.generic.db.RacksDBNodeType'>
    >>> db.types.nodes.first().id
    'sm220bt'

Classes Specializations

Some specialized DBObject subclasses provide additional methods and attributes, either for conveniency or to provide additional features. These specializations are documented for each class.

RacksDB

Attributes

The RacksDB class provides the following specialized attribute:

  • nodes: the DBDict object containing all nodes of all infrastructures defined in RacksDB database.

    Example

    Count the total number of nodes in the database:

    >>> len(db.nodes)
    196
  • racks: the DBList object containing all racks of all datacenter rooms defined in RacksDB database.

    Examples

    Count the total number of racks in the database:

    >>> len(db.racks)
    53

    Get the name of the first rack:

    >>> db.racks[0].name
    'R1-A01'

RacksDBDatacenter

Filtering

The RacksDBDatacenter class provides an implementation of the _filter() method, for easy filtering of DBList and DBDict containing RacksDBDatacenter objects. It accepts the following arguments:

  • name: the name of an infrastructure

  • tags: a list of tags

A datacenter is selected only if it matches all criteria. If multiple tags are provided, only the datacenters for which all the tags are applied are selected.

Examples

Get the datacenters named paris:

>>> db.datacenters.filter(name='paris')

Get all datacenters with tag tier2:

>>> db.datacenters.filter(tags=['tier2'])

RacksDBInfrastructure

Attributes

The RacksDBInfrastructure class provides the following specialized attribute:

  • nodes: the DBDict object containing all nodes of all layout parts of the infrastructure.

    Example

    Count the total number of nodes in the tiger infrastructure:

    >>> len(db.infrastructures['tiger'].nodes)
    75

Filtering

The RacksDBInfrastructure class provides an implementation of the _filter() method, for easy filtering of DBList and DBDict containing RacksDBInfrastructure objects. It accepts the following arguments:

  • name: the name of an infrastructure

  • tags: a list of tags

An infrastructure is selected only if it matches all criteria. If multiple tags are provided, only the infrastructures for which all the tags are applied are selected.

Examples

Get the infrastructure named mercury:

>>> db.infrastructures.filter(name='mercury')

Get all infrastructures with tag cluster:

>>> db.infrastructures.filter(tags=['cluster'])

RacksDBNode

Filtering

The RacksDBNode class provides an implementation of the _filter() method, for easy filtering of DBList and DBDict containing RacksDBNode objects. It accepts the following arguments:

  • infrastructure: the name of an infrastructure

  • name: the name of a node

  • tags: a list of tags

A node is selected only if it matches all criteria. If multiple tags are provided, only the nodes for which all the tags are applied are selected.

Examples

Get all nodes named cn001:

>>> db.nodes.filter(name='cn001')

Get all nodes of infrastructure tiger with tag compute:

>>> db.nodes.filter(infrastructure='tiger', tags=['compute'])

RacksDBRack

Attributes

The RacksDBRack class provides the following specialized attribute:

  • nodes: DBList object containing all nodes located in this rack.

    This attribute is also a computed property which means it is also visible in RacksDB search dumps.
    Example

    Print the list of nodes in every racks located in row R1 of room noisy in datacenter paris:

    >>> for rack in db.datacenters["paris"].rooms["noisy"].rows["R1"].racks:
    ...     print(f"{rack.name}: {[nodes.name for nodes in rack.nodes]}")
    ...
    R1-A01: ['mecn0001', 'mecn0002', 'mecn0003', 'mecn0004', …]
    R1-A02: ['mecn0061', 'mecn0062', 'mecn0063', 'mecn0064', …]
    R1-A03: []
    R1-A04: []
    R1-A05: []
    R1-A06: []
    R1-A07: []
    R1-A08: []
    R1-A09: []
    R1-A10: []
  • fillrate: a float representing the filling rate of the rack between 0 (empty) and 1 (full).

    This attribute is also a computed property which means it is also visible in RacksDB search dumps.
    Example

    Print the filling rate of every racks located in row R1 of room noisy in datacenter paris:

    >>> for rack in db.datacenters["paris"].rooms["noisy"].rows["R1"].racks:
    ...   print(f"{rack.name}: {rack.fillrate*100:.2f}%")
    ...
    R1-A01: 97.62%
    R1-A02: 95.24%
    R1-A03: 0.00%
    R1-A04: 0.00%
    R1-A05: 0.00%
    R1-A06: 0.00%
    R1-A07: 0.00%
    R1-A08: 0.00%
    R1-A09: 0.00%
    R1-A10: 0.00%

Filtering

The RacksDBRack class provides an implementation of the _filter() method, for easy filtering of DBList and DBDict containing RacksDBRack objects. It accepts the following arguments:

  • name: the name of a rack

A rack is selected only if it matches the name criteria.

Example

Get rack named R1-A02:

>>> db.racks.filter(name='R1-A02')

RacksDBRacksRow

Attributes

The RacksDBRacksRow class provides the following specialized attribute:

  • nbracks: an integer representing the number of racks in the row.

    This attribute is also a computed property which means it is also visible in RacksDB search dumps.
    Example

    Print the number of racks in every rows of room noisy in datacenter paris:

    >>> for row in db.datacenters["paris"].rooms["noisy"].rows:
    ...     print(f"{row.name}: {row.nbracks} rack(s)")
    ...
    R1: 10 rack(s)
    R2: 10 rack(s)
    R3: 9 rack(s)
    R4: 6 rack(s)
    R5: 6 rack(s)
    R6: 6 rack(s)
    R7: 6 rack(s)

Database content

By combining the explanations about the Generic Object model, the database structure and the available classes specializations, you get all theoretical information required to explore database with RacksDB Python library. This section provide some practical examples to illustrate the principles.

Types

The equipments types are available through the types attribute of RacksDB object:

>>> db.types
<racksdb.generic.db.RacksDBTypes object at 0x7f5660345810>
Examples

Print the height in meters of all types of racks:

>>> for rack in db.types.racks:
...   print(f"{rack.id}: {rack.height/10**3}m")
...
standard: 1.867m
half: 1.198m

Print the list of node types ID and models:

>>> for nodetype in db.types.nodes:
...   print(f"{nodetype.id}: {nodetype.model}")
...
sm220bt: SuperMicro A+ Server 2124BT-HTR
sm610u: SuperMicro Ultra SYS-610U-TNR
hpesyn480: HPE Synergy 480 Gen10 Compute Module
dellr550: Dell PowerEdge R550

Print the number of network interfaces, with their bandwidth in Gb/s, for each network equipment type:

>>> for equipment in db.types.network:
...   print(f"{equipment.model} :")
...   for netif in equipment.netifs:
...     print(f" [{netif.type.upper()}] {netif.number}x{netif.bandwidth*8/10**9}Gb/s")
...
Cisco Catalyst 3650 switch :
 [ETHERNET] 48x1.0Gb/s

Print the raw capacity in TB of each storage equipment type:

>>> size = 0
>>> for equipment in db.types.storage:
...   for disk in equipment.disks:
...     size += disk.size * disk.number
...   print(f"{equipment.model}: {size/1024**4}TB")
...
QNAP TS-H1277XU-RP: 48.0TB

Datacenters

Datacenters are available through the datacenters attribute of RacksDB object:

>>> db.datacenters
<racksdb.generic.db.DBList object at 0x7f5660344c10>
Examples

Get the name of the first datacenter:

>>> db.datacenters.first().name
'paris'

Get the list of all datacenters rooms:

>>> [room.name for datacenter in db.datacenters for room in datacenter.rooms]
['noisy']

Print the list of racks by room per datacenter:

>>> for datacenter in db.datacenters:
...   for room in datacenter.rooms:
...       print(f"{datacenter.name}: {room.name}: "
...             f"racks: {[rack.name for row in room.rows for rack in row.racks]}")
...
paris: noisy: racks: ['R1-A01', 'R1-A02', 'R1-A03', … 'R7-A04', 'R7-A05', 'R7-A06']

Infrastructures

Infrastructures are available through the infrastructures attribute of RacksDB object:

>>> db.infrastructures
<racksdb.generic.db.DBList object at 0x7f5660346890>
Examples

Get the list of all infrastructure names:

>>> [infrastructure.name for infrastructure in db.infrastructures]
['tiger', 'mercury']

Print the list of nodes of the tiger infrastructure:

>>> for node in db.infrastructures['tiger'].nodes:
...     print(node.name)
...
cn001
cn002
cn003
…
cn226
cn227
cn228

Print the list of racks and datacenter where the tiger infrastructure is located:

>>> for part in db.infrastructures['tiger'].layout:
...     print(f"{part.rack.name} ({part.rack.datacenter.name})")
...
R01 (paris)
R02 (paris)

The list of tags applied to node srv001 of infrastructure tiger:

>>> db.infrastructures['tiger'].nodes['srv001'].tags
['compute', 'servers']

The name of nodes in infrastructure tiger with tag servers:

>>> for node in db.infrastructures['tiger'].nodes.filter(tags=['servers']):
...   node.name
...
'srv001'
'srv002'

The set of racks where are located all nodes with tag compute:

>>> set([node.rack.name for node in db.nodes.filter(tags=['compute'])])
{'R02', 'R1-A01', 'R01', 'R1-A02'}

The model names of nodes with tag servers:

>>> set([node.type.model for node in db.nodes.filter(tags=['servers'])])
{'Dell PowerEdge R550'}