Package ganeti :: Package rapi :: Module rlib2
[hide private]
[frames] | no frames]

Source Code for Module ganeti.rapi.rlib2

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc. 
   5  # All rights reserved. 
   6  # 
   7  # Redistribution and use in source and binary forms, with or without 
   8  # modification, are permitted provided that the following conditions are 
   9  # met: 
  10  # 
  11  # 1. Redistributions of source code must retain the above copyright notice, 
  12  # this list of conditions and the following disclaimer. 
  13  # 
  14  # 2. Redistributions in binary form must reproduce the above copyright 
  15  # notice, this list of conditions and the following disclaimer in the 
  16  # documentation and/or other materials provided with the distribution. 
  17  # 
  18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
  19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
  20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
  22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
  25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
  26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
  27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
  28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  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  # pylint: disable=C0103 
  64   
  65  # C0103: Invalid name, since the R_* names are not conforming 
  66   
  67  from ganeti import opcodes 
  68  from ganeti import objects 
  69  from ganeti import http 
  70  from ganeti import constants 
  71  from ganeti import cli 
  72  from ganeti import rapi 
  73  from ganeti import ht 
  74  from ganeti import compat 
  75  from ganeti.rapi import baserlib 
  76   
  77   
  78  _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"] 
  79  I_FIELDS = ["name", "admin_state", "os", 
  80              "pnode", "snodes", 
  81              "disk_template", 
  82              "nic.ips", "nic.macs", "nic.modes", "nic.uuids", "nic.names", 
  83              "nic.links", "nic.networks", "nic.networks.names", "nic.bridges", 
  84              "network_port", 
  85              "disk.sizes", "disk.spindles", "disk_usage", "disk.uuids", 
  86              "disk.names", 
  87              "beparams", "hvparams", 
  88              "oper_state", "oper_ram", "oper_vcpus", "status", 
  89              "custom_hvparams", "custom_beparams", "custom_nicparams", 
  90              ] + _COMMON_FIELDS 
  91   
  92  N_FIELDS = ["name", "offline", "master_candidate", "drained", 
  93              "dtotal", "dfree", "sptotal", "spfree", 
  94              "mtotal", "mnode", "mfree", 
  95              "pinst_cnt", "sinst_cnt", 
  96              "ctotal", "cnos", "cnodes", "csockets", 
  97              "pip", "sip", "role", 
  98              "pinst_list", "sinst_list", 
  99              "master_capable", "vm_capable", 
 100              "ndparams", 
 101              "group.uuid", 
 102              ] + _COMMON_FIELDS 
 103   
 104  NET_FIELDS = ["name", "network", "gateway", 
 105                "network6", "gateway6", 
 106                "mac_prefix", 
 107                "free_count", "reserved_count", 
 108                "map", "group_list", "inst_list", 
 109                "external_reservations", 
 110                ] + _COMMON_FIELDS 
 111   
 112  G_FIELDS = [ 
 113    "alloc_policy", 
 114    "name", 
 115    "node_cnt", 
 116    "node_list", 
 117    "ipolicy", 
 118    "custom_ipolicy", 
 119    "diskparams", 
 120    "custom_diskparams", 
 121    "ndparams", 
 122    "custom_ndparams", 
 123    ] + _COMMON_FIELDS 
 124   
 125  FILTER_RULE_FIELDS = [ 
 126    "watermark", 
 127    "priority", 
 128    "predicates", 
 129    "action", 
 130    "reason_trail", 
 131    "uuid", 
 132    ] 
 133   
 134  J_FIELDS_BULK = [ 
 135    "id", "ops", "status", "summary", 
 136    "opstatus", 
 137    "received_ts", "start_ts", "end_ts", 
 138    ] 
 139   
 140  J_FIELDS = J_FIELDS_BULK + [ 
 141    "oplog", 
 142    "opresult", 
 143    ] 
 144   
 145  _NR_DRAINED = "drained" 
 146  _NR_MASTER_CANDIDATE = "master-candidate" 
 147  _NR_MASTER = "master" 
 148  _NR_OFFLINE = "offline" 
 149  _NR_REGULAR = "regular" 
 150   
 151  _NR_MAP = { 
 152    constants.NR_MASTER: _NR_MASTER, 
 153    constants.NR_MCANDIDATE: _NR_MASTER_CANDIDATE, 
 154    constants.NR_DRAINED: _NR_DRAINED, 
 155    constants.NR_OFFLINE: _NR_OFFLINE, 
 156    constants.NR_REGULAR: _NR_REGULAR, 
 157    } 
 158   
 159  assert frozenset(_NR_MAP.keys()) == constants.NR_ALL 
 160   
 161  # Request data version field 
 162  _REQ_DATA_VERSION = "__version__" 
 163   
 164  # Feature string for instance creation request data version 1 
 165  _INST_CREATE_REQV1 = "instance-create-reqv1" 
 166   
 167  # Feature string for instance reinstall request version 1 
 168  _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1" 
 169   
 170  # Feature string for node migration version 1 
 171  _NODE_MIGRATE_REQV1 = "node-migrate-reqv1" 
 172   
 173  # Feature string for node evacuation with LU-generated jobs 
 174  _NODE_EVAC_RES1 = "node-evac-res1" 
 175   
 176  ALL_FEATURES = compat.UniqueFrozenset([ 
 177    _INST_CREATE_REQV1, 
 178    _INST_REINSTALL_REQV1, 
 179    _NODE_MIGRATE_REQV1, 
 180    _NODE_EVAC_RES1, 
 181    ]) 
 182   
 183  # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change. 
 184  _WFJC_TIMEOUT = 10 
185 186 187 # FIXME: For compatibility we update the beparams/memory field. Needs to be 188 # removed in Ganeti 2.8 189 -def _UpdateBeparams(inst):
190 """Updates the beparams dict of inst to support the memory field. 191 192 @param inst: Inst dict 193 @return: Updated inst dict 194 195 """ 196 beparams = inst["beparams"] 197 beparams[constants.BE_MEMORY] = beparams[constants.BE_MAXMEM] 198 199 return inst
200
201 202 -class R_root(baserlib.ResourceBase):
203 """/ resource. 204 205 """ 206 @staticmethod
207 - def GET():
208 """Supported for legacy reasons. 209 210 """ 211 return None
212
213 214 -class R_2(R_root):
215 """/2 resource. 216 217 """
218
219 220 -class R_version(baserlib.ResourceBase):
221 """/version resource. 222 223 This resource should be used to determine the remote API version and 224 to adapt clients accordingly. 225 226 """ 227 @staticmethod
228 - def GET():
229 """Returns the remote API version. 230 231 """ 232 return constants.RAPI_VERSION
233
234 235 -class R_2_info(baserlib.OpcodeResource):
236 """/2/info resource. 237 238 """ 239 GET_OPCODE = opcodes.OpClusterQuery 240 GET_ALIASES = { 241 "volume_group_name": "vg_name", 242 "drbd_usermode_helper": "drbd_helper", 243 } 244
245 - def GET(self):
246 """Returns cluster information. 247 248 """ 249 client = self.GetClient() 250 return client.QueryClusterInfo()
251
252 253 -class R_2_features(baserlib.ResourceBase):
254 """/2/features resource. 255 256 """ 257 @staticmethod
258 - def GET():
259 """Returns list of optional RAPI features implemented. 260 261 """ 262 return list(ALL_FEATURES)
263
264 265 -class R_2_os(baserlib.OpcodeResource):
266 """/2/os resource. 267 268 """ 269 GET_OPCODE = opcodes.OpOsDiagnose 270
271 - def GET(self):
272 """Return a list of all OSes. 273 274 Can return error 500 in case of a problem. 275 276 Example: ["debian-etch"] 277 278 """ 279 cl = self.GetClient() 280 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[]) 281 job_id = self.SubmitJob([op], cl=cl) 282 # we use custom feedback function, instead of print we log the status 283 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn) 284 diagnose_data = result[0] 285 286 if not isinstance(diagnose_data, list): 287 raise http.HttpBadGateway(message="Can't get OS list") 288 289 os_names = [] 290 for (name, variants) in diagnose_data: 291 os_names.extend(cli.CalculateOSNames(name, variants)) 292 293 return os_names
294
295 296 -class R_2_redist_config(baserlib.OpcodeResource):
297 """/2/redistribute-config resource. 298 299 """ 300 PUT_OPCODE = opcodes.OpClusterRedistConf
301
302 303 -class R_2_cluster_modify(baserlib.OpcodeResource):
304 """/2/modify resource. 305 306 """ 307 PUT_OPCODE = opcodes.OpClusterSetParams 308 PUT_FORBIDDEN = [ 309 "compression_tools", 310 ]
311
312 313 -def checkFilterParameters(data):
314 """Checks and extracts filter rule parameters from a request body. 315 316 @return: the checked parameters: (priority, predicates, action). 317 318 """ 319 320 if not isinstance(data, dict): 321 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 322 323 # Forbid unknown parameters 324 allowed_params = set(["priority", "predicates", "action", "reason"]) 325 for param in data: 326 if param not in allowed_params: 327 raise http.HttpBadRequest("Invalid body parameters: filter rule doesn't" 328 " support the parameter '%s'" % param) 329 330 priority = baserlib.CheckParameter( 331 data, "priority", exptype=int, default=0) 332 333 # We leave the deeper check into the predicates list to the server. 334 predicates = baserlib.CheckParameter( 335 data, "predicates", exptype=list, default=[]) 336 337 # The action can be a string or a list; we leave the check to the server. 338 action = baserlib.CheckParameter(data, "action", default="CONTINUE") 339 340 reason = baserlib.CheckParameter(data, "reason", exptype=list, default=[]) 341 342 return (priority, predicates, action, reason)
343
344 345 -class R_2_filters(baserlib.ResourceBase):
346 """/2/filters resource. 347 348 """ 349
350 - def GET(self):
351 """Returns a list of all filter rules. 352 353 @return: a dictionary with filter rule UUID and uri. 354 355 """ 356 client = self.GetClient() 357 358 if self.useBulk(): 359 bulkdata = client.QueryFilters(None, FILTER_RULE_FIELDS) 360 return baserlib.MapBulkFields(bulkdata, FILTER_RULE_FIELDS) 361 else: 362 jobdata = map(compat.fst, client.QueryFilters(None, ["uuid"])) 363 return baserlib.BuildUriList(jobdata, "/2/filters/%s", 364 uri_fields=("uuid", "uri"))
365
366 - def POST(self):
367 """Adds a filter rule. 368 369 @return: the UUID of the newly created filter rule. 370 371 """ 372 priority, predicates, action, reason = \ 373 checkFilterParameters(self.request_body) 374 375 # ReplaceFilter(None, ...) inserts a new filter. 376 return self.GetClient().ReplaceFilter(None, priority, predicates, action, 377 reason)
378
379 380 -class R_2_filters_uuid(baserlib.ResourceBase):
381 """/2/filters/[filter_uuid] resource. 382 383 """
384 - def GET(self):
385 """Returns a filter rule. 386 387 @return: a dictionary with job parameters. 388 The result includes: 389 - uuid: unique filter ID string 390 - watermark: highest job ID ever used as a number 391 - priority: filter priority as a non-negative number 392 - predicates: filter predicates, each one being a list 393 with the first element being the name of the predicate 394 and the rest being parameters suitable for that predicate 395 - action: effect of the filter as a string 396 - reason_trail: reasons for the addition of this filter as a 397 list of lists 398 399 """ 400 uuid = self.items[0] 401 402 result = baserlib.HandleItemQueryErrors(self.GetClient().QueryFilters, 403 uuids=[uuid], 404 fields=FILTER_RULE_FIELDS) 405 406 return baserlib.MapFields(FILTER_RULE_FIELDS, result[0])
407
408 - def PUT(self):
409 """Replaces an existing filter rule, or creates one if it doesn't 410 exist already. 411 412 @return: the UUID of the changed or created filter rule. 413 414 """ 415 uuid = self.items[0] 416 417 priority, predicates, action, reason = \ 418 checkFilterParameters(self.request_body) 419 420 return self.GetClient().ReplaceFilter(uuid, priority, predicates, action, 421 reason)
422
423 - def DELETE(self):
424 """Deletes a filter rule. 425 426 """ 427 uuid = self.items[0] 428 return self.GetClient().DeleteFilter(uuid)
429
430 431 -class R_2_jobs(baserlib.ResourceBase):
432 """/2/jobs resource. 433 434 """
435 - def GET(self):
436 """Returns a dictionary of jobs. 437 438 @return: a dictionary with jobs id and uri. 439 440 """ 441 client = self.GetClient() 442 443 if self.useBulk(): 444 bulkdata = client.QueryJobs(None, J_FIELDS_BULK) 445 return baserlib.MapBulkFields(bulkdata, J_FIELDS_BULK) 446 else: 447 jobdata = map(compat.fst, client.QueryJobs(None, ["id"])) 448 return baserlib.BuildUriList(jobdata, "/2/jobs/%s", 449 uri_fields=("id", "uri"))
450
451 452 -class R_2_jobs_id(baserlib.ResourceBase):
453 """/2/jobs/[job_id] resource. 454 455 """
456 - def GET(self):
457 """Returns a job status. 458 459 @return: a dictionary with job parameters. 460 The result includes: 461 - id: job ID as a number 462 - status: current job status as a string 463 - ops: involved OpCodes as a list of dictionaries for each 464 opcodes in the job 465 - opstatus: OpCodes status as a list 466 - opresult: OpCodes results as a list of lists 467 468 """ 469 job_id = self.items[0] 470 result = self.GetClient().QueryJobs([job_id, ], J_FIELDS)[0] 471 if result is None: 472 raise http.HttpNotFound() 473 return baserlib.MapFields(J_FIELDS, result)
474
475 - def DELETE(self):
476 """Cancel not-yet-started job. 477 478 """ 479 job_id = self.items[0] 480 result = self.GetClient().CancelJob(job_id) 481 return result
482
483 484 -class R_2_jobs_id_wait(baserlib.ResourceBase):
485 """/2/jobs/[job_id]/wait resource. 486 487 """ 488 # WaitForJobChange provides access to sensitive information and blocks 489 # machine resources (it's a blocking RAPI call), hence restricting access. 490 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 491
492 - def GET(self):
493 """Waits for job changes. 494 495 """ 496 job_id = self.items[0] 497 498 fields = self.getBodyParameter("fields") 499 prev_job_info = self.getBodyParameter("previous_job_info", None) 500 prev_log_serial = self.getBodyParameter("previous_log_serial", None) 501 502 if not isinstance(fields, list): 503 raise http.HttpBadRequest("The 'fields' parameter should be a list") 504 505 if not (prev_job_info is None or isinstance(prev_job_info, list)): 506 raise http.HttpBadRequest("The 'previous_job_info' parameter should" 507 " be a list") 508 509 if not (prev_log_serial is None or 510 isinstance(prev_log_serial, (int, long))): 511 raise http.HttpBadRequest("The 'previous_log_serial' parameter should" 512 " be a number") 513 514 client = self.GetClient() 515 result = client.WaitForJobChangeOnce(job_id, fields, 516 prev_job_info, prev_log_serial, 517 timeout=_WFJC_TIMEOUT) 518 if not result: 519 raise http.HttpNotFound() 520 521 if result == constants.JOB_NOTCHANGED: 522 # No changes 523 return None 524 525 (job_info, log_entries) = result 526 527 return { 528 "job_info": job_info, 529 "log_entries": log_entries, 530 }
531
532 533 -class R_2_nodes(baserlib.OpcodeResource):
534 """/2/nodes resource. 535 536 """ 537
538 - def GET(self):
539 """Returns a list of all nodes. 540 541 """ 542 client = self.GetClient() 543 544 if self.useBulk(): 545 bulkdata = client.QueryNodes([], N_FIELDS, False) 546 return baserlib.MapBulkFields(bulkdata, N_FIELDS) 547 else: 548 nodesdata = client.QueryNodes([], ["name"], False) 549 nodeslist = [row[0] for row in nodesdata] 550 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s", 551 uri_fields=("id", "uri"))
552
553 554 -class R_2_nodes_name(baserlib.OpcodeResource):
555 """/2/nodes/[node_name] resource. 556 557 """ 558 GET_ALIASES = { 559 "sip": "secondary_ip", 560 } 561
562 - def GET(self):
563 """Send information about a node. 564 565 """ 566 node_name = self.items[0] 567 client = self.GetClient() 568 569 result = baserlib.HandleItemQueryErrors(client.QueryNodes, 570 names=[node_name], fields=N_FIELDS, 571 use_locking=self.useLocking()) 572 573 return baserlib.MapFields(N_FIELDS, result[0])
574
575 576 -class R_2_nodes_name_powercycle(baserlib.OpcodeResource):
577 """/2/nodes/[node_name]/powercycle resource. 578 579 """ 580 POST_OPCODE = opcodes.OpNodePowercycle 581
582 - def GetPostOpInput(self):
583 """Tries to powercycle a node. 584 585 """ 586 return (self.request_body, { 587 "node_name": self.items[0], 588 "force": self.useForce(), 589 })
590
591 592 -class R_2_nodes_name_role(baserlib.OpcodeResource):
593 """/2/nodes/[node_name]/role resource. 594 595 """ 596 PUT_OPCODE = opcodes.OpNodeSetParams 597
598 - def GET(self):
599 """Returns the current node role. 600 601 @return: Node role 602 603 """ 604 node_name = self.items[0] 605 client = self.GetClient() 606 result = client.QueryNodes(names=[node_name], fields=["role"], 607 use_locking=self.useLocking()) 608 609 return _NR_MAP[result[0][0]]
610
611 - def GetPutOpInput(self):
612 """Sets the node role. 613 614 """ 615 baserlib.CheckType(self.request_body, basestring, "Body contents") 616 617 role = self.request_body 618 619 if role == _NR_REGULAR: 620 candidate = False 621 offline = False 622 drained = False 623 624 elif role == _NR_MASTER_CANDIDATE: 625 candidate = True 626 offline = drained = None 627 628 elif role == _NR_DRAINED: 629 drained = True 630 candidate = offline = None 631 632 elif role == _NR_OFFLINE: 633 offline = True 634 candidate = drained = None 635 636 else: 637 raise http.HttpBadRequest("Can't set '%s' role" % role) 638 639 assert len(self.items) == 1 640 641 return ({}, { 642 "node_name": self.items[0], 643 "master_candidate": candidate, 644 "offline": offline, 645 "drained": drained, 646 "force": self.useForce(), 647 "auto_promote": bool(self._checkIntVariable("auto-promote", default=0)), 648 })
649
650 651 -class R_2_nodes_name_evacuate(baserlib.OpcodeResource):
652 """/2/nodes/[node_name]/evacuate resource. 653 654 """ 655 POST_OPCODE = opcodes.OpNodeEvacuate 656
657 - def GetPostOpInput(self):
658 """Evacuate all instances off a node. 659 660 """ 661 return (self.request_body, { 662 "node_name": self.items[0], 663 "dry_run": self.dryRun(), 664 })
665
666 667 -class R_2_nodes_name_migrate(baserlib.OpcodeResource):
668 """/2/nodes/[node_name]/migrate resource. 669 670 """ 671 POST_OPCODE = opcodes.OpNodeMigrate 672
673 - def GetPostOpInput(self):
674 """Migrate all primary instances from a node. 675 676 """ 677 if self.queryargs: 678 # Support old-style requests 679 if "live" in self.queryargs and "mode" in self.queryargs: 680 raise http.HttpBadRequest("Only one of 'live' and 'mode' should" 681 " be passed") 682 683 if "live" in self.queryargs: 684 if self._checkIntVariable("live", default=1): 685 mode = constants.HT_MIGRATION_LIVE 686 else: 687 mode = constants.HT_MIGRATION_NONLIVE 688 else: 689 mode = self._checkStringVariable("mode", default=None) 690 691 data = { 692 "mode": mode, 693 } 694 else: 695 data = self.request_body 696 697 return (data, { 698 "node_name": self.items[0], 699 })
700
701 702 -class R_2_nodes_name_modify(baserlib.OpcodeResource):
703 """/2/nodes/[node_name]/modify resource. 704 705 """ 706 POST_OPCODE = opcodes.OpNodeSetParams 707
708 - def GetPostOpInput(self):
709 """Changes parameters of a node. 710 711 """ 712 assert len(self.items) == 1 713 714 return (self.request_body, { 715 "node_name": self.items[0], 716 })
717
718 719 -class R_2_nodes_name_storage(baserlib.OpcodeResource):
720 """/2/nodes/[node_name]/storage resource. 721 722 """ 723 # LUNodeQueryStorage acquires locks, hence restricting access to GET 724 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 725 GET_OPCODE = opcodes.OpNodeQueryStorage 726
727 - def GetGetOpInput(self):
728 """List storage available on a node. 729 730 """ 731 storage_type = self._checkStringVariable("storage_type", None) 732 output_fields = self._checkStringVariable("output_fields", None) 733 734 if not output_fields: 735 raise http.HttpBadRequest("Missing the required 'output_fields'" 736 " parameter") 737 738 return ({}, { 739 "nodes": [self.items[0]], 740 "storage_type": storage_type, 741 "output_fields": output_fields.split(","), 742 })
743
744 745 -class R_2_nodes_name_storage_modify(baserlib.OpcodeResource):
746 """/2/nodes/[node_name]/storage/modify resource. 747 748 """ 749 PUT_OPCODE = opcodes.OpNodeModifyStorage 750
751 - def GetPutOpInput(self):
752 """Modifies a storage volume on a node. 753 754 """ 755 storage_type = self._checkStringVariable("storage_type", None) 756 name = self._checkStringVariable("name", None) 757 758 if not name: 759 raise http.HttpBadRequest("Missing the required 'name'" 760 " parameter") 761 762 changes = {} 763 764 if "allocatable" in self.queryargs: 765 changes[constants.SF_ALLOCATABLE] = \ 766 bool(self._checkIntVariable("allocatable", default=1)) 767 768 return ({}, { 769 "node_name": self.items[0], 770 "storage_type": storage_type, 771 "name": name, 772 "changes": changes, 773 })
774
775 776 -class R_2_nodes_name_storage_repair(baserlib.OpcodeResource):
777 """/2/nodes/[node_name]/storage/repair resource. 778 779 """ 780 PUT_OPCODE = opcodes.OpRepairNodeStorage 781
782 - def GetPutOpInput(self):
783 """Repairs a storage volume on a node. 784 785 """ 786 storage_type = self._checkStringVariable("storage_type", None) 787 name = self._checkStringVariable("name", None) 788 if not name: 789 raise http.HttpBadRequest("Missing the required 'name'" 790 " parameter") 791 792 return ({}, { 793 "node_name": self.items[0], 794 "storage_type": storage_type, 795 "name": name, 796 })
797
798 799 -class R_2_networks(baserlib.OpcodeResource):
800 """/2/networks resource. 801 802 """ 803 POST_OPCODE = opcodes.OpNetworkAdd 804 POST_RENAME = { 805 "name": "network_name", 806 } 807
808 - def GetPostOpInput(self):
809 """Create a network. 810 811 """ 812 assert not self.items 813 return (self.request_body, { 814 "dry_run": self.dryRun(), 815 })
816
817 - def GET(self):
818 """Returns a list of all networks. 819 820 """ 821 client = self.GetClient() 822 823 if self.useBulk(): 824 bulkdata = client.QueryNetworks([], NET_FIELDS, False) 825 return baserlib.MapBulkFields(bulkdata, NET_FIELDS) 826 else: 827 data = client.QueryNetworks([], ["name"], False) 828 networknames = [row[0] for row in data] 829 return baserlib.BuildUriList(networknames, "/2/networks/%s", 830 uri_fields=("name", "uri"))
831
832 833 -class R_2_networks_name(baserlib.OpcodeResource):
834 """/2/networks/[network_name] resource. 835 836 """ 837 DELETE_OPCODE = opcodes.OpNetworkRemove 838
839 - def GET(self):
840 """Send information about a network. 841 842 """ 843 network_name = self.items[0] 844 client = self.GetClient() 845 846 result = baserlib.HandleItemQueryErrors(client.QueryNetworks, 847 names=[network_name], 848 fields=NET_FIELDS, 849 use_locking=self.useLocking()) 850 851 return baserlib.MapFields(NET_FIELDS, result[0])
852
853 - def GetDeleteOpInput(self):
854 """Delete a network. 855 856 """ 857 assert len(self.items) == 1 858 return (self.request_body, { 859 "network_name": self.items[0], 860 "dry_run": self.dryRun(), 861 })
862
863 864 -class R_2_networks_name_connect(baserlib.OpcodeResource):
865 """/2/networks/[network_name]/connect resource. 866 867 """ 868 PUT_OPCODE = opcodes.OpNetworkConnect 869
870 - def GetPutOpInput(self):
871 """Changes some parameters of node group. 872 873 """ 874 assert self.items 875 return (self.request_body, { 876 "network_name": self.items[0], 877 "dry_run": self.dryRun(), 878 })
879
880 881 -class R_2_networks_name_disconnect(baserlib.OpcodeResource):
882 """/2/networks/[network_name]/disconnect resource. 883 884 """ 885 PUT_OPCODE = opcodes.OpNetworkDisconnect 886
887 - def GetPutOpInput(self):
888 """Changes some parameters of node group. 889 890 """ 891 assert self.items 892 return (self.request_body, { 893 "network_name": self.items[0], 894 "dry_run": self.dryRun(), 895 })
896
897 898 -class R_2_networks_name_modify(baserlib.OpcodeResource):
899 """/2/networks/[network_name]/modify resource. 900 901 """ 902 PUT_OPCODE = opcodes.OpNetworkSetParams 903
904 - def GetPutOpInput(self):
905 """Changes some parameters of network. 906 907 """ 908 assert self.items 909 return (self.request_body, { 910 "network_name": self.items[0], 911 })
912
913 914 -class R_2_groups(baserlib.OpcodeResource):
915 """/2/groups resource. 916 917 """ 918 POST_OPCODE = opcodes.OpGroupAdd 919 POST_RENAME = { 920 "name": "group_name", 921 } 922
923 - def GetPostOpInput(self):
924 """Create a node group. 925 926 927 """ 928 assert not self.items 929 return (self.request_body, { 930 "dry_run": self.dryRun(), 931 })
932
933 - def GET(self):
934 """Returns a list of all node groups. 935 936 """ 937 client = self.GetClient() 938 939 if self.useBulk(): 940 bulkdata = client.QueryGroups([], G_FIELDS, False) 941 return baserlib.MapBulkFields(bulkdata, G_FIELDS) 942 else: 943 data = client.QueryGroups([], ["name"], False) 944 groupnames = [row[0] for row in data] 945 return baserlib.BuildUriList(groupnames, "/2/groups/%s", 946 uri_fields=("name", "uri"))
947
948 949 -class R_2_groups_name(baserlib.OpcodeResource):
950 """/2/groups/[group_name] resource. 951 952 """ 953 DELETE_OPCODE = opcodes.OpGroupRemove 954
955 - def GET(self):
956 """Send information about a node group. 957 958 """ 959 group_name = self.items[0] 960 client = self.GetClient() 961 962 result = baserlib.HandleItemQueryErrors(client.QueryGroups, 963 names=[group_name], fields=G_FIELDS, 964 use_locking=self.useLocking()) 965 966 return baserlib.MapFields(G_FIELDS, result[0])
967
968 - def GetDeleteOpInput(self):
969 """Delete a node group. 970 971 """ 972 assert len(self.items) == 1 973 return ({}, { 974 "group_name": self.items[0], 975 "dry_run": self.dryRun(), 976 })
977
978 979 -class R_2_groups_name_modify(baserlib.OpcodeResource):
980 """/2/groups/[group_name]/modify resource. 981 982 """ 983 PUT_OPCODE = opcodes.OpGroupSetParams 984 PUT_RENAME = { 985 "custom_ndparams": "ndparams", 986 "custom_ipolicy": "ipolicy", 987 "custom_diskparams": "diskparams", 988 } 989
990 - def GetPutOpInput(self):
991 """Changes some parameters of node group. 992 993 """ 994 assert self.items 995 return (self.request_body, { 996 "group_name": self.items[0], 997 })
998
999 1000 -class R_2_groups_name_rename(baserlib.OpcodeResource):
1001 """/2/groups/[group_name]/rename resource. 1002 1003 """ 1004 PUT_OPCODE = opcodes.OpGroupRename 1005
1006 - def GetPutOpInput(self):
1007 """Changes the name of a node group. 1008 1009 """ 1010 assert len(self.items) == 1 1011 return (self.request_body, { 1012 "group_name": self.items[0], 1013 "dry_run": self.dryRun(), 1014 })
1015
1016 1017 -class R_2_groups_name_assign_nodes(baserlib.OpcodeResource):
1018 """/2/groups/[group_name]/assign-nodes resource. 1019 1020 """ 1021 PUT_OPCODE = opcodes.OpGroupAssignNodes 1022
1023 - def GetPutOpInput(self):
1024 """Assigns nodes to a group. 1025 1026 """ 1027 assert len(self.items) == 1 1028 return (self.request_body, { 1029 "group_name": self.items[0], 1030 "dry_run": self.dryRun(), 1031 "force": self.useForce(), 1032 })
1033
1034 1035 -def _ConvertUsbDevices(data):
1036 """Convert in place the usb_devices string to the proper format. 1037 1038 In Ganeti 2.8.4 the separator for the usb_devices hvparam was changed from 1039 comma to space because commas cannot be accepted on the command line 1040 (they already act as the separator between different hvparams). RAPI 1041 should be able to accept commas for backwards compatibility, but we want 1042 it to also accept the new space separator. Therefore, we convert 1043 spaces into commas here and keep the old parsing logic elsewhere. 1044 1045 """ 1046 try: 1047 hvparams = data["hvparams"] 1048 usb_devices = hvparams[constants.HV_USB_DEVICES] 1049 hvparams[constants.HV_USB_DEVICES] = usb_devices.replace(" ", ",") 1050 data["hvparams"] = hvparams 1051 except KeyError: 1052 #No usb_devices, no modification required 1053 pass
1054
1055 1056 -class R_2_instances(baserlib.OpcodeResource):
1057 """/2/instances resource. 1058 1059 """ 1060 POST_OPCODE = opcodes.OpInstanceCreate 1061 POST_RENAME = { 1062 "os": "os_type", 1063 "name": "instance_name", 1064 } 1065
1066 - def GET(self):
1067 """Returns a list of all available instances. 1068 1069 """ 1070 client = self.GetClient() 1071 1072 use_locking = self.useLocking() 1073 if self.useBulk(): 1074 bulkdata = client.QueryInstances([], I_FIELDS, use_locking) 1075 return map(_UpdateBeparams, baserlib.MapBulkFields(bulkdata, I_FIELDS)) 1076 else: 1077 instancesdata = client.QueryInstances([], ["name"], use_locking) 1078 instanceslist = [row[0] for row in instancesdata] 1079 return baserlib.BuildUriList(instanceslist, "/2/instances/%s", 1080 uri_fields=("id", "uri"))
1081
1082 - def GetPostOpInput(self):
1083 """Create an instance. 1084 1085 @return: a job id 1086 1087 """ 1088 baserlib.CheckType(self.request_body, dict, "Body contents") 1089 1090 # Default to request data version 0 1091 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0) 1092 1093 if data_version == 0: 1094 raise http.HttpBadRequest("Instance creation request version 0 is no" 1095 " longer supported") 1096 elif data_version != 1: 1097 raise http.HttpBadRequest("Unsupported request data version %s" % 1098 data_version) 1099 1100 data = self.request_body.copy() 1101 # Remove "__version__" 1102 data.pop(_REQ_DATA_VERSION, None) 1103 1104 _ConvertUsbDevices(data) 1105 1106 return (data, { 1107 "dry_run": self.dryRun(), 1108 })
1109
1110 1111 -class R_2_instances_multi_alloc(baserlib.OpcodeResource):
1112 """/2/instances-multi-alloc resource. 1113 1114 """ 1115 POST_OPCODE = opcodes.OpInstanceMultiAlloc 1116
1117 - def GetPostOpInput(self):
1118 """Try to allocate multiple instances. 1119 1120 @return: A dict with submitted jobs, allocatable instances and failed 1121 allocations 1122 1123 """ 1124 if "instances" not in self.request_body: 1125 raise http.HttpBadRequest("Request is missing required 'instances' field" 1126 " in body") 1127 1128 # Unlike most other RAPI calls, this one is composed of individual opcodes, 1129 # and we have to do the filling ourselves 1130 OPCODE_RENAME = { 1131 "os": "os_type", 1132 "name": "instance_name", 1133 } 1134 1135 body = objects.FillDict(self.request_body, { 1136 "instances": [ 1137 baserlib.FillOpcode(opcodes.OpInstanceCreate, inst, {}, 1138 rename=OPCODE_RENAME) 1139 for inst in self.request_body["instances"] 1140 ], 1141 }) 1142 1143 return (body, { 1144 "dry_run": self.dryRun(), 1145 })
1146
1147 1148 -class R_2_instances_name(baserlib.OpcodeResource):
1149 """/2/instances/[instance_name] resource. 1150 1151 """ 1152 DELETE_OPCODE = opcodes.OpInstanceRemove 1153
1154 - def GET(self):
1155 """Send information about an instance. 1156 1157 """ 1158 client = self.GetClient() 1159 instance_name = self.items[0] 1160 1161 result = baserlib.HandleItemQueryErrors(client.QueryInstances, 1162 names=[instance_name], 1163 fields=I_FIELDS, 1164 use_locking=self.useLocking()) 1165 1166 return _UpdateBeparams(baserlib.MapFields(I_FIELDS, result[0]))
1167
1168 - def GetDeleteOpInput(self):
1169 """Delete an instance. 1170 1171 """ 1172 assert len(self.items) == 1 1173 return (self.request_body, { 1174 "instance_name": self.items[0], 1175 "ignore_failures": False, 1176 "dry_run": self.dryRun(), 1177 })
1178
1179 1180 -class R_2_instances_name_info(baserlib.OpcodeResource):
1181 """/2/instances/[instance_name]/info resource. 1182 1183 """ 1184 GET_OPCODE = opcodes.OpInstanceQueryData 1185
1186 - def GetGetOpInput(self):
1187 """Request detailed instance information. 1188 1189 """ 1190 assert len(self.items) == 1 1191 return ({}, { 1192 "instances": [self.items[0]], 1193 "static": bool(self._checkIntVariable("static", default=0)), 1194 })
1195
1196 1197 -class R_2_instances_name_reboot(baserlib.OpcodeResource):
1198 """/2/instances/[instance_name]/reboot resource. 1199 1200 Implements an instance reboot. 1201 1202 """ 1203 POST_OPCODE = opcodes.OpInstanceReboot 1204
1205 - def GetPostOpInput(self):
1206 """Reboot an instance. 1207 1208 The URI takes type=[hard|soft|full] and 1209 ignore_secondaries=[False|True] parameters. 1210 1211 """ 1212 return (self.request_body, { 1213 "instance_name": self.items[0], 1214 "reboot_type": 1215 self.queryargs.get("type", [constants.INSTANCE_REBOOT_HARD])[0], 1216 "ignore_secondaries": bool(self._checkIntVariable("ignore_secondaries")), 1217 "dry_run": self.dryRun(), 1218 })
1219
1220 1221 -class R_2_instances_name_startup(baserlib.OpcodeResource):
1222 """/2/instances/[instance_name]/startup resource. 1223 1224 Implements an instance startup. 1225 1226 """ 1227 PUT_OPCODE = opcodes.OpInstanceStartup 1228
1229 - def GetPutOpInput(self):
1230 """Startup an instance. 1231 1232 The URI takes force=[False|True] parameter to start the instance 1233 if even if secondary disks are failing. 1234 1235 """ 1236 return ({}, { 1237 "instance_name": self.items[0], 1238 "force": self.useForce(), 1239 "dry_run": self.dryRun(), 1240 "no_remember": bool(self._checkIntVariable("no_remember")), 1241 })
1242
1243 1244 -class R_2_instances_name_shutdown(baserlib.OpcodeResource):
1245 """/2/instances/[instance_name]/shutdown resource. 1246 1247 Implements an instance shutdown. 1248 1249 """ 1250 PUT_OPCODE = opcodes.OpInstanceShutdown 1251
1252 - def GetPutOpInput(self):
1253 """Shutdown an instance. 1254 1255 """ 1256 return (self.request_body, { 1257 "instance_name": self.items[0], 1258 "no_remember": bool(self._checkIntVariable("no_remember")), 1259 "dry_run": self.dryRun(), 1260 })
1261
1262 1263 -def _ParseInstanceReinstallRequest(name, data):
1264 """Parses a request for reinstalling an instance. 1265 1266 """ 1267 if not isinstance(data, dict): 1268 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 1269 1270 ostype = baserlib.CheckParameter(data, "os", default=None) 1271 start = baserlib.CheckParameter(data, "start", exptype=bool, 1272 default=True) 1273 osparams = baserlib.CheckParameter(data, "osparams", default=None) 1274 1275 ops = [ 1276 opcodes.OpInstanceShutdown(instance_name=name), 1277 opcodes.OpInstanceReinstall(instance_name=name, os_type=ostype, 1278 osparams=osparams), 1279 ] 1280 1281 if start: 1282 ops.append(opcodes.OpInstanceStartup(instance_name=name, force=False)) 1283 1284 return ops
1285
1286 1287 -class R_2_instances_name_reinstall(baserlib.OpcodeResource):
1288 """/2/instances/[instance_name]/reinstall resource. 1289 1290 Implements an instance reinstall. 1291 1292 """ 1293 POST_OPCODE = opcodes.OpInstanceReinstall 1294
1295 - def POST(self):
1296 """Reinstall an instance. 1297 1298 The URI takes os=name and nostartup=[0|1] optional 1299 parameters. By default, the instance will be started 1300 automatically. 1301 1302 """ 1303 if self.request_body: 1304 if self.queryargs: 1305 raise http.HttpBadRequest("Can't combine query and body parameters") 1306 1307 body = self.request_body 1308 elif self.queryargs: 1309 # Legacy interface, do not modify/extend 1310 body = { 1311 "os": self._checkStringVariable("os"), 1312 "start": not self._checkIntVariable("nostartup"), 1313 } 1314 else: 1315 body = {} 1316 1317 ops = _ParseInstanceReinstallRequest(self.items[0], body) 1318 1319 return self.SubmitJob(ops)
1320
1321 1322 -class R_2_instances_name_replace_disks(baserlib.OpcodeResource):
1323 """/2/instances/[instance_name]/replace-disks resource. 1324 1325 """ 1326 POST_OPCODE = opcodes.OpInstanceReplaceDisks 1327
1328 - def GetPostOpInput(self):
1329 """Replaces disks on an instance. 1330 1331 """ 1332 static = { 1333 "instance_name": self.items[0], 1334 } 1335 1336 if self.request_body: 1337 data = self.request_body 1338 elif self.queryargs: 1339 # Legacy interface, do not modify/extend 1340 data = { 1341 "remote_node": self._checkStringVariable("remote_node", default=None), 1342 "mode": self._checkStringVariable("mode", default=None), 1343 "disks": self._checkStringVariable("disks", default=None), 1344 "iallocator": self._checkStringVariable("iallocator", default=None), 1345 } 1346 else: 1347 data = {} 1348 1349 # Parse disks 1350 try: 1351 raw_disks = data.pop("disks") 1352 except KeyError: 1353 pass 1354 else: 1355 if raw_disks: 1356 if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102 1357 data["disks"] = raw_disks 1358 else: 1359 # Backwards compatibility for strings of the format "1, 2, 3" 1360 try: 1361 data["disks"] = [int(part) for part in raw_disks.split(",")] 1362 except (TypeError, ValueError), err: 1363 raise http.HttpBadRequest("Invalid disk index passed: %s" % err) 1364 1365 return (data, static)
1366
1367 1368 -class R_2_instances_name_activate_disks(baserlib.OpcodeResource):
1369 """/2/instances/[instance_name]/activate-disks resource. 1370 1371 """ 1372 PUT_OPCODE = opcodes.OpInstanceActivateDisks 1373
1374 - def GetPutOpInput(self):
1375 """Activate disks for an instance. 1376 1377 The URI might contain ignore_size to ignore current recorded size. 1378 1379 """ 1380 return ({}, { 1381 "instance_name": self.items[0], 1382 "ignore_size": bool(self._checkIntVariable("ignore_size")), 1383 })
1384
1385 1386 -class R_2_instances_name_deactivate_disks(baserlib.OpcodeResource):
1387 """/2/instances/[instance_name]/deactivate-disks resource. 1388 1389 """ 1390 PUT_OPCODE = opcodes.OpInstanceDeactivateDisks 1391
1392 - def GetPutOpInput(self):
1393 """Deactivate disks for an instance. 1394 1395 """ 1396 return ({}, { 1397 "instance_name": self.items[0], 1398 })
1399
1400 1401 -class R_2_instances_name_recreate_disks(baserlib.OpcodeResource):
1402 """/2/instances/[instance_name]/recreate-disks resource. 1403 1404 """ 1405 POST_OPCODE = opcodes.OpInstanceRecreateDisks 1406
1407 - def GetPostOpInput(self):
1408 """Recreate disks for an instance. 1409 1410 """ 1411 return (self.request_body, { 1412 "instance_name": self.items[0], 1413 })
1414
1415 1416 -class R_2_instances_name_prepare_export(baserlib.OpcodeResource):
1417 """/2/instances/[instance_name]/prepare-export resource. 1418 1419 """ 1420 PUT_OPCODE = opcodes.OpBackupPrepare 1421
1422 - def GetPutOpInput(self):
1423 """Prepares an export for an instance. 1424 1425 """ 1426 return ({}, { 1427 "instance_name": self.items[0], 1428 "mode": self._checkStringVariable("mode"), 1429 })
1430
1431 1432 -class R_2_instances_name_export(baserlib.OpcodeResource):
1433 """/2/instances/[instance_name]/export resource. 1434 1435 """ 1436 PUT_OPCODE = opcodes.OpBackupExport 1437 PUT_RENAME = { 1438 "destination": "target_node", 1439 } 1440
1441 - def GetPutOpInput(self):
1442 """Exports an instance. 1443 1444 """ 1445 return (self.request_body, { 1446 "instance_name": self.items[0], 1447 })
1448
1449 1450 -class R_2_instances_name_migrate(baserlib.OpcodeResource):
1451 """/2/instances/[instance_name]/migrate resource. 1452 1453 """ 1454 PUT_OPCODE = opcodes.OpInstanceMigrate 1455
1456 - def GetPutOpInput(self):
1457 """Migrates an instance. 1458 1459 """ 1460 return (self.request_body, { 1461 "instance_name": self.items[0], 1462 })
1463
1464 1465 -class R_2_instances_name_failover(baserlib.OpcodeResource):
1466 """/2/instances/[instance_name]/failover resource. 1467 1468 """ 1469 PUT_OPCODE = opcodes.OpInstanceFailover 1470
1471 - def GetPutOpInput(self):
1472 """Does a failover of an instance. 1473 1474 """ 1475 return (self.request_body, { 1476 "instance_name": self.items[0], 1477 })
1478
1479 1480 -class R_2_instances_name_rename(baserlib.OpcodeResource):
1481 """/2/instances/[instance_name]/rename resource. 1482 1483 """ 1484 PUT_OPCODE = opcodes.OpInstanceRename 1485
1486 - def GetPutOpInput(self):
1487 """Changes the name of an instance. 1488 1489 """ 1490 return (self.request_body, { 1491 "instance_name": self.items[0], 1492 })
1493
1494 1495 -class R_2_instances_name_modify(baserlib.OpcodeResource):
1496 """/2/instances/[instance_name]/modify resource. 1497 1498 """ 1499 PUT_OPCODE = opcodes.OpInstanceSetParams 1500 PUT_RENAME = { 1501 "custom_beparams": "beparams", 1502 "custom_hvparams": "hvparams", 1503 } 1504
1505 - def GetPutOpInput(self):
1506 """Changes parameters of an instance. 1507 1508 """ 1509 data = self.request_body.copy() 1510 _ConvertUsbDevices(data) 1511 1512 return (data, { 1513 "instance_name": self.items[0], 1514 })
1515
1516 1517 -class R_2_instances_name_disk_grow(baserlib.OpcodeResource):
1518 """/2/instances/[instance_name]/disk/[disk_index]/grow resource. 1519 1520 """ 1521 POST_OPCODE = opcodes.OpInstanceGrowDisk 1522
1523 - def GetPostOpInput(self):
1524 """Increases the size of an instance disk. 1525 1526 """ 1527 return (self.request_body, { 1528 "instance_name": self.items[0], 1529 "disk": int(self.items[1]), 1530 })
1531
1532 1533 -class R_2_instances_name_console(baserlib.ResourceBase):
1534 """/2/instances/[instance_name]/console resource. 1535 1536 """ 1537 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ] 1538 GET_OPCODE = opcodes.OpInstanceConsole 1539
1540 - def GET(self):
1541 """Request information for connecting to instance's console. 1542 1543 @return: Serialized instance console description, see 1544 L{objects.InstanceConsole} 1545 1546 """ 1547 instance_name = self.items[0] 1548 client = self.GetClient() 1549 1550 ((console, oper_state), ) = \ 1551 client.QueryInstances([instance_name], ["console", "oper_state"], False) 1552 1553 if not oper_state: 1554 raise http.HttpServiceUnavailable("Instance console unavailable") 1555 1556 assert isinstance(console, dict) 1557 return console
1558
1559 1560 -def _GetQueryFields(args):
1561 """Tries to extract C{fields} query parameter. 1562 1563 @type args: dictionary 1564 @rtype: list of string 1565 @raise http.HttpBadRequest: When parameter can't be found 1566 1567 """ 1568 try: 1569 fields = args["fields"] 1570 except KeyError: 1571 raise http.HttpBadRequest("Missing 'fields' query argument") 1572 1573 return _SplitQueryFields(fields[0])
1574
1575 1576 -def _SplitQueryFields(fields):
1577 """Splits fields as given for a query request. 1578 1579 @type fields: string 1580 @rtype: list of string 1581 1582 """ 1583 return [i.strip() for i in fields.split(",")]
1584
1585 1586 -class R_2_query(baserlib.ResourceBase):
1587 """/2/query/[resource] resource. 1588 1589 """ 1590 # Results might contain sensitive information 1591 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ] 1592 PUT_ACCESS = GET_ACCESS 1593 GET_OPCODE = opcodes.OpQuery 1594 PUT_OPCODE = opcodes.OpQuery 1595
1596 - def _Query(self, fields, qfilter):
1597 client = self.GetClient() 1598 return client.Query(self.items[0], fields, qfilter).ToDict()
1599
1600 - def GET(self):
1601 """Returns resource information. 1602 1603 @return: Query result, see L{objects.QueryResponse} 1604 1605 """ 1606 return self._Query(_GetQueryFields(self.queryargs), None)
1607
1608 - def PUT(self):
1609 """Submits job querying for resources. 1610 1611 @return: Query result, see L{objects.QueryResponse} 1612 1613 """ 1614 body = self.request_body 1615 1616 baserlib.CheckType(body, dict, "Body contents") 1617 1618 try: 1619 fields = body["fields"] 1620 except KeyError: 1621 fields = _GetQueryFields(self.queryargs) 1622 1623 qfilter = body.get("qfilter", None) 1624 # TODO: remove this after 2.7 1625 if qfilter is None: 1626 qfilter = body.get("filter", None) 1627 1628 return self._Query(fields, qfilter)
1629
1630 1631 -class R_2_query_fields(baserlib.ResourceBase):
1632 """/2/query/[resource]/fields resource. 1633 1634 """ 1635 GET_OPCODE = opcodes.OpQueryFields 1636
1637 - def GET(self):
1638 """Retrieves list of available fields for a resource. 1639 1640 @return: List of serialized L{objects.QueryFieldDefinition} 1641 1642 """ 1643 try: 1644 raw_fields = self.queryargs["fields"] 1645 except KeyError: 1646 fields = None 1647 else: 1648 fields = _SplitQueryFields(raw_fields[0]) 1649 1650 return self.GetClient().QueryFields(self.items[0], fields).ToDict()
1651
1652 1653 -class _R_Tags(baserlib.OpcodeResource):
1654 """Quasiclass for tagging resources. 1655 1656 Manages tags. When inheriting this class you must define the 1657 TAG_LEVEL for it. 1658 1659 """ 1660 TAG_LEVEL = None 1661 GET_OPCODE = opcodes.OpTagsGet 1662 PUT_OPCODE = opcodes.OpTagsSet 1663 DELETE_OPCODE = opcodes.OpTagsDel 1664
1665 - def __init__(self, items, queryargs, req, **kwargs):
1666 """A tag resource constructor. 1667 1668 We have to override the default to sort out cluster naming case. 1669 1670 """ 1671 baserlib.OpcodeResource.__init__(self, items, queryargs, req, **kwargs) 1672 1673 if self.TAG_LEVEL == constants.TAG_CLUSTER: 1674 self.name = None 1675 else: 1676 self.name = items[0]
1677
1678 - def GET(self):
1679 """Returns a list of tags. 1680 1681 Example: ["tag1", "tag2", "tag3"] 1682 1683 """ 1684 kind = self.TAG_LEVEL 1685 1686 if kind in constants.VALID_TAG_TYPES: 1687 cl = self.GetClient() 1688 if kind == constants.TAG_CLUSTER: 1689 if self.name: 1690 raise http.HttpBadRequest("Can't specify a name" 1691 " for cluster tag request") 1692 tags = list(cl.QueryTags(kind, "")) 1693 else: 1694 if not self.name: 1695 raise http.HttpBadRequest("Missing name on tag request") 1696 tags = list(cl.QueryTags(kind, self.name)) 1697 1698 else: 1699 raise http.HttpBadRequest("Unhandled tag type!") 1700 1701 return list(tags)
1702
1703 - def GetPutOpInput(self):
1704 """Add a set of tags. 1705 1706 The request as a list of strings should be PUT to this URI. And 1707 you'll have back a job id. 1708 1709 """ 1710 return ({}, { 1711 "kind": self.TAG_LEVEL, 1712 "name": self.name, 1713 "tags": self.queryargs.get("tag", []), 1714 "dry_run": self.dryRun(), 1715 })
1716
1717 - def GetDeleteOpInput(self):
1718 """Delete a tag. 1719 1720 In order to delete a set of tags, the DELETE 1721 request should be addressed to URI like: 1722 /tags?tag=[tag]&tag=[tag] 1723 1724 """ 1725 # Re-use code 1726 return self.GetPutOpInput()
1727
1728 1729 -class R_2_instances_name_tags(_R_Tags):
1730 """ /2/instances/[instance_name]/tags resource. 1731 1732 Manages per-instance tags. 1733 1734 """ 1735 TAG_LEVEL = constants.TAG_INSTANCE
1736
1737 1738 -class R_2_nodes_name_tags(_R_Tags):
1739 """ /2/nodes/[node_name]/tags resource. 1740 1741 Manages per-node tags. 1742 1743 """ 1744 TAG_LEVEL = constants.TAG_NODE
1745
1746 1747 -class R_2_groups_name_tags(_R_Tags):
1748 """ /2/groups/[group_name]/tags resource. 1749 1750 Manages per-nodegroup tags. 1751 1752 """ 1753 TAG_LEVEL = constants.TAG_NODEGROUP
1754
1755 1756 -class R_2_networks_name_tags(_R_Tags):
1757 """ /2/networks/[network_name]/tags resource. 1758 1759 Manages per-network tags. 1760 1761 """ 1762 TAG_LEVEL = constants.TAG_NETWORK
1763
1764 1765 -class R_2_tags(_R_Tags):
1766 """ /2/tags resource. 1767 1768 Manages cluster tags. 1769 1770 """ 1771 TAG_LEVEL = constants.TAG_CLUSTER
1772