Revision 9189c902 doc/design-query2.rst

b/doc/design-query2.rst
57 57
subclasses of ``objects.ConfigObject``, providing existing facilities
58 58
for de-/serializing.
59 59

  
60
.. _query-request:
61

  
62
Query request
63
+++++++++++++
64

  
65
Each query operation will take a single parameter, a query request
66
dictionary with the following properties:
67

  
68
``kind``
69
  Denotes request kind. One of the following strings:
70

  
71
  ``query``
72
    Execute a query on items, optionally filtered (see below). For a
73
    description of the result see :ref:`query-result`.
74
  ``fields``
75
    Return list of supported fields as :ref:`field definitions
76
    <field-def>`. For a complete description of the result see
77
    :ref:`fields-result`.
78

  
79
``filter``
60
Regular expressions
61
+++++++++++++++++++
62

  
63
As it turned out, only very few fields for instances used regular
64
expressions, all of which can easily be turned into static field names.
65
Therefore their use in field names is dropped. Reasons:
66

  
67
- When regexps are used and a field name is not listed as a simple
68
  string in the field dictionary, all keys in the field dictionary have
69
  to be checked whether they're a regular expression object and if so,
70
  matched (see ``utils.FindMatch``).
71
- Code becomes simpler. There would be no need anymore to care about
72
  regular expressions as field names—they'd all be simple strings, even
73
  if there are many more. The list of field names would be static once
74
  built at module-load time.
75
- There's the issue of formatting titles for the clients. Should it be
76
  done in the server? In the client? The field definition's title would
77
  contain backreferences to the regexp groups in the field name
78
  (``re.MatchObject.expand`` can be used). With just strings, the field
79
  definitions can be passed directly to the client. They're static.
80
- Only a side note: In the memory consumed for 1'000
81
  ``_sre.SRE_Pattern`` objects (as returned by ``re.compile`` for an
82
  expression with one group) one can easily store 10'000 strings of the
83
  same length (the regexp objects keep the expression string around, so
84
  compiling the expression always uses more memory).
85

  
86

  
87
.. _item-types:
88

  
89
Item types
90
++++++++++
91

  
92
The proposal is to implement this new interface for the following
93
items:
94

  
95
``instance``
96
  Instances
97
``node``
98
  Nodes
99
``job``
100
  Jobs
101
``lock``
102
  Locks
103

  
104
.. _data-query:
105

  
106
Data query
107
++++++++++
108

  
109
.. _data-query-request:
110

  
111
Request
112
^^^^^^^
113

  
114
The request is a dictionary with the following entries:
115

  
116
``kind`` (string, required)
117
  An :ref:`item type <item-types>`.
118
``fields`` (list of strings, required)
119
  List of names of fields to return. Example::
120

  
121
    ["name", "mem", "nic0.ip", "disk0.size", "disk1.size"]
122

  
123
``filter`` (optional)
80 124
  This will be used to filter queries. In this implementation only names
81 125
  can be filtered to replace the previous ``names`` parameter to
82 126
  queries. An empty filter (``None``) will return all items. To retrieve
......
98 142
but disabled in the master daemon, will be dropped. Direct calls to
99 143
opcodes have to be used instead.
100 144

  
101
.. _query-result:
145
.. _data-query-response:
102 146

  
103
New query result format
104
+++++++++++++++++++++++
147
Response
148
^^^^^^^^
105 149

  
106 150
The result is a dictionary with the following entries:
107 151

  
108
``fields``
152
``fields`` (list of :ref:`field definitions <field-def>`)
109 153
  In-order list of a :ref:`field definition <field-def>` for each
110 154
  requested field, unknown fields are returned with the kind
111 155
  ``unknown``. Length must be equal to number of requested fields.
112
``data``
113
  A list of lists, one list for each item found. Each item's list must
114
  have one entry for each field listed in ``fields``. Each field entry
115
  is a tuple of ``(status, value)``. ``status`` must be one of the
116
  following values:
156
``data`` (list of lists of tuples)
157
  List of lists, one list for each item found. Each item's list must
158
  have one entry for each field listed in ``fields`` (meaning their
159
  length is equal). Each field entry is a tuple of ``(status, value)``.
160
  ``status`` must be one of the following values:
117 161

  
118 162
  Normal (numeric 0)
119 163
    Value is available and matches the kind in the :ref:`field
120 164
    definition <field-def>`.
121
  Value unavailable (numeric 1)
165
  Unknown field (numeric 1)
166
    Field for this column is not known. Value must be ``None``.
167
  No data (numeric 2)
122 168
    Exact meaning depends on query, e.g. node is unreachable or marked
123 169
    offline. Value must be ``None``.
124
  Unknown field (numeric 2)
125
    Field for this column is not known. Value must be ``None``.
170
  Value unavailable for item (numeric 3)
171
    Used if, for example, NIC 3 is requested for an instance with only
172
    one network interface. Value must be ``None``.
126 173

  
127
Example::
174
Example response after requesting the fields ``name``, ``mfree``,
175
``xyz``, ``mtotal``, ``nic0.ip``, ``nic1.ip`` and ``nic2.ip``::
128 176

  
129 177
  {
130 178
    "fields": [
......
133 181
      # Unknown field
134 182
      { "name": "xyz", "title": None, "kind": "unknown", },
135 183
      { "name": "mtotal", "title": "MemTotal", "kind": "unit", },
184
      { "name": "nic0.ip", "title": "Nic.IP/0", "kind": "text", },
185
      { "name": "nic1.ip", "title": "Nic.IP/1", "kind": "text", },
186
      { "name": "nic2.ip", "title": "Nic.IP/2", "kind": "text", },
136 187
      ],
137 188

  
138 189
    "data": [
139
      [(0, "node1"), (0, 128), (2, None), (0, 4096)],
140
      # Node not available
141
      [(0, "node2"), (1, None), (2, None), (1, None)],
190
      [(0, "node1"), (0, 128), (1, None), (0, 4096),
191
       (0, "192.0.2.1"), (0, "192.0.2.2"), (3, None)],
192
      [(0, "node2"), (0, 96), (1, None), (0, 5000),
193
       (0, "192.0.2.21"), (0, "192.0.2.39"), (3, "192.0.2.90")],
194
      # Node not available, can't get "mfree" or "mtotal"
195
      [(0, "node3"), (2, None), (1, None), (2, None),
196
       (0, "192.0.2.30"), (3, None), (3, None)],
142 197
      ],
143 198
  }
144 199

  
145
.. _fields-result:
200
.. _fields-query:
201

  
202
Fields query
203
++++++++++++
204

  
205
.. _fields-query-request:
206

  
207
Request
208
^^^^^^^
209

  
210
The request is a dictionary with the following entries:
211

  
212
``kind`` (string, required)
213
  An :ref:`item type <item-types>`.
214
``fields`` (list of strings, optional)
215
  List of names of fields to return. If not set, all fields are
216
  returned. Example::
217

  
218
    ["name", "mem", "nic0.ip", "disk0.size", "disk1.size"]
146 219

  
147
Field query result format
148
+++++++++++++++++++++++++
220
.. _fields-query-response:
221

  
222
Response
223
^^^^^^^^
149 224

  
150 225
The result is a dictionary with the following entries:
151 226

  
152
``fields``
153
  List of :ref:`field definitions <field-def>` for each available field.
227
``fields`` (list of :ref:`field definitions <field-def>`)
228
  List of a :ref:`field definition <field-def>` for each field. If
229
  ``fields`` was set in the request and contained an unknown field, it
230
  is returned as type ``unknown``.
154 231

  
155 232
Example::
156 233

  
......
159 236
      { "name": "name", "title": "Name", "kind": "text", },
160 237
      { "name": "mfree", "title": "MemFree", "kind": "unit", },
161 238
      { "name": "mtotal", "title": "MemTotal", "kind": "unit", },
239
      { "name": "nic0.ip", "title": "Nic.IP/0", "kind": "text", },
240
      { "name": "nic1.ip", "title": "Nic.IP/1", "kind": "text", },
241
      { "name": "nic2.ip", "title": "Nic.IP/2", "kind": "text", },
242
      { "name": "nic3.ip", "title": "Nic.IP/3", "kind": "text", },
243
      # …
244
      { "name": "disk0.size", "title": "Disk.Size/0", "kind": "unit", },
245
      { "name": "disk1.size", "title": "Disk.Size/1", "kind": "unit", },
246
      { "name": "disk2.size", "title": "Disk.Size/2", "kind": "unit", },
247
      { "name": "disk3.size", "title": "Disk.Size/3", "kind": "unit", },
248
      # …
162 249
      ]
163 250
  }
164 251

  
......
169 256

  
170 257
A field definition is a dictionary with the following entries:
171 258

  
172
``name``
173
  The field name as a regular expression. The latter is necessary to
174
  represent dynamic fields (e.g. NIC 3 of an instance).
175
``title``
259
``name`` (string)
260
  Field name. Must only contain characters matching ``[a-z0-9/._]``.
261
``title`` (string)
176 262
  Human-readable title to use in output. Must not contain whitespace.
177
``kind``
263
``kind`` (string)
178 264
  Field type, one of the following:
179 265

  
180
.. TODO: Investigate whether there are fields with floating point
181
.. numbers
182

  
183 266
  ``unknown``
184
    Unknown field (only used for :ref:`data queries <query-request>`)
267
    Unknown field
185 268
  ``text``
186 269
    String
187 270
  ``bool``
......
190 273
    Numeric
191 274
  ``unit``
192 275
    Numeric, in megabytes
276
  ``timestamp``
277
    Unix timestamp in seconds since the epoch
193 278
  ``other``
194 279
    Free-form type, depending on query
195 280

  
......
197 282
  formatting any unknown types the same way as "other", which should be
198 283
  a string representation in most cases.
199 284

  
285
.. TODO: Investigate whether there are fields with floating point
286
.. numbers
287

  
200 288
Example 1 (item name)::
201 289

  
202 290
  {
......
227 315
+++++++++++++++++
228 316

  
229 317
To limit the amount of code necessary, the :ref:`new result format
230
<query-result>` will be converted for older clients. Unavailable values
231
are set to ``None``. If unknown fields were requested, the whole query
232
fails as the client expects exactly the fields it requested.
318
<data-query-response>` will be converted for clients calling the old
319
methods.  Unavailable values are set to ``None``. If unknown fields were
320
requested, the whole query fails as the client expects exactly the
321
fields it requested.
322

  
323
.. _luxi:
233 324

  
234 325
LUXI
235 326
++++
236 327

  
237 328
Currently query calls take a number of parameters, e.g. names, fields
238 329
and whether to use locking. These will continue to work and return the
239
:ref:`old result format <old-result-format>`. To use the new query
240
requests, the same calls must be invoked with a single parameter as the
241
:ref:`query object <query-request>`. Only clients using the new call
242
syntax will be able to make use of new features such as filters.
330
:ref:`old result format <old-result-format>`. Only clients using the
331
new calls described below will be able to make use of new features such
332
as filters. Two new calls are introduced:
333

  
334
``Query``
335
  Execute a query on items, optionally filtered. Takes a single
336
  parameter, a :ref:`query object <data-query-request>` encoded as a
337
  dictionary and returns a :ref:`data query response
338
  <data-query-response`.
339
``QueryFields``
340
  Return list of supported fields as :ref:`field definitions
341
  <field-def>`. Takes a single parameter, a :ref:`fields query object
342
  <fields-query-request>` encoded as a dictionary and returns a
343
  :ref:`fields query response <fields-query-response>`.
344

  
243 345

  
244 346
Python
245 347
++++++
......
258 360
proposal directly and to receive the unmodified result. The new formats
259 361
are a lot more verbose, flexible and extensible.
260 362

  
363
.. _cli-programs:
364

  
365
CLI programs
366
++++++++++++
367

  
368
Command line programs might have difficulties to display the verbose
369
status data to the user. There are several options:
370

  
371
- Use colours to indicate missing values
372
- Display status as value in parentheses, e.g. "(unavailable)"
373
- Hide unknown columns from the result table and print a warning
374
- Exit with non-zero code to indicate failures and/or missing data
375

  
376
Some are better for interactive usage, some better for use by other
377
programs. It is expected that a combination will be used. The column
378
separator (``--separator=…``) can be used to differentiate between
379
interactive and programmatic usage.
380

  
261 381

  
262 382
Other discussed solutions
263 383
-------------------------

Also available in: Unified diff