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/schemas/racksdb.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/schemas/racksdb.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.
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.
The RacksDBTypes
object has attributes nodes
, network
, storage
, misc
and racks
corresponding to the properties of
Types object:
>>> for prop in ['nodes', 'network', 'storage', 'misc', 'racks']:
... hasattr(db.types, prop)
...
True
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 aDBDict
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).
ExamplesThe 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.
ExamplesThe type of
type
property of Node object is a reference to NodeType object, the attribute is a reference the correspondingRacksDBNodeType
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 parentRacksDBDatacenter
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.
ExamplesThe
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
For this reaison, it has the |
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
).
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.
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.
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 anotherDBList
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 theDBList
without triggering automatic expansion.ExampleConsidering the previous example with the list of
Rack
, iterating over theDBList
withitervalues()
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
.
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.
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.
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.
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 anotherDBDict
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 theDBDict
object.ExampleGet 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
: theDBDict
object containing all nodes of all infrastructures defined in RacksDB database.ExampleCount the total number of nodes in the database:
>>> len(db.nodes) 196
-
racks
: theDBList
object containing all racks of all datacenter rooms defined in RacksDB database.ExamplesCount 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.
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
: theDBDict
object containing all nodes of all layout parts of the infrastructure.ExampleCount 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.
Get the infrastructure named mercury:
>>> db.infrastructures.filter(name='mercury')
Get all infrastructures with tag cluster:
>>> db.infrastructures.filter(tags=['cluster'])
RacksDBMiscEquipment
Filtering
The RacksDBNode
, RacksDBStorageEquipment
, RacksDBNetworkEquipment
and
RacksDBMiscEquipment
classes provide an implementation of the _filter()
method, for easy filtering of DBList
and DBDict
containing these objects. It accepts the following arguments:
-
infrastructure
: the name of an infrastructure -
name
: the name of a node -
tags
: a list of tags
The equipment is selected only if it matches all criteria. If multiple tags are provided, only the equipment for which all the tags are applied are selected.
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
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.
Get rack named R1-A02:
>>> db.racks.filter(name='R1-A02')
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>
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>
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>
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'}