Revision 208a6cff

b/doc/rapi.rst
1289 1289
It supports the ``dry-run`` argument.
1290 1290

  
1291 1291

  
1292
``/2/query/[resource]``
1293
+++++++++++++++++++++++
1294

  
1295
Requests resource information. Available fields can be found in man
1296
pages and using ``/2/query/[resource]/fields``. The resource is one of
1297
:pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the :doc:`query2
1298
design document <design-query2>` for more details.
1299

  
1300
Supports the following commands: ``GET``, ``PUT``.
1301

  
1302
``GET``
1303
~~~~~~~
1304

  
1305
Returns list of included fields and actual data. Takes a query parameter
1306
named "fields", containing a comma-separated list of field names. Does
1307
not support filtering.
1308

  
1309
``PUT``
1310
~~~~~~~
1311

  
1312
Returns list of included fields and actual data. The list of requested
1313
fields can either be given as the query parameter "fields" or as a body
1314
parameter with the same name. The optional body parameter "filter" can
1315
be given and must be either ``null`` or a list containing filter
1316
operators.
1317

  
1318

  
1319
``/2/query/[resource]/fields``
1320
++++++++++++++++++++++++++++++
1321

  
1322
Request list of available fields for a resource. The resource is one of
1323
:pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the
1324
:doc:`query2 design document <design-query2>` for more details.
1325

  
1326
Supports the following commands: ``GET``.
1327

  
1328
``GET``
1329
~~~~~~~
1330

  
1331
Returns a list of field descriptions for available fields. Takes an
1332
optional query parameter named "fields", containing a comma-separated
1333
list of field names.
1334

  
1335

  
1292 1336
``/2/os``
1293 1337
+++++++++
1294 1338

  
b/lib/rapi/client.py
1583 1583
                             ("/%s/groups/%s/rename" %
1584 1584
                              (GANETI_RAPI_VERSION, group)), None, body)
1585 1585

  
1586

  
1587 1586
  def AssignGroupNodes(self, group, nodes, force=False, dry_run=False):
1588 1587
    """Assigns nodes to a group.
1589 1588

  
......
1611 1610
    return self._SendRequest(HTTP_PUT,
1612 1611
                             ("/%s/groups/%s/assign-nodes" %
1613 1612
                             (GANETI_RAPI_VERSION, group)), query, body)
1613

  
1614
  def Query(self, what, fields, filter_=None):
1615
    """Retrieves information about resources.
1616

  
1617
    @type what: string
1618
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1619
    @type fields: list of string
1620
    @param fields: Requested fields
1621
    @type filter_: None or list
1622
    @param filter_ Query filter
1623

  
1624
    @rtype: string
1625
    @return: job id
1626

  
1627
    """
1628
    body = {
1629
      "fields": fields,
1630
      }
1631

  
1632
    if filter_ is not None:
1633
      body["filter"] = filter_
1634

  
1635
    return self._SendRequest(HTTP_PUT,
1636
                             ("/%s/query/%s" %
1637
                              (GANETI_RAPI_VERSION, what)), None, body)
1638

  
1639
  def QueryFields(self, what, fields=None):
1640
    """Retrieves available fields for a resource.
1641

  
1642
    @type what: string
1643
    @param what: Resource name, one of L{constants.QR_VIA_RAPI}
1644
    @type fields: list of string
1645
    @param fields: Requested fields
1646

  
1647
    @rtype: string
1648
    @return: job id
1649

  
1650
    """
1651
    query = []
1652

  
1653
    if fields is not None:
1654
      query.append(("fields", ",".join(fields)))
1655

  
1656
    return self._SendRequest(HTTP_GET,
1657
                             ("/%s/query/%s/fields" %
1658
                              (GANETI_RAPI_VERSION, what)), query, None)
b/lib/rapi/connector.py
243 243
    "/2/redistribute-config": rlib2.R_2_redist_config,
244 244
    "/2/features": rlib2.R_2_features,
245 245
    "/2/modify": rlib2.R_2_cluster_modify,
246
    re.compile(r"^/2/query/(%s)$" % query_res_pattern): rlib2.R_2_query,
247
    re.compile(r"^/2/query/(%s)/fields$" % query_res_pattern):
248
      rlib2.R_2_query_fields,
246 249
    }
247 250

  
248 251

  
b/lib/rapi/rlib2.py
1259 1259
    return console
1260 1260

  
1261 1261

  
1262
def _GetQueryFields(args):
1263
  """
1264

  
1265
  """
1266
  try:
1267
    fields = args["fields"]
1268
  except KeyError:
1269
    raise http.HttpBadRequest("Missing 'fields' query argument")
1270

  
1271
  return _SplitQueryFields(fields[0])
1272

  
1273

  
1274
def _SplitQueryFields(fields):
1275
  """
1276

  
1277
  """
1278
  return [i.strip() for i in fields.split(",")]
1279

  
1280

  
1281
class R_2_query(baserlib.R_Generic):
1282
  """/2/query/[resource] resource.
1283

  
1284
  """
1285
  # Results might contain sensitive information
1286
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
1287

  
1288
  def _Query(self, fields, filter_):
1289
    return baserlib.GetClient().Query(self.items[0], fields, filter_).ToDict()
1290

  
1291
  def GET(self):
1292
    """Returns resource information.
1293

  
1294
    @return: Query result, see L{objects.QueryResponse}
1295

  
1296
    """
1297
    return self._Query(_GetQueryFields(self.queryargs), None)
1298

  
1299
  def PUT(self):
1300
    """Submits job querying for resources.
1301

  
1302
    @return: Query result, see L{objects.QueryResponse}
1303

  
1304
    """
1305
    body = self.request_body
1306

  
1307
    baserlib.CheckType(body, dict, "Body contents")
1308

  
1309
    try:
1310
      fields = body["fields"]
1311
    except KeyError:
1312
      fields = _GetQueryFields(self.queryargs)
1313

  
1314
    return self._Query(fields, self.request_body.get("filter", None))
1315

  
1316

  
1317
class R_2_query_fields(baserlib.R_Generic):
1318
  """/2/query/[resource]/fields resource.
1319

  
1320
  """
1321
  def GET(self):
1322
    """Retrieves list of available fields for a resource.
1323

  
1324
    @return: List of serialized L{objects.QueryFieldDefinition}
1325

  
1326
    """
1327
    try:
1328
      raw_fields = self.queryargs["fields"]
1329
    except KeyError:
1330
      fields = None
1331
    else:
1332
      fields = _SplitQueryFields(raw_fields[0])
1333

  
1334
    return baserlib.GetClient().QueryFields(self.items[0], fields).ToDict()
1335

  
1336

  
1262 1337
class _R_Tags(baserlib.R_Generic):
1263 1338
  """ Quasiclass for tagging resources
1264 1339

  
b/test/ganeti.rapi.client_unittest.py
31 31
from ganeti import http
32 32
from ganeti import serializer
33 33
from ganeti import utils
34
from ganeti import query
35
from ganeti import objects
34 36

  
35 37
from ganeti.rapi import connector
36 38
from ganeti.rapi import rlib2
......
1152 1154
      self.assertEqual(data["amount"], amount)
1153 1155
      self.assertEqual(self.rapi.CountPending(), 0)
1154 1156

  
1157
  def testQuery(self):
1158
    for idx, what in enumerate(constants.QR_VIA_RAPI):
1159
      for idx2, filter_ in enumerate([None, ["?", "name"]]):
1160
        job_id = 11010 + (idx << 4) + (idx2 << 16)
1161
        fields = sorted(query.ALL_FIELDS[what].keys())[:10]
1162

  
1163
        self.rapi.AddResponse(str(job_id))
1164
        self.assertEqual(self.client.Query(what, fields, filter_=filter_),
1165
                         job_id)
1166
        self.assertItems([what])
1167
        self.assertHandler(rlib2.R_2_query)
1168
        self.assertFalse(self.rapi.GetLastHandler().queryargs)
1169
        data = serializer.LoadJson(self.rapi.GetLastRequestData())
1170
        self.assertEqual(data["fields"], fields)
1171
        if filter_ is None:
1172
          self.assertTrue("filter" not in data)
1173
        else:
1174
          self.assertEqual(data["filter"], filter_)
1175
        self.assertEqual(self.rapi.CountPending(), 0)
1176

  
1177
  def testQueryFields(self):
1178
    exp_result = objects.QueryFieldsResponse(fields=[
1179
      objects.QueryFieldDefinition(name="pnode", title="PNode",
1180
                                   kind=constants.QFT_NUMBER),
1181
      objects.QueryFieldDefinition(name="other", title="Other",
1182
                                   kind=constants.QFT_BOOL),
1183
      ])
1184

  
1185
    for what in constants.QR_VIA_RAPI:
1186
      for fields in [None, ["name", "_unknown_"], ["&", "?|"]]:
1187
        self.rapi.AddResponse(serializer.DumpJson(exp_result.ToDict()))
1188
        result = self.client.QueryFields(what, fields=fields)
1189
        self.assertItems([what])
1190
        self.assertHandler(rlib2.R_2_query_fields)
1191
        self.assertFalse(self.rapi.GetLastRequestData())
1192

  
1193
        queryargs = self.rapi.GetLastHandler().queryargs
1194
        if fields is None:
1195
          self.assertFalse(queryargs)
1196
        else:
1197
          self.assertEqual(queryargs, {
1198
            "fields": [",".join(fields)],
1199
            })
1200

  
1201
        self.assertEqual(objects.QueryFieldsResponse.FromDict(result).ToDict(),
1202
                         exp_result.ToDict())
1203

  
1204
        self.assertEqual(self.rapi.CountPending(), 0)
1205

  
1155 1206

  
1156 1207
class RapiTestRunner(unittest.TextTestRunner):
1157 1208
  def run(self, *args):

Also available in: Unified diff