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