1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Remote API resource implementations.
32
33 PUT or POST?
34 ============
35
36 According to RFC2616 the main difference between PUT and POST is that
37 POST can create new resources but PUT can only create the resource the
38 URI was pointing to on the PUT request.
39
40 In the context of this module POST on ``/2/instances`` to change an existing
41 entity is legitimate, while PUT would not be. PUT creates a new entity (e.g. a
42 new instance) with a name specified in the request.
43
44 Quoting from RFC2616, section 9.6::
45
46 The fundamental difference between the POST and PUT requests is reflected in
47 the different meaning of the Request-URI. The URI in a POST request
48 identifies the resource that will handle the enclosed entity. That resource
49 might be a data-accepting process, a gateway to some other protocol, or a
50 separate entity that accepts annotations. In contrast, the URI in a PUT
51 request identifies the entity enclosed with the request -- the user agent
52 knows what URI is intended and the server MUST NOT attempt to apply the
53 request to some other resource. If the server desires that the request be
54 applied to a different URI, it MUST send a 301 (Moved Permanently) response;
55 the user agent MAY then make its own decision regarding whether or not to
56 redirect the request.
57
58 So when adding new methods, if they are operating on the URI entity itself,
59 PUT should be prefered over POST.
60
61 """
62
63
64
65
66
67 import OpenSSL
68
69 from ganeti import opcodes
70 from ganeti import objects
71 from ganeti import http
72 from ganeti import constants
73 from ganeti import cli
74 from ganeti import rapi
75 from ganeti import ht
76 from ganeti import compat
77 from ganeti.rapi import baserlib
78
79
80 _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"]
81 I_FIELDS = ["name", "admin_state", "os",
82 "pnode", "snodes",
83 "disk_template",
84 "nic.ips", "nic.macs", "nic.modes", "nic.uuids", "nic.names",
85 "nic.links", "nic.networks", "nic.networks.names", "nic.bridges",
86 "network_port",
87 "disk.sizes", "disk.spindles", "disk_usage", "disk.uuids",
88 "disk.names",
89 "beparams", "hvparams",
90 "oper_state", "oper_ram", "oper_vcpus", "status",
91 "custom_hvparams", "custom_beparams", "custom_nicparams",
92 ] + _COMMON_FIELDS
93
94 N_FIELDS = ["name", "offline", "master_candidate", "drained",
95 "dtotal", "dfree", "sptotal", "spfree",
96 "mtotal", "mnode", "mfree",
97 "pinst_cnt", "sinst_cnt",
98 "ctotal", "cnos", "cnodes", "csockets",
99 "pip", "sip", "role",
100 "pinst_list", "sinst_list",
101 "master_capable", "vm_capable",
102 "ndparams",
103 "group.uuid",
104 ] + _COMMON_FIELDS
105
106 NET_FIELDS = ["name", "network", "gateway",
107 "network6", "gateway6",
108 "mac_prefix",
109 "free_count", "reserved_count",
110 "map", "group_list", "inst_list",
111 "external_reservations",
112 ] + _COMMON_FIELDS
113
114 G_FIELDS = [
115 "alloc_policy",
116 "name",
117 "node_cnt",
118 "node_list",
119 "ipolicy",
120 "custom_ipolicy",
121 "diskparams",
122 "custom_diskparams",
123 "ndparams",
124 "custom_ndparams",
125 ] + _COMMON_FIELDS
126
127 FILTER_RULE_FIELDS = [
128 "watermark",
129 "priority",
130 "predicates",
131 "action",
132 "reason_trail",
133 "uuid",
134 ]
135
136 J_FIELDS_BULK = [
137 "id", "ops", "status", "summary",
138 "opstatus",
139 "received_ts", "start_ts", "end_ts",
140 ]
141
142 J_FIELDS = J_FIELDS_BULK + [
143 "oplog",
144 "opresult",
145 ]
146
147 _NR_DRAINED = "drained"
148 _NR_MASTER_CANDIDATE = "master-candidate"
149 _NR_MASTER = "master"
150 _NR_OFFLINE = "offline"
151 _NR_REGULAR = "regular"
152
153 _NR_MAP = {
154 constants.NR_MASTER: _NR_MASTER,
155 constants.NR_MCANDIDATE: _NR_MASTER_CANDIDATE,
156 constants.NR_DRAINED: _NR_DRAINED,
157 constants.NR_OFFLINE: _NR_OFFLINE,
158 constants.NR_REGULAR: _NR_REGULAR,
159 }
160
161 assert frozenset(_NR_MAP.keys()) == constants.NR_ALL
162
163
164 _REQ_DATA_VERSION = "__version__"
165
166
167 _INST_CREATE_REQV1 = "instance-create-reqv1"
168
169
170 _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1"
171
172
173 _NODE_MIGRATE_REQV1 = "node-migrate-reqv1"
174
175
176 _NODE_EVAC_RES1 = "node-evac-res1"
177
178 ALL_FEATURES = compat.UniqueFrozenset([
179 _INST_CREATE_REQV1,
180 _INST_REINSTALL_REQV1,
181 _NODE_MIGRATE_REQV1,
182 _NODE_EVAC_RES1,
183 ])
184
185
186 _WFJC_TIMEOUT = 10
192 """Updates the beparams dict of inst to support the memory field.
193
194 @param inst: Inst dict
195 @return: Updated inst dict
196
197 """
198 beparams = inst["beparams"]
199 beparams[constants.BE_MEMORY] = beparams[constants.BE_MAXMEM]
200
201 return inst
202
205 """Utility function to monitor the state of an open connection.
206
207 @param sock: Connection's open socket
208 @return: True if the connection was remotely closed, otherwise False
209
210 """
211 try:
212 result = sock.recv(0)
213 if result == "":
214 return True
215
216 except OpenSSL.SSL.WantReadError:
217 return False
218
219 except OpenSSL.SSL.ZeroReturnError:
220 return True
221
222 except OpenSSL.SSL.SysCallError:
223 return True
224 return False
225
226
227 -class R_root(baserlib.ResourceBase):
228 """/ resource.
229
230 """
231 @staticmethod
233 """Supported for legacy reasons.
234
235 """
236 return None
237
238
239 -class R_2(R_root):
240 """/2 resource.
241
242 """
243
246 """/version resource.
247
248 This resource should be used to determine the remote API version and
249 to adapt clients accordingly.
250
251 """
252 @staticmethod
258
259
260 -class R_2_info(baserlib.OpcodeResource):
261 """/2/info resource.
262
263 """
264 GET_OPCODE = opcodes.OpClusterQuery
265 GET_ALIASES = {
266 "volume_group_name": "vg_name",
267 "drbd_usermode_helper": "drbd_helper",
268 }
269
276
279 """/2/features resource.
280
281 """
282 @staticmethod
284 """Returns list of optional RAPI features implemented.
285
286 """
287 return list(ALL_FEATURES)
288
289
290 -class R_2_os(baserlib.OpcodeResource):
291 """/2/os resource.
292
293 """
294 GET_OPCODE = opcodes.OpOsDiagnose
295
297 """Return a list of all OSes.
298
299 Can return error 500 in case of a problem.
300
301 Example: ["debian-etch"]
302
303 """
304 cl = self.GetClient()
305 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
306 cancel_fn = (lambda: _CheckIfConnectionDropped(self._req.request_sock))
307 job_id = self.SubmitJob([op], cl=cl)
308
309 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn,
310 cancel_fn=cancel_fn)
311 diagnose_data = result[0]
312
313 if not isinstance(diagnose_data, list):
314 raise http.HttpBadGateway(message="Can't get OS list")
315
316 os_names = []
317 for (name, variants) in diagnose_data:
318 os_names.extend(cli.CalculateOSNames(name, variants))
319
320 return os_names
321
328
338
341 """Checks and extracts filter rule parameters from a request body.
342
343 @return: the checked parameters: (priority, predicates, action).
344
345 """
346
347 if not isinstance(data, dict):
348 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
349
350
351 allowed_params = set(["priority", "predicates", "action", "reason"])
352 for param in data:
353 if param not in allowed_params:
354 raise http.HttpBadRequest("Invalid body parameters: filter rule doesn't"
355 " support the parameter '%s'" % param)
356
357 priority = baserlib.CheckParameter(
358 data, "priority", exptype=int, default=0)
359
360
361 predicates = baserlib.CheckParameter(
362 data, "predicates", exptype=list, default=[])
363
364
365 action = baserlib.CheckParameter(data, "action", default="CONTINUE")
366
367 reason = baserlib.CheckParameter(data, "reason", exptype=list, default=[])
368
369 return (priority, predicates, action, reason)
370
373 """/2/filters resource.
374
375 """
376
392
394 """Adds a filter rule.
395
396 @return: the UUID of the newly created filter rule.
397
398 """
399 priority, predicates, action, reason = \
400 checkFilterParameters(self.request_body)
401
402
403 return self.GetClient().ReplaceFilter(None, priority, predicates, action,
404 reason)
405
408 """/2/filters/[filter_uuid] resource.
409
410 """
412 """Returns a filter rule.
413
414 @return: a dictionary with job parameters.
415 The result includes:
416 - uuid: unique filter ID string
417 - watermark: highest job ID ever used as a number
418 - priority: filter priority as a non-negative number
419 - predicates: filter predicates, each one being a list
420 with the first element being the name of the predicate
421 and the rest being parameters suitable for that predicate
422 - action: effect of the filter as a string
423 - reason_trail: reasons for the addition of this filter as a
424 list of lists
425
426 """
427 uuid = self.items[0]
428
429 result = baserlib.HandleItemQueryErrors(self.GetClient().QueryFilters,
430 uuids=[uuid],
431 fields=FILTER_RULE_FIELDS)
432
433 return baserlib.MapFields(FILTER_RULE_FIELDS, result[0])
434
436 """Replaces an existing filter rule, or creates one if it doesn't
437 exist already.
438
439 @return: the UUID of the changed or created filter rule.
440
441 """
442 uuid = self.items[0]
443
444 priority, predicates, action, reason = \
445 checkFilterParameters(self.request_body)
446
447 return self.GetClient().ReplaceFilter(uuid, priority, predicates, action,
448 reason)
449
451 """Deletes a filter rule.
452
453 """
454 uuid = self.items[0]
455 return self.GetClient().DeleteFilter(uuid)
456
457
458 -class R_2_jobs(baserlib.ResourceBase):
459 """/2/jobs resource.
460
461 """
477
480 """/2/jobs/[job_id] resource.
481
482 """
484 """Returns a job status.
485
486 @return: a dictionary with job parameters.
487 The result includes:
488 - id: job ID as a number
489 - status: current job status as a string
490 - ops: involved OpCodes as a list of dictionaries for each
491 opcodes in the job
492 - opstatus: OpCodes status as a list
493 - opresult: OpCodes results as a list of lists
494
495 """
496 job_id = self.items[0]
497 result = self.GetClient().QueryJobs([job_id, ], J_FIELDS)[0]
498 if result is None:
499 raise http.HttpNotFound()
500 return baserlib.MapFields(J_FIELDS, result)
501
503 """Cancel not-yet-started job.
504
505 """
506 job_id = self.items[0]
507 result = self.GetClient().CancelJob(job_id)
508 return result
509
512 """/2/jobs/[job_id]/wait resource.
513
514 """
515
516
517 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
518
520 """Waits for job changes.
521
522 """
523 job_id = self.items[0]
524
525 fields = self.getBodyParameter("fields")
526 prev_job_info = self.getBodyParameter("previous_job_info", None)
527 prev_log_serial = self.getBodyParameter("previous_log_serial", None)
528
529 if not isinstance(fields, list):
530 raise http.HttpBadRequest("The 'fields' parameter should be a list")
531
532 if not (prev_job_info is None or isinstance(prev_job_info, list)):
533 raise http.HttpBadRequest("The 'previous_job_info' parameter should"
534 " be a list")
535
536 if not (prev_log_serial is None or
537 isinstance(prev_log_serial, (int, long))):
538 raise http.HttpBadRequest("The 'previous_log_serial' parameter should"
539 " be a number")
540
541 client = self.GetClient()
542 result = client.WaitForJobChangeOnce(job_id, fields,
543 prev_job_info, prev_log_serial,
544 timeout=_WFJC_TIMEOUT)
545 if not result:
546 raise http.HttpNotFound()
547
548 if result == constants.JOB_NOTCHANGED:
549
550 return None
551
552 (job_info, log_entries) = result
553
554 return {
555 "job_info": job_info,
556 "log_entries": log_entries,
557 }
558
559
560 -class R_2_nodes(baserlib.OpcodeResource):
561 """/2/nodes resource.
562
563 """
564
579
582 """/2/nodes/[node_name] resource.
583
584 """
585 GET_ALIASES = {
586 "sip": "secondary_ip",
587 }
588
601
604 """/2/nodes/[node_name]/powercycle resource.
605
606 """
607 POST_OPCODE = opcodes.OpNodePowercycle
608
610 """Tries to powercycle a node.
611
612 """
613 return (self.request_body, {
614 "node_name": self.items[0],
615 "force": self.useForce(),
616 })
617
620 """/2/nodes/[node_name]/role resource.
621
622 """
623 PUT_OPCODE = opcodes.OpNodeSetParams
624
626 """Returns the current node role.
627
628 @return: Node role
629
630 """
631 node_name = self.items[0]
632 client = self.GetClient()
633 result = client.QueryNodes(names=[node_name], fields=["role"],
634 use_locking=self.useLocking())
635
636 return _NR_MAP[result[0][0]]
637
676
679 """/2/nodes/[node_name]/evacuate resource.
680
681 """
682 POST_OPCODE = opcodes.OpNodeEvacuate
683
685 """Evacuate all instances off a node.
686
687 """
688 return (self.request_body, {
689 "node_name": self.items[0],
690 "dry_run": self.dryRun(),
691 })
692
695 """/2/nodes/[node_name]/migrate resource.
696
697 """
698 POST_OPCODE = opcodes.OpNodeMigrate
699
701 """Migrate all primary instances from a node.
702
703 """
704 if self.queryargs:
705
706 if "live" in self.queryargs and "mode" in self.queryargs:
707 raise http.HttpBadRequest("Only one of 'live' and 'mode' should"
708 " be passed")
709
710 if "live" in self.queryargs:
711 if self._checkIntVariable("live", default=1):
712 mode = constants.HT_MIGRATION_LIVE
713 else:
714 mode = constants.HT_MIGRATION_NONLIVE
715 else:
716 mode = self._checkStringVariable("mode", default=None)
717
718 data = {
719 "mode": mode,
720 }
721 else:
722 data = self.request_body
723
724 return (data, {
725 "node_name": self.items[0],
726 })
727
730 """/2/nodes/[node_name]/modify resource.
731
732 """
733 POST_OPCODE = opcodes.OpNodeSetParams
734
736 """Changes parameters of a node.
737
738 """
739 assert len(self.items) == 1
740
741 return (self.request_body, {
742 "node_name": self.items[0],
743 })
744
770
801
824
827 """/2/networks resource.
828
829 """
830 POST_OPCODE = opcodes.OpNetworkAdd
831 POST_RENAME = {
832 "name": "network_name",
833 }
834
836 """Create a network.
837
838 """
839 assert not self.items
840 return (self.request_body, {
841 "dry_run": self.dryRun(),
842 })
843
858
889
906
923
939
942 """/2/groups resource.
943
944 """
945 POST_OPCODE = opcodes.OpGroupAdd
946 POST_RENAME = {
947 "name": "group_name",
948 }
949
951 """Create a node group.
952
953
954 """
955 assert not self.items
956 return (self.request_body, {
957 "dry_run": self.dryRun(),
958 })
959
974
977 """/2/groups/[group_name] resource.
978
979 """
980 DELETE_OPCODE = opcodes.OpGroupRemove
981
994
1004
1007 """/2/groups/[group_name]/modify resource.
1008
1009 """
1010 PUT_OPCODE = opcodes.OpGroupSetParams
1011 PUT_RENAME = {
1012 "custom_ndparams": "ndparams",
1013 "custom_ipolicy": "ipolicy",
1014 "custom_diskparams": "diskparams",
1015 }
1016
1025
1028 """/2/groups/[group_name]/rename resource.
1029
1030 """
1031 PUT_OPCODE = opcodes.OpGroupRename
1032
1042
1060
1063 """Convert in place the usb_devices string to the proper format.
1064
1065 In Ganeti 2.8.4 the separator for the usb_devices hvparam was changed from
1066 comma to space because commas cannot be accepted on the command line
1067 (they already act as the separator between different hvparams). RAPI
1068 should be able to accept commas for backwards compatibility, but we want
1069 it to also accept the new space separator. Therefore, we convert
1070 spaces into commas here and keep the old parsing logic elsewhere.
1071
1072 """
1073 try:
1074 hvparams = data["hvparams"]
1075 usb_devices = hvparams[constants.HV_USB_DEVICES]
1076 hvparams[constants.HV_USB_DEVICES] = usb_devices.replace(" ", ",")
1077 data["hvparams"] = hvparams
1078 except KeyError:
1079
1080 pass
1081
1084 """/2/instances resource.
1085
1086 """
1087 POST_OPCODE = opcodes.OpInstanceCreate
1088 POST_RENAME = {
1089 "os": "os_type",
1090 "name": "instance_name",
1091 }
1092
1108
1136
1139 """/2/instances-multi-alloc resource.
1140
1141 """
1142 POST_OPCODE = opcodes.OpInstanceMultiAlloc
1143
1145 """Try to allocate multiple instances.
1146
1147 @return: A dict with submitted jobs, allocatable instances and failed
1148 allocations
1149
1150 """
1151 if "instances" not in self.request_body:
1152 raise http.HttpBadRequest("Request is missing required 'instances' field"
1153 " in body")
1154
1155
1156
1157 OPCODE_RENAME = {
1158 "os": "os_type",
1159 "name": "instance_name",
1160 }
1161
1162 body = objects.FillDict(self.request_body, {
1163 "instances": [
1164 baserlib.FillOpcode(opcodes.OpInstanceCreate, inst, {},
1165 rename=OPCODE_RENAME)
1166 for inst in self.request_body["instances"]
1167 ],
1168 })
1169
1170 return (body, {
1171 "dry_run": self.dryRun(),
1172 })
1173
1176 """/2/instances/[instance_name] resource.
1177
1178 """
1179 DELETE_OPCODE = opcodes.OpInstanceRemove
1180
1194
1205
1222
1225 """/2/instances/[instance_name]/reboot resource.
1226
1227 Implements an instance reboot.
1228
1229 """
1230 POST_OPCODE = opcodes.OpInstanceReboot
1231
1233 """Reboot an instance.
1234
1235 The URI takes type=[hard|soft|full] and
1236 ignore_secondaries=[False|True] parameters.
1237
1238 """
1239 return (self.request_body, {
1240 "instance_name": self.items[0],
1241 "reboot_type":
1242 self.queryargs.get("type", [constants.INSTANCE_REBOOT_HARD])[0],
1243 "ignore_secondaries": bool(self._checkIntVariable("ignore_secondaries")),
1244 "dry_run": self.dryRun(),
1245 })
1246
1249 """/2/instances/[instance_name]/startup resource.
1250
1251 Implements an instance startup.
1252
1253 """
1254 PUT_OPCODE = opcodes.OpInstanceStartup
1255
1269
1272 """/2/instances/[instance_name]/shutdown resource.
1273
1274 Implements an instance shutdown.
1275
1276 """
1277 PUT_OPCODE = opcodes.OpInstanceShutdown
1278
1288
1291 """Parses a request for reinstalling an instance.
1292
1293 """
1294 if not isinstance(data, dict):
1295 raise http.HttpBadRequest("Invalid body contents, not a dictionary")
1296
1297 ostype = baserlib.CheckParameter(data, "os", default=None)
1298 start = baserlib.CheckParameter(data, "start", exptype=bool,
1299 default=True)
1300 osparams = baserlib.CheckParameter(data, "osparams", default=None)
1301
1302 ops = [
1303 opcodes.OpInstanceShutdown(instance_name=name),
1304 opcodes.OpInstanceReinstall(instance_name=name, os_type=ostype,
1305 osparams=osparams),
1306 ]
1307
1308 if start:
1309 ops.append(opcodes.OpInstanceStartup(instance_name=name, force=False))
1310
1311 return ops
1312
1315 """/2/instances/[instance_name]/reinstall resource.
1316
1317 Implements an instance reinstall.
1318
1319 """
1320 POST_OPCODE = opcodes.OpInstanceReinstall
1321
1323 """Reinstall an instance.
1324
1325 The URI takes os=name and nostartup=[0|1] optional
1326 parameters. By default, the instance will be started
1327 automatically.
1328
1329 """
1330 if self.request_body:
1331 if self.queryargs:
1332 raise http.HttpBadRequest("Can't combine query and body parameters")
1333
1334 body = self.request_body
1335 elif self.queryargs:
1336
1337 body = {
1338 "os": self._checkStringVariable("os"),
1339 "start": not self._checkIntVariable("nostartup"),
1340 }
1341 else:
1342 body = {}
1343
1344 ops = _ParseInstanceReinstallRequest(self.items[0], body)
1345
1346 return self.SubmitJob(ops)
1347
1350 """/2/instances/[instance_name]/replace-disks resource.
1351
1352 """
1353 POST_OPCODE = opcodes.OpInstanceReplaceDisks
1354
1356 """Replaces disks on an instance.
1357
1358 """
1359 static = {
1360 "instance_name": self.items[0],
1361 }
1362
1363 if self.request_body:
1364 data = self.request_body
1365 elif self.queryargs:
1366
1367 data = {
1368 "remote_node": self._checkStringVariable("remote_node", default=None),
1369 "mode": self._checkStringVariable("mode", default=None),
1370 "disks": self._checkStringVariable("disks", default=None),
1371 "iallocator": self._checkStringVariable("iallocator", default=None),
1372 }
1373 else:
1374 data = {}
1375
1376
1377 try:
1378 raw_disks = data.pop("disks")
1379 except KeyError:
1380 pass
1381 else:
1382 if raw_disks:
1383 if ht.TListOf(ht.TInt)(raw_disks):
1384 data["disks"] = raw_disks
1385 else:
1386
1387 try:
1388 data["disks"] = [int(part) for part in raw_disks.split(",")]
1389 except (TypeError, ValueError), err:
1390 raise http.HttpBadRequest("Invalid disk index passed: %s" % err)
1391
1392 return (data, static)
1393
1411
1427
1430 """/2/instances/[instance_name]/recreate-disks resource.
1431
1432 """
1433 POST_OPCODE = opcodes.OpInstanceRecreateDisks
1434
1436 """Recreate disks for an instance.
1437
1438 """
1439 return (self.request_body, {
1440 "instance_name": self.items[0],
1441 })
1442
1445 """/2/instances/[instance_name]/prepare-export resource.
1446
1447 """
1448 PUT_OPCODE = opcodes.OpBackupPrepare
1449
1458
1461 """/2/instances/[instance_name]/export resource.
1462
1463 """
1464 PUT_OPCODE = opcodes.OpBackupExport
1465 PUT_RENAME = {
1466 "destination": "target_node",
1467 }
1468
1476
1491
1506
1509 """/2/instances/[instance_name]/rename resource.
1510
1511 """
1512 PUT_OPCODE = opcodes.OpInstanceRename
1513
1521
1524 """/2/instances/[instance_name]/modify resource.
1525
1526 """
1527 PUT_OPCODE = opcodes.OpInstanceSetParams
1528 PUT_RENAME = {
1529 "custom_beparams": "beparams",
1530 "custom_hvparams": "hvparams",
1531 }
1532
1543
1546 """/2/instances/[instance_name]/disk/[disk_index]/grow resource.
1547
1548 """
1549 POST_OPCODE = opcodes.OpInstanceGrowDisk
1550
1552 """Increases the size of an instance disk.
1553
1554 """
1555 return (self.request_body, {
1556 "instance_name": self.items[0],
1557 "disk": int(self.items[1]),
1558 })
1559
1562 """/2/instances/[instance_name]/console resource.
1563
1564 """
1565 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]
1566 GET_OPCODE = opcodes.OpInstanceConsole
1567
1569 """Request information for connecting to instance's console.
1570
1571 @return: Serialized instance console description, see
1572 L{objects.InstanceConsole}
1573
1574 """
1575 instance_name = self.items[0]
1576 client = self.GetClient()
1577
1578 ((console, oper_state), ) = \
1579 client.QueryInstances([instance_name], ["console", "oper_state"], False)
1580
1581 if not oper_state:
1582 raise http.HttpServiceUnavailable("Instance console unavailable")
1583
1584 assert isinstance(console, dict)
1585 return console
1586
1589 """Tries to extract C{fields} query parameter.
1590
1591 @type args: dictionary
1592 @rtype: list of string
1593 @raise http.HttpBadRequest: When parameter can't be found
1594
1595 """
1596 try:
1597 fields = args["fields"]
1598 except KeyError:
1599 raise http.HttpBadRequest("Missing 'fields' query argument")
1600
1601 return _SplitQueryFields(fields[0])
1602
1605 """Splits fields as given for a query request.
1606
1607 @type fields: string
1608 @rtype: list of string
1609
1610 """
1611 return [i.strip() for i in fields.split(",")]
1612
1613
1614 -class R_2_query(baserlib.ResourceBase):
1615 """/2/query/[resource] resource.
1616
1617 """
1618
1619 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]
1620 PUT_ACCESS = GET_ACCESS
1621 GET_OPCODE = opcodes.OpQuery
1622 PUT_OPCODE = opcodes.OpQuery
1623
1624 - def _Query(self, fields, qfilter):
1627
1629 """Returns resource information.
1630
1631 @return: Query result, see L{objects.QueryResponse}
1632
1633 """
1634 return self._Query(_GetQueryFields(self.queryargs), None)
1635
1637 """Submits job querying for resources.
1638
1639 @return: Query result, see L{objects.QueryResponse}
1640
1641 """
1642 body = self.request_body
1643
1644 baserlib.CheckType(body, dict, "Body contents")
1645
1646 try:
1647 fields = body["fields"]
1648 except KeyError:
1649 fields = _GetQueryFields(self.queryargs)
1650
1651 qfilter = body.get("qfilter", None)
1652
1653 if qfilter is None:
1654 qfilter = body.get("filter", None)
1655
1656 return self._Query(fields, qfilter)
1657
1660 """/2/query/[resource]/fields resource.
1661
1662 """
1663 GET_OPCODE = opcodes.OpQueryFields
1664
1666 """Retrieves list of available fields for a resource.
1667
1668 @return: List of serialized L{objects.QueryFieldDefinition}
1669
1670 """
1671 try:
1672 raw_fields = self.queryargs["fields"]
1673 except KeyError:
1674 fields = None
1675 else:
1676 fields = _SplitQueryFields(raw_fields[0])
1677
1678 return self.GetClient().QueryFields(self.items[0], fields).ToDict()
1679
1755
1764
1773
1782
1791
1800