Contents
Queries are used to retrieve information about the cluster, e.g. a list of instances or nodes. For historical reasons they use a simple data structure for their result. The client submits the fields it would like to receive and the query returns a list for each item (instance, node, etc.) available. Each item consists of another list representing the fields’ values.
This data structure has a few drawbacks. It can’t associate a status (e.g. “node offline”) with fields as using special values can lead to ambiguities. Additionally it can’t mark fields as “not found” as the list of returned columns must match the fields requested.
Example:
>>> cli.GetClient().QueryNodes([], ["name", "pip", "mfree"], False)
[
['node1.example.com', '192.0.2.18', 14800],
['node2.example.com', '192.0.2.19', 31280]
]
There is no way for clients to determine the list of possible fields, meaning they have to be hardcoded. Selecting unknown fields raises an exception:
>>> cli.GetClient().QueryNodes([], ["name", "UnknownField"], False)
ganeti.errors.OpPrereqError: (u'Unknown output fields selected: UnknownField', u'wrong_input')
The client must also know each fields’ kind, that is whether a field is numeric, boolean, describes a storage size, etc. Centralizing this information in one place, the master daemon, is desirable.
The current query result format can not be changed as it’s being used in various places. Changing the format from one Ganeti version to another would cause too much disruption. For this reason the ability to explicitly request a new result format must be added while the old format stays the default.
The implementation of query filters is planned for the future. To avoid having to change the calls again, a (hopefully) future-compatible interface will be implemented now.
In Python code, the objects described below will be implemented using subclasses of objects.ConfigObject, providing existing facilities for de-/serializing.
As it turned out, only very few fields for instances used regular expressions, all of which can easily be turned into static field names. Therefore their use in field names is dropped. Reasons:
The proposal is to implement this new interface for the following items:
The request is a dictionary with the following entries:
List of names of fields to return. Example:
["name", "mem", "nic0.ip", "disk0.size", "disk1.size"]
This will be used to filter queries. In this implementation only names can be filtered to replace the previous names parameter to queries. An empty filter (None) will return all items. To retrieve specific names, the filter must be specified as follows, with the inner part repeated for each name:
["|", ["=", "name", "node1"], ["=", "name", "node2"], …]
Filters consist of S-expressions (["operator", <operants…>]) and extensions will be made in the future to allow for more operators and fields. Such extensions might include a Python-style “in” operator, but for simplicity only “=” is supported in this implementation.
To reiterate: Filters for this implementation must consist of exactly one OR expression (["|", …]) and one or more name equality filters (["=", "name", "…"]).
Support for synchronous queries, currently available in the interface but disabled in the master daemon, will be dropped. Direct calls to opcodes have to be used instead.
The result is a dictionary with the following entries:
List of lists, one list for each item found. Each item’s list must have one entry for each field listed in fields (meaning their length is equal). Each field entry is a tuple of (status, value). status must be one of the following values:
Example response after requesting the fields name, mfree, xyz, mtotal, nic0.ip, nic1.ip and nic2.ip:
{
"fields": [
{ "name": "name", "title": "Name", "kind": "text", },
{ "name": "mfree", "title": "MemFree", "kind": "unit", },
# Unknown field
{ "name": "xyz", "title": None, "kind": "unknown", },
{ "name": "mtotal", "title": "MemTotal", "kind": "unit", },
{ "name": "nic0.ip", "title": "Nic.IP/0", "kind": "text", },
{ "name": "nic1.ip", "title": "Nic.IP/1", "kind": "text", },
{ "name": "nic2.ip", "title": "Nic.IP/2", "kind": "text", },
],
"data": [
[(0, "node1"), (0, 128), (1, None), (0, 4096),
(0, "192.0.2.1"), (0, "192.0.2.2"), (3, None)],
[(0, "node2"), (0, 96), (1, None), (0, 5000),
(0, "192.0.2.21"), (0, "192.0.2.39"), (3, "192.0.2.90")],
# Node not available, can't get "mfree" or "mtotal"
[(0, "node3"), (2, None), (1, None), (2, None),
(0, "192.0.2.30"), (3, None), (3, None)],
],
}
The request is a dictionary with the following entries:
List of names of fields to return. If not set, all fields are returned. Example:
["name", "mem", "nic0.ip", "disk0.size", "disk1.size"]
The result is a dictionary with the following entries:
Example:
{
"fields": [
{ "name": "name", "title": "Name", "kind": "text", },
{ "name": "mfree", "title": "MemFree", "kind": "unit", },
{ "name": "mtotal", "title": "MemTotal", "kind": "unit", },
{ "name": "nic0.ip", "title": "Nic.IP/0", "kind": "text", },
{ "name": "nic1.ip", "title": "Nic.IP/1", "kind": "text", },
{ "name": "nic2.ip", "title": "Nic.IP/2", "kind": "text", },
{ "name": "nic3.ip", "title": "Nic.IP/3", "kind": "text", },
# …
{ "name": "disk0.size", "title": "Disk.Size/0", "kind": "unit", },
{ "name": "disk1.size", "title": "Disk.Size/1", "kind": "unit", },
{ "name": "disk2.size", "title": "Disk.Size/2", "kind": "unit", },
{ "name": "disk3.size", "title": "Disk.Size/3", "kind": "unit", },
# …
]
}
A field definition is a dictionary with the following entries:
Field type, one of the following:
More types can be added in the future, so clients should default to formatting any unknown types the same way as “other”, which should be a string representation in most cases.
Example 1 (item name):
{
"name": "name",
"title": "Name",
"kind": "text",
}
Example 2 (free memory):
{
"name": "mfree",
"title": "MemFree",
"kind": "unit",
}
Example 3 (list of primary instances):
{
"name": "pinst",
"title": "PrimaryInstances",
"kind": "other",
}
To limit the amount of code necessary, the new result format will be converted for clients calling the old methods. Unavailable values are set to None. If unknown fields were requested, the whole query fails as the client expects exactly the fields it requested.
Currently query calls take a number of parameters, e.g. names, fields and whether to use locking. These will continue to work and return the old result format. Only clients using the new calls described below will be able to make use of new features such as filters. Two new calls are introduced:
The LUXI API is more or less mapped directly into Python. In addition to the existing stub functions new ones will be added for the new query requests.
The RAPI interface already returns dictionaries for each item, but to not break compatibility no changes should be made to the structure (e.g. to include field definitions). The proposal here is to add a new parameter to allow clients to execute the requests described in this proposal directly and to receive the unmodified result. The new formats are a lot more verbose, flexible and extensible.
Command line programs might have difficulties to display the verbose status data to the user. There are several options:
Some are better for interactive usage, some better for use by other programs. It is expected that a combination will be used. The column separator (--separator=…) can be used to differentiate between interactive and programmatic usage.
Another solution discussed was to add an additional column for each non-static field containing the status. Clients interested in the status could explicitly query for it.