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 Google Inc. 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify 
   7  # it under the terms of the GNU General Public License as published by 
   8  # the Free Software Foundation; either version 2 of the License, or 
   9  # (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, but 
  12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  14  # General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """Remote API resource implementations. 
  23   
  24  PUT or POST? 
  25  ============ 
  26   
  27  According to RFC2616 the main difference between PUT and POST is that 
  28  POST can create new resources but PUT can only create the resource the 
  29  URI was pointing to on the PUT request. 
  30   
  31  In the context of this module POST on ``/2/instances`` to change an existing 
  32  entity is legitimate, while PUT would not be. PUT creates a new entity (e.g. a 
  33  new instance) with a name specified in the request. 
  34   
  35  Quoting from RFC2616, section 9.6:: 
  36   
  37    The fundamental difference between the POST and PUT requests is reflected in 
  38    the different meaning of the Request-URI. The URI in a POST request 
  39    identifies the resource that will handle the enclosed entity. That resource 
  40    might be a data-accepting process, a gateway to some other protocol, or a 
  41    separate entity that accepts annotations. In contrast, the URI in a PUT 
  42    request identifies the entity enclosed with the request -- the user agent 
  43    knows what URI is intended and the server MUST NOT attempt to apply the 
  44    request to some other resource. If the server desires that the request be 
  45    applied to a different URI, it MUST send a 301 (Moved Permanently) response; 
  46    the user agent MAY then make its own decision regarding whether or not to 
  47    redirect the request. 
  48   
  49  So when adding new methods, if they are operating on the URI entity itself, 
  50  PUT should be prefered over POST. 
  51   
  52  """ 
  53   
  54  # pylint: disable=C0103 
  55   
  56  # C0103: Invalid name, since the R_* names are not conforming 
  57   
  58  from ganeti import opcodes 
  59  from ganeti import http 
  60  from ganeti import constants 
  61  from ganeti import cli 
  62  from ganeti import rapi 
  63  from ganeti import ht 
  64  from ganeti import compat 
  65  from ganeti.rapi import baserlib 
  66   
  67   
  68  _COMMON_FIELDS = ["ctime", "mtime", "uuid", "serial_no", "tags"] 
  69  I_FIELDS = ["name", "admin_state", "os", 
  70              "pnode", "snodes", 
  71              "disk_template", 
  72              "nic.ips", "nic.macs", "nic.modes", "nic.links", "nic.bridges", 
  73              "network_port", 
  74              "disk.sizes", "disk_usage", 
  75              "beparams", "hvparams", 
  76              "oper_state", "oper_ram", "oper_vcpus", "status", 
  77              "custom_hvparams", "custom_beparams", "custom_nicparams", 
  78              ] + _COMMON_FIELDS 
  79   
  80  N_FIELDS = ["name", "offline", "master_candidate", "drained", 
  81              "dtotal", "dfree", 
  82              "mtotal", "mnode", "mfree", 
  83              "pinst_cnt", "sinst_cnt", 
  84              "ctotal", "cnodes", "csockets", 
  85              "pip", "sip", "role", 
  86              "pinst_list", "sinst_list", 
  87              "master_capable", "vm_capable", 
  88              "group.uuid", 
  89              ] + _COMMON_FIELDS 
  90   
  91  G_FIELDS = [ 
  92    "alloc_policy", 
  93    "name", 
  94    "node_cnt", 
  95    "node_list", 
  96    ] + _COMMON_FIELDS 
  97   
  98  J_FIELDS_BULK = [ 
  99    "id", "ops", "status", "summary", 
 100    "opstatus", 
 101    "received_ts", "start_ts", "end_ts", 
 102    ] 
 103   
 104  J_FIELDS = J_FIELDS_BULK + [ 
 105    "oplog", 
 106    "opresult", 
 107    ] 
 108   
 109  _NR_DRAINED = "drained" 
 110  _NR_MASTER_CANDIATE = "master-candidate" 
 111  _NR_MASTER = "master" 
 112  _NR_OFFLINE = "offline" 
 113  _NR_REGULAR = "regular" 
 114   
 115  _NR_MAP = { 
 116    constants.NR_MASTER: _NR_MASTER, 
 117    constants.NR_MCANDIDATE: _NR_MASTER_CANDIATE, 
 118    constants.NR_DRAINED: _NR_DRAINED, 
 119    constants.NR_OFFLINE: _NR_OFFLINE, 
 120    constants.NR_REGULAR: _NR_REGULAR, 
 121    } 
 122   
 123  assert frozenset(_NR_MAP.keys()) == constants.NR_ALL 
 124   
 125  # Request data version field 
 126  _REQ_DATA_VERSION = "__version__" 
 127   
 128  # Feature string for instance creation request data version 1 
 129  _INST_CREATE_REQV1 = "instance-create-reqv1" 
 130   
 131  # Feature string for instance reinstall request version 1 
 132  _INST_REINSTALL_REQV1 = "instance-reinstall-reqv1" 
 133   
 134  # Feature string for node migration version 1 
 135  _NODE_MIGRATE_REQV1 = "node-migrate-reqv1" 
 136   
 137  # Feature string for node evacuation with LU-generated jobs 
 138  _NODE_EVAC_RES1 = "node-evac-res1" 
 139   
 140  ALL_FEATURES = frozenset([ 
 141    _INST_CREATE_REQV1, 
 142    _INST_REINSTALL_REQV1, 
 143    _NODE_MIGRATE_REQV1, 
 144    _NODE_EVAC_RES1, 
 145    ]) 
 146   
 147  # Timeout for /2/jobs/[job_id]/wait. Gives job up to 10 seconds to change. 
 148  _WFJC_TIMEOUT = 10 
149 150 151 -class R_version(baserlib.R_Generic):
152 """/version resource. 153 154 This resource should be used to determine the remote API version and 155 to adapt clients accordingly. 156 157 """ 158 @staticmethod
159 - def GET():
160 """Returns the remote API version. 161 162 """ 163 return constants.RAPI_VERSION
164
165 166 -class R_2_info(baserlib.R_Generic):
167 """/2/info resource. 168 169 """ 170 @staticmethod
171 - def GET():
172 """Returns cluster information. 173 174 """ 175 client = baserlib.GetClient() 176 return client.QueryClusterInfo()
177
178 179 -class R_2_features(baserlib.R_Generic):
180 """/2/features resource. 181 182 """ 183 @staticmethod
184 - def GET():
185 """Returns list of optional RAPI features implemented. 186 187 """ 188 return list(ALL_FEATURES)
189
190 191 -class R_2_os(baserlib.R_Generic):
192 """/2/os resource. 193 194 """ 195 @staticmethod
196 - def GET():
197 """Return a list of all OSes. 198 199 Can return error 500 in case of a problem. 200 201 Example: ["debian-etch"] 202 203 """ 204 cl = baserlib.GetClient() 205 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[]) 206 job_id = baserlib.SubmitJob([op], cl) 207 # we use custom feedback function, instead of print we log the status 208 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn) 209 diagnose_data = result[0] 210 211 if not isinstance(diagnose_data, list): 212 raise http.HttpBadGateway(message="Can't get OS list") 213 214 os_names = [] 215 for (name, variants) in diagnose_data: 216 os_names.extend(cli.CalculateOSNames(name, variants)) 217 218 return os_names
219
220 221 -class R_2_redist_config(baserlib.R_Generic):
222 """/2/redistribute-config resource. 223 224 """ 225 @staticmethod
226 - def PUT():
227 """Redistribute configuration to all nodes. 228 229 """ 230 return baserlib.SubmitJob([opcodes.OpClusterRedistConf()])
231
232 233 -class R_2_cluster_modify(baserlib.R_Generic):
234 """/2/modify resource. 235 236 """
237 - def PUT(self):
238 """Modifies cluster parameters. 239 240 @return: a job id 241 242 """ 243 op = baserlib.FillOpcode(opcodes.OpClusterSetParams, self.request_body, 244 None) 245 246 return baserlib.SubmitJob([op])
247
248 249 -class R_2_jobs(baserlib.R_Generic):
250 """/2/jobs resource. 251 252 """
253 - def GET(self):
254 """Returns a dictionary of jobs. 255 256 @return: a dictionary with jobs id and uri. 257 258 """ 259 client = baserlib.GetClient() 260 261 if self.useBulk(): 262 bulkdata = client.QueryJobs(None, J_FIELDS_BULK) 263 return baserlib.MapBulkFields(bulkdata, J_FIELDS_BULK) 264 else: 265 jobdata = map(compat.fst, client.QueryJobs(None, ["id"])) 266 return baserlib.BuildUriList(jobdata, "/2/jobs/%s", 267 uri_fields=("id", "uri"))
268
269 270 -class R_2_jobs_id(baserlib.R_Generic):
271 """/2/jobs/[job_id] resource. 272 273 """
274 - def GET(self):
275 """Returns a job status. 276 277 @return: a dictionary with job parameters. 278 The result includes: 279 - id: job ID as a number 280 - status: current job status as a string 281 - ops: involved OpCodes as a list of dictionaries for each 282 opcodes in the job 283 - opstatus: OpCodes status as a list 284 - opresult: OpCodes results as a list of lists 285 286 """ 287 job_id = self.items[0] 288 result = baserlib.GetClient().QueryJobs([job_id, ], J_FIELDS)[0] 289 if result is None: 290 raise http.HttpNotFound() 291 return baserlib.MapFields(J_FIELDS, result)
292
293 - def DELETE(self):
294 """Cancel not-yet-started job. 295 296 """ 297 job_id = self.items[0] 298 result = baserlib.GetClient().CancelJob(job_id) 299 return result
300
301 302 -class R_2_jobs_id_wait(baserlib.R_Generic):
303 """/2/jobs/[job_id]/wait resource. 304 305 """ 306 # WaitForJobChange provides access to sensitive information and blocks 307 # machine resources (it's a blocking RAPI call), hence restricting access. 308 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 309
310 - def GET(self):
311 """Waits for job changes. 312 313 """ 314 job_id = self.items[0] 315 316 fields = self.getBodyParameter("fields") 317 prev_job_info = self.getBodyParameter("previous_job_info", None) 318 prev_log_serial = self.getBodyParameter("previous_log_serial", None) 319 320 if not isinstance(fields, list): 321 raise http.HttpBadRequest("The 'fields' parameter should be a list") 322 323 if not (prev_job_info is None or isinstance(prev_job_info, list)): 324 raise http.HttpBadRequest("The 'previous_job_info' parameter should" 325 " be a list") 326 327 if not (prev_log_serial is None or 328 isinstance(prev_log_serial, (int, long))): 329 raise http.HttpBadRequest("The 'previous_log_serial' parameter should" 330 " be a number") 331 332 client = baserlib.GetClient() 333 result = client.WaitForJobChangeOnce(job_id, fields, 334 prev_job_info, prev_log_serial, 335 timeout=_WFJC_TIMEOUT) 336 if not result: 337 raise http.HttpNotFound() 338 339 if result == constants.JOB_NOTCHANGED: 340 # No changes 341 return None 342 343 (job_info, log_entries) = result 344 345 return { 346 "job_info": job_info, 347 "log_entries": log_entries, 348 }
349
350 351 -class R_2_nodes(baserlib.R_Generic):
352 """/2/nodes resource. 353 354 """
355 - def GET(self):
356 """Returns a list of all nodes. 357 358 """ 359 client = baserlib.GetClient() 360 361 if self.useBulk(): 362 bulkdata = client.QueryNodes([], N_FIELDS, False) 363 return baserlib.MapBulkFields(bulkdata, N_FIELDS) 364 else: 365 nodesdata = client.QueryNodes([], ["name"], False) 366 nodeslist = [row[0] for row in nodesdata] 367 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s", 368 uri_fields=("id", "uri"))
369
370 371 -class R_2_nodes_name(baserlib.R_Generic):
372 """/2/nodes/[node_name] resource. 373 374 """
375 - def GET(self):
376 """Send information about a node. 377 378 """ 379 node_name = self.items[0] 380 client = baserlib.GetClient() 381 382 result = baserlib.HandleItemQueryErrors(client.QueryNodes, 383 names=[node_name], fields=N_FIELDS, 384 use_locking=self.useLocking()) 385 386 return baserlib.MapFields(N_FIELDS, result[0])
387
388 389 -class R_2_nodes_name_role(baserlib.R_Generic):
390 """ /2/nodes/[node_name]/role resource. 391 392 """
393 - def GET(self):
394 """Returns the current node role. 395 396 @return: Node role 397 398 """ 399 node_name = self.items[0] 400 client = baserlib.GetClient() 401 result = client.QueryNodes(names=[node_name], fields=["role"], 402 use_locking=self.useLocking()) 403 404 return _NR_MAP[result[0][0]]
405
406 - def PUT(self):
407 """Sets the node role. 408 409 @return: a job id 410 411 """ 412 if not isinstance(self.request_body, basestring): 413 raise http.HttpBadRequest("Invalid body contents, not a string") 414 415 node_name = self.items[0] 416 role = self.request_body 417 418 if role == _NR_REGULAR: 419 candidate = False 420 offline = False 421 drained = False 422 423 elif role == _NR_MASTER_CANDIATE: 424 candidate = True 425 offline = drained = None 426 427 elif role == _NR_DRAINED: 428 drained = True 429 candidate = offline = None 430 431 elif role == _NR_OFFLINE: 432 offline = True 433 candidate = drained = None 434 435 else: 436 raise http.HttpBadRequest("Can't set '%s' role" % role) 437 438 op = opcodes.OpNodeSetParams(node_name=node_name, 439 master_candidate=candidate, 440 offline=offline, 441 drained=drained, 442 force=bool(self.useForce())) 443 444 return baserlib.SubmitJob([op])
445
446 447 -class R_2_nodes_name_evacuate(baserlib.R_Generic):
448 """/2/nodes/[node_name]/evacuate resource. 449 450 """
451 - def POST(self):
452 """Evacuate all instances off a node. 453 454 """ 455 op = baserlib.FillOpcode(opcodes.OpNodeEvacuate, self.request_body, { 456 "node_name": self.items[0], 457 "dry_run": self.dryRun(), 458 }) 459 460 return baserlib.SubmitJob([op])
461
462 463 -class R_2_nodes_name_migrate(baserlib.R_Generic):
464 """/2/nodes/[node_name]/migrate resource. 465 466 """
467 - def POST(self):
468 """Migrate all primary instances from a node. 469 470 """ 471 node_name = self.items[0] 472 473 if self.queryargs: 474 # Support old-style requests 475 if "live" in self.queryargs and "mode" in self.queryargs: 476 raise http.HttpBadRequest("Only one of 'live' and 'mode' should" 477 " be passed") 478 479 if "live" in self.queryargs: 480 if self._checkIntVariable("live", default=1): 481 mode = constants.HT_MIGRATION_LIVE 482 else: 483 mode = constants.HT_MIGRATION_NONLIVE 484 else: 485 mode = self._checkStringVariable("mode", default=None) 486 487 data = { 488 "mode": mode, 489 } 490 else: 491 data = self.request_body 492 493 op = baserlib.FillOpcode(opcodes.OpNodeMigrate, data, { 494 "node_name": node_name, 495 }) 496 497 return baserlib.SubmitJob([op])
498
499 500 -class R_2_nodes_name_storage(baserlib.R_Generic):
501 """/2/nodes/[node_name]/storage resource. 502 503 """ 504 # LUNodeQueryStorage acquires locks, hence restricting access to GET 505 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 506
507 - def GET(self):
508 node_name = self.items[0] 509 510 storage_type = self._checkStringVariable("storage_type", None) 511 if not storage_type: 512 raise http.HttpBadRequest("Missing the required 'storage_type'" 513 " parameter") 514 515 output_fields = self._checkStringVariable("output_fields", None) 516 if not output_fields: 517 raise http.HttpBadRequest("Missing the required 'output_fields'" 518 " parameter") 519 520 op = opcodes.OpNodeQueryStorage(nodes=[node_name], 521 storage_type=storage_type, 522 output_fields=output_fields.split(",")) 523 return baserlib.SubmitJob([op])
524
525 526 -class R_2_nodes_name_storage_modify(baserlib.R_Generic):
527 """/2/nodes/[node_name]/storage/modify resource. 528 529 """
530 - def PUT(self):
531 node_name = self.items[0] 532 533 storage_type = self._checkStringVariable("storage_type", None) 534 if not storage_type: 535 raise http.HttpBadRequest("Missing the required 'storage_type'" 536 " parameter") 537 538 name = self._checkStringVariable("name", None) 539 if not name: 540 raise http.HttpBadRequest("Missing the required 'name'" 541 " parameter") 542 543 changes = {} 544 545 if "allocatable" in self.queryargs: 546 changes[constants.SF_ALLOCATABLE] = \ 547 bool(self._checkIntVariable("allocatable", default=1)) 548 549 op = opcodes.OpNodeModifyStorage(node_name=node_name, 550 storage_type=storage_type, 551 name=name, 552 changes=changes) 553 return baserlib.SubmitJob([op])
554
555 556 -class R_2_nodes_name_storage_repair(baserlib.R_Generic):
557 """/2/nodes/[node_name]/storage/repair resource. 558 559 """
560 - def PUT(self):
561 node_name = self.items[0] 562 563 storage_type = self._checkStringVariable("storage_type", None) 564 if not storage_type: 565 raise http.HttpBadRequest("Missing the required 'storage_type'" 566 " parameter") 567 568 name = self._checkStringVariable("name", None) 569 if not name: 570 raise http.HttpBadRequest("Missing the required 'name'" 571 " parameter") 572 573 op = opcodes.OpRepairNodeStorage(node_name=node_name, 574 storage_type=storage_type, 575 name=name) 576 return baserlib.SubmitJob([op])
577
578 579 -def _ParseCreateGroupRequest(data, dry_run):
580 """Parses a request for creating a node group. 581 582 @rtype: L{opcodes.OpGroupAdd} 583 @return: Group creation opcode 584 585 """ 586 override = { 587 "dry_run": dry_run, 588 } 589 590 rename = { 591 "name": "group_name", 592 } 593 594 return baserlib.FillOpcode(opcodes.OpGroupAdd, data, override, 595 rename=rename)
596
597 598 -class R_2_groups(baserlib.R_Generic):
599 """/2/groups resource. 600 601 """
602 - def GET(self):
603 """Returns a list of all node groups. 604 605 """ 606 client = baserlib.GetClient() 607 608 if self.useBulk(): 609 bulkdata = client.QueryGroups([], G_FIELDS, False) 610 return baserlib.MapBulkFields(bulkdata, G_FIELDS) 611 else: 612 data = client.QueryGroups([], ["name"], False) 613 groupnames = [row[0] for row in data] 614 return baserlib.BuildUriList(groupnames, "/2/groups/%s", 615 uri_fields=("name", "uri"))
616
617 - def POST(self):
618 """Create a node group. 619 620 @return: a job id 621 622 """ 623 baserlib.CheckType(self.request_body, dict, "Body contents") 624 op = _ParseCreateGroupRequest(self.request_body, self.dryRun()) 625 return baserlib.SubmitJob([op])
626
627 628 -class R_2_groups_name(baserlib.R_Generic):
629 """/2/groups/[group_name] resource. 630 631 """
632 - def GET(self):
633 """Send information about a node group. 634 635 """ 636 group_name = self.items[0] 637 client = baserlib.GetClient() 638 639 result = baserlib.HandleItemQueryErrors(client.QueryGroups, 640 names=[group_name], fields=G_FIELDS, 641 use_locking=self.useLocking()) 642 643 return baserlib.MapFields(G_FIELDS, result[0])
644
645 - def DELETE(self):
646 """Delete a node group. 647 648 """ 649 op = opcodes.OpGroupRemove(group_name=self.items[0], 650 dry_run=bool(self.dryRun())) 651 652 return baserlib.SubmitJob([op])
653
654 655 -def _ParseModifyGroupRequest(name, data):
656 """Parses a request for modifying a node group. 657 658 @rtype: L{opcodes.OpGroupSetParams} 659 @return: Group modify opcode 660 661 """ 662 return baserlib.FillOpcode(opcodes.OpGroupSetParams, data, { 663 "group_name": name, 664 })
665
666 667 -class R_2_groups_name_modify(baserlib.R_Generic):
668 """/2/groups/[group_name]/modify resource. 669 670 """
671 - def PUT(self):
672 """Changes some parameters of node group. 673 674 @return: a job id 675 676 """ 677 baserlib.CheckType(self.request_body, dict, "Body contents") 678 679 op = _ParseModifyGroupRequest(self.items[0], self.request_body) 680 681 return baserlib.SubmitJob([op])
682
683 684 -def _ParseRenameGroupRequest(name, data, dry_run):
685 """Parses a request for renaming a node group. 686 687 @type name: string 688 @param name: name of the node group to rename 689 @type data: dict 690 @param data: the body received by the rename request 691 @type dry_run: bool 692 @param dry_run: whether to perform a dry run 693 694 @rtype: L{opcodes.OpGroupRename} 695 @return: Node group rename opcode 696 697 """ 698 return baserlib.FillOpcode(opcodes.OpGroupRename, data, { 699 "group_name": name, 700 "dry_run": dry_run, 701 })
702
703 704 -class R_2_groups_name_rename(baserlib.R_Generic):
705 """/2/groups/[group_name]/rename resource. 706 707 """
708 - def PUT(self):
709 """Changes the name of a node group. 710 711 @return: a job id 712 713 """ 714 baserlib.CheckType(self.request_body, dict, "Body contents") 715 op = _ParseRenameGroupRequest(self.items[0], self.request_body, 716 self.dryRun()) 717 return baserlib.SubmitJob([op])
718
719 720 -class R_2_groups_name_assign_nodes(baserlib.R_Generic):
721 """/2/groups/[group_name]/assign-nodes resource. 722 723 """
724 - def PUT(self):
725 """Assigns nodes to a group. 726 727 @return: a job id 728 729 """ 730 op = baserlib.FillOpcode(opcodes.OpGroupAssignNodes, self.request_body, { 731 "group_name": self.items[0], 732 "dry_run": self.dryRun(), 733 "force": self.useForce(), 734 }) 735 736 return baserlib.SubmitJob([op])
737
738 739 -def _ParseInstanceCreateRequestVersion1(data, dry_run):
740 """Parses an instance creation request version 1. 741 742 @rtype: L{opcodes.OpInstanceCreate} 743 @return: Instance creation opcode 744 745 """ 746 override = { 747 "dry_run": dry_run, 748 } 749 750 rename = { 751 "os": "os_type", 752 "name": "instance_name", 753 } 754 755 return baserlib.FillOpcode(opcodes.OpInstanceCreate, data, override, 756 rename=rename)
757
758 759 -class R_2_instances(baserlib.R_Generic):
760 """/2/instances resource. 761 762 """
763 - def GET(self):
764 """Returns a list of all available instances. 765 766 """ 767 client = baserlib.GetClient() 768 769 use_locking = self.useLocking() 770 if self.useBulk(): 771 bulkdata = client.QueryInstances([], I_FIELDS, use_locking) 772 return baserlib.MapBulkFields(bulkdata, I_FIELDS) 773 else: 774 instancesdata = client.QueryInstances([], ["name"], use_locking) 775 instanceslist = [row[0] for row in instancesdata] 776 return baserlib.BuildUriList(instanceslist, "/2/instances/%s", 777 uri_fields=("id", "uri"))
778
779 - def POST(self):
780 """Create an instance. 781 782 @return: a job id 783 784 """ 785 if not isinstance(self.request_body, dict): 786 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 787 788 # Default to request data version 0 789 data_version = self.getBodyParameter(_REQ_DATA_VERSION, 0) 790 791 if data_version == 0: 792 raise http.HttpBadRequest("Instance creation request version 0 is no" 793 " longer supported") 794 elif data_version == 1: 795 data = self.request_body.copy() 796 # Remove "__version__" 797 data.pop(_REQ_DATA_VERSION, None) 798 op = _ParseInstanceCreateRequestVersion1(data, self.dryRun()) 799 else: 800 raise http.HttpBadRequest("Unsupported request data version %s" % 801 data_version) 802 803 return baserlib.SubmitJob([op])
804
805 806 -class R_2_instances_name(baserlib.R_Generic):
807 """/2/instances/[instance_name] resource. 808 809 """
810 - def GET(self):
811 """Send information about an instance. 812 813 """ 814 client = baserlib.GetClient() 815 instance_name = self.items[0] 816 817 result = baserlib.HandleItemQueryErrors(client.QueryInstances, 818 names=[instance_name], 819 fields=I_FIELDS, 820 use_locking=self.useLocking()) 821 822 return baserlib.MapFields(I_FIELDS, result[0])
823
824 - def DELETE(self):
825 """Delete an instance. 826 827 """ 828 op = opcodes.OpInstanceRemove(instance_name=self.items[0], 829 ignore_failures=False, 830 dry_run=bool(self.dryRun())) 831 return baserlib.SubmitJob([op])
832
833 834 -class R_2_instances_name_info(baserlib.R_Generic):
835 """/2/instances/[instance_name]/info resource. 836 837 """
838 - def GET(self):
839 """Request detailed instance information. 840 841 """ 842 instance_name = self.items[0] 843 static = bool(self._checkIntVariable("static", default=0)) 844 845 op = opcodes.OpInstanceQueryData(instances=[instance_name], 846 static=static) 847 return baserlib.SubmitJob([op])
848
849 850 -class R_2_instances_name_reboot(baserlib.R_Generic):
851 """/2/instances/[instance_name]/reboot resource. 852 853 Implements an instance reboot. 854 855 """
856 - def POST(self):
857 """Reboot an instance. 858 859 The URI takes type=[hard|soft|full] and 860 ignore_secondaries=[False|True] parameters. 861 862 """ 863 instance_name = self.items[0] 864 reboot_type = self.queryargs.get("type", 865 [constants.INSTANCE_REBOOT_HARD])[0] 866 ignore_secondaries = bool(self._checkIntVariable("ignore_secondaries")) 867 op = opcodes.OpInstanceReboot(instance_name=instance_name, 868 reboot_type=reboot_type, 869 ignore_secondaries=ignore_secondaries, 870 dry_run=bool(self.dryRun())) 871 872 return baserlib.SubmitJob([op])
873
874 875 -class R_2_instances_name_startup(baserlib.R_Generic):
876 """/2/instances/[instance_name]/startup resource. 877 878 Implements an instance startup. 879 880 """
881 - def PUT(self):
882 """Startup an instance. 883 884 The URI takes force=[False|True] parameter to start the instance 885 if even if secondary disks are failing. 886 887 """ 888 instance_name = self.items[0] 889 force_startup = bool(self._checkIntVariable("force")) 890 no_remember = bool(self._checkIntVariable("no_remember")) 891 op = opcodes.OpInstanceStartup(instance_name=instance_name, 892 force=force_startup, 893 dry_run=bool(self.dryRun()), 894 no_remember=no_remember) 895 896 return baserlib.SubmitJob([op])
897
898 899 -def _ParseShutdownInstanceRequest(name, data, dry_run, no_remember):
900 """Parses a request for an instance shutdown. 901 902 @rtype: L{opcodes.OpInstanceShutdown} 903 @return: Instance shutdown opcode 904 905 """ 906 return baserlib.FillOpcode(opcodes.OpInstanceShutdown, data, { 907 "instance_name": name, 908 "dry_run": dry_run, 909 "no_remember": no_remember, 910 })
911
912 913 -class R_2_instances_name_shutdown(baserlib.R_Generic):
914 """/2/instances/[instance_name]/shutdown resource. 915 916 Implements an instance shutdown. 917 918 """
919 - def PUT(self):
920 """Shutdown an instance. 921 922 @return: a job id 923 924 """ 925 no_remember = bool(self._checkIntVariable("no_remember")) 926 op = _ParseShutdownInstanceRequest(self.items[0], self.request_body, 927 bool(self.dryRun()), no_remember) 928 929 return baserlib.SubmitJob([op])
930
931 932 -def _ParseInstanceReinstallRequest(name, data):
933 """Parses a request for reinstalling an instance. 934 935 """ 936 if not isinstance(data, dict): 937 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 938 939 ostype = baserlib.CheckParameter(data, "os", default=None) 940 start = baserlib.CheckParameter(data, "start", exptype=bool, 941 default=True) 942 osparams = baserlib.CheckParameter(data, "osparams", default=None) 943 944 ops = [ 945 opcodes.OpInstanceShutdown(instance_name=name), 946 opcodes.OpInstanceReinstall(instance_name=name, os_type=ostype, 947 osparams=osparams), 948 ] 949 950 if start: 951 ops.append(opcodes.OpInstanceStartup(instance_name=name, force=False)) 952 953 return ops
954
955 956 -class R_2_instances_name_reinstall(baserlib.R_Generic):
957 """/2/instances/[instance_name]/reinstall resource. 958 959 Implements an instance reinstall. 960 961 """
962 - def POST(self):
963 """Reinstall an instance. 964 965 The URI takes os=name and nostartup=[0|1] optional 966 parameters. By default, the instance will be started 967 automatically. 968 969 """ 970 if self.request_body: 971 if self.queryargs: 972 raise http.HttpBadRequest("Can't combine query and body parameters") 973 974 body = self.request_body 975 elif self.queryargs: 976 # Legacy interface, do not modify/extend 977 body = { 978 "os": self._checkStringVariable("os"), 979 "start": not self._checkIntVariable("nostartup"), 980 } 981 else: 982 body = {} 983 984 ops = _ParseInstanceReinstallRequest(self.items[0], body) 985 986 return baserlib.SubmitJob(ops)
987
988 989 -def _ParseInstanceReplaceDisksRequest(name, data):
990 """Parses a request for an instance export. 991 992 @rtype: L{opcodes.OpInstanceReplaceDisks} 993 @return: Instance export opcode 994 995 """ 996 override = { 997 "instance_name": name, 998 } 999 1000 # Parse disks 1001 try: 1002 raw_disks = data.pop("disks") 1003 except KeyError: 1004 pass 1005 else: 1006 if raw_disks: 1007 if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102 1008 data["disks"] = raw_disks 1009 else: 1010 # Backwards compatibility for strings of the format "1, 2, 3" 1011 try: 1012 data["disks"] = [int(part) for part in raw_disks.split(",")] 1013 except (TypeError, ValueError), err: 1014 raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err)) 1015 1016 return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
1017
1018 1019 -class R_2_instances_name_replace_disks(baserlib.R_Generic):
1020 """/2/instances/[instance_name]/replace-disks resource. 1021 1022 """
1023 - def POST(self):
1024 """Replaces disks on an instance. 1025 1026 """ 1027 if self.request_body: 1028 body = self.request_body 1029 elif self.queryargs: 1030 # Legacy interface, do not modify/extend 1031 body = { 1032 "remote_node": self._checkStringVariable("remote_node", default=None), 1033 "mode": self._checkStringVariable("mode", default=None), 1034 "disks": self._checkStringVariable("disks", default=None), 1035 "iallocator": self._checkStringVariable("iallocator", default=None), 1036 } 1037 else: 1038 body = {} 1039 1040 op = _ParseInstanceReplaceDisksRequest(self.items[0], body) 1041 1042 return baserlib.SubmitJob([op])
1043
1044 1045 -class R_2_instances_name_activate_disks(baserlib.R_Generic):
1046 """/2/instances/[instance_name]/activate-disks resource. 1047 1048 """
1049 - def PUT(self):
1050 """Activate disks for an instance. 1051 1052 The URI might contain ignore_size to ignore current recorded size. 1053 1054 """ 1055 instance_name = self.items[0] 1056 ignore_size = bool(self._checkIntVariable("ignore_size")) 1057 1058 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name, 1059 ignore_size=ignore_size) 1060 1061 return baserlib.SubmitJob([op])
1062
1063 1064 -class R_2_instances_name_deactivate_disks(baserlib.R_Generic):
1065 """/2/instances/[instance_name]/deactivate-disks resource. 1066 1067 """
1068 - def PUT(self):
1069 """Deactivate disks for an instance. 1070 1071 """ 1072 instance_name = self.items[0] 1073 1074 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name) 1075 1076 return baserlib.SubmitJob([op])
1077
1078 1079 -class R_2_instances_name_prepare_export(baserlib.R_Generic):
1080 """/2/instances/[instance_name]/prepare-export resource. 1081 1082 """
1083 - def PUT(self):
1084 """Prepares an export for an instance. 1085 1086 @return: a job id 1087 1088 """ 1089 instance_name = self.items[0] 1090 mode = self._checkStringVariable("mode") 1091 1092 op = opcodes.OpBackupPrepare(instance_name=instance_name, 1093 mode=mode) 1094 1095 return baserlib.SubmitJob([op])
1096
1097 1098 -def _ParseExportInstanceRequest(name, data):
1099 """Parses a request for an instance export. 1100 1101 @rtype: L{opcodes.OpBackupExport} 1102 @return: Instance export opcode 1103 1104 """ 1105 # Rename "destination" to "target_node" 1106 try: 1107 data["target_node"] = data.pop("destination") 1108 except KeyError: 1109 pass 1110 1111 return baserlib.FillOpcode(opcodes.OpBackupExport, data, { 1112 "instance_name": name, 1113 })
1114
1115 1116 -class R_2_instances_name_export(baserlib.R_Generic):
1117 """/2/instances/[instance_name]/export resource. 1118 1119 """
1120 - def PUT(self):
1121 """Exports an instance. 1122 1123 @return: a job id 1124 1125 """ 1126 if not isinstance(self.request_body, dict): 1127 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 1128 1129 op = _ParseExportInstanceRequest(self.items[0], self.request_body) 1130 1131 return baserlib.SubmitJob([op])
1132
1133 1134 -def _ParseMigrateInstanceRequest(name, data):
1135 """Parses a request for an instance migration. 1136 1137 @rtype: L{opcodes.OpInstanceMigrate} 1138 @return: Instance migration opcode 1139 1140 """ 1141 return baserlib.FillOpcode(opcodes.OpInstanceMigrate, data, { 1142 "instance_name": name, 1143 })
1144
1145 1146 -class R_2_instances_name_migrate(baserlib.R_Generic):
1147 """/2/instances/[instance_name]/migrate resource. 1148 1149 """
1150 - def PUT(self):
1151 """Migrates an instance. 1152 1153 @return: a job id 1154 1155 """ 1156 baserlib.CheckType(self.request_body, dict, "Body contents") 1157 1158 op = _ParseMigrateInstanceRequest(self.items[0], self.request_body) 1159 1160 return baserlib.SubmitJob([op])
1161
1162 1163 -class R_2_instances_name_failover(baserlib.R_Generic):
1164 """/2/instances/[instance_name]/failover resource. 1165 1166 """
1167 - def PUT(self):
1168 """Does a failover of an instance. 1169 1170 @return: a job id 1171 1172 """ 1173 baserlib.CheckType(self.request_body, dict, "Body contents") 1174 1175 op = baserlib.FillOpcode(opcodes.OpInstanceFailover, self.request_body, { 1176 "instance_name": self.items[0], 1177 }) 1178 1179 return baserlib.SubmitJob([op])
1180
1181 1182 -def _ParseRenameInstanceRequest(name, data):
1183 """Parses a request for renaming an instance. 1184 1185 @rtype: L{opcodes.OpInstanceRename} 1186 @return: Instance rename opcode 1187 1188 """ 1189 return baserlib.FillOpcode(opcodes.OpInstanceRename, data, { 1190 "instance_name": name, 1191 })
1192
1193 1194 -class R_2_instances_name_rename(baserlib.R_Generic):
1195 """/2/instances/[instance_name]/rename resource. 1196 1197 """
1198 - def PUT(self):
1199 """Changes the name of an instance. 1200 1201 @return: a job id 1202 1203 """ 1204 baserlib.CheckType(self.request_body, dict, "Body contents") 1205 1206 op = _ParseRenameInstanceRequest(self.items[0], self.request_body) 1207 1208 return baserlib.SubmitJob([op])
1209
1210 1211 -def _ParseModifyInstanceRequest(name, data):
1212 """Parses a request for modifying an instance. 1213 1214 @rtype: L{opcodes.OpInstanceSetParams} 1215 @return: Instance modify opcode 1216 1217 """ 1218 return baserlib.FillOpcode(opcodes.OpInstanceSetParams, data, { 1219 "instance_name": name, 1220 })
1221
1222 1223 -class R_2_instances_name_modify(baserlib.R_Generic):
1224 """/2/instances/[instance_name]/modify resource. 1225 1226 """
1227 - def PUT(self):
1228 """Changes some parameters of an instance. 1229 1230 @return: a job id 1231 1232 """ 1233 baserlib.CheckType(self.request_body, dict, "Body contents") 1234 1235 op = _ParseModifyInstanceRequest(self.items[0], self.request_body) 1236 1237 return baserlib.SubmitJob([op])
1238
1239 1240 -class R_2_instances_name_disk_grow(baserlib.R_Generic):
1241 """/2/instances/[instance_name]/disk/[disk_index]/grow resource. 1242 1243 """
1244 - def POST(self):
1245 """Increases the size of an instance disk. 1246 1247 @return: a job id 1248 1249 """ 1250 op = baserlib.FillOpcode(opcodes.OpInstanceGrowDisk, self.request_body, { 1251 "instance_name": self.items[0], 1252 "disk": int(self.items[1]), 1253 }) 1254 1255 return baserlib.SubmitJob([op])
1256
1257 1258 -class R_2_instances_name_console(baserlib.R_Generic):
1259 """/2/instances/[instance_name]/console resource. 1260 1261 """ 1262 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 1263
1264 - def GET(self):
1265 """Request information for connecting to instance's console. 1266 1267 @return: Serialized instance console description, see 1268 L{objects.InstanceConsole} 1269 1270 """ 1271 client = baserlib.GetClient() 1272 1273 ((console, ), ) = client.QueryInstances([self.items[0]], ["console"], False) 1274 1275 if console is None: 1276 raise http.HttpServiceUnavailable("Instance console unavailable") 1277 1278 assert isinstance(console, dict) 1279 return console
1280
1281 1282 -def _GetQueryFields(args):
1283 """ 1284 1285 """ 1286 try: 1287 fields = args["fields"] 1288 except KeyError: 1289 raise http.HttpBadRequest("Missing 'fields' query argument") 1290 1291 return _SplitQueryFields(fields[0])
1292
1293 1294 -def _SplitQueryFields(fields):
1295 """ 1296 1297 """ 1298 return [i.strip() for i in fields.split(",")]
1299
1300 1301 -class R_2_query(baserlib.R_Generic):
1302 """/2/query/[resource] resource. 1303 1304 """ 1305 # Results might contain sensitive information 1306 GET_ACCESS = [rapi.RAPI_ACCESS_WRITE] 1307
1308 - def _Query(self, fields, filter_):
1309 return baserlib.GetClient().Query(self.items[0], fields, filter_).ToDict()
1310
1311 - def GET(self):
1312 """Returns resource information. 1313 1314 @return: Query result, see L{objects.QueryResponse} 1315 1316 """ 1317 return self._Query(_GetQueryFields(self.queryargs), None)
1318
1319 - def PUT(self):
1320 """Submits job querying for resources. 1321 1322 @return: Query result, see L{objects.QueryResponse} 1323 1324 """ 1325 body = self.request_body 1326 1327 baserlib.CheckType(body, dict, "Body contents") 1328 1329 try: 1330 fields = body["fields"] 1331 except KeyError: 1332 fields = _GetQueryFields(self.queryargs) 1333 1334 return self._Query(fields, self.request_body.get("filter", None))
1335
1336 1337 -class R_2_query_fields(baserlib.R_Generic):
1338 """/2/query/[resource]/fields resource. 1339 1340 """
1341 - def GET(self):
1342 """Retrieves list of available fields for a resource. 1343 1344 @return: List of serialized L{objects.QueryFieldDefinition} 1345 1346 """ 1347 try: 1348 raw_fields = self.queryargs["fields"] 1349 except KeyError: 1350 fields = None 1351 else: 1352 fields = _SplitQueryFields(raw_fields[0]) 1353 1354 return baserlib.GetClient().QueryFields(self.items[0], fields).ToDict()
1355
1356 1357 -class _R_Tags(baserlib.R_Generic):
1358 """ Quasiclass for tagging resources 1359 1360 Manages tags. When inheriting this class you must define the 1361 TAG_LEVEL for it. 1362 1363 """ 1364 TAG_LEVEL = None 1365
1366 - def __init__(self, items, queryargs, req):
1367 """A tag resource constructor. 1368 1369 We have to override the default to sort out cluster naming case. 1370 1371 """ 1372 baserlib.R_Generic.__init__(self, items, queryargs, req) 1373 1374 if self.TAG_LEVEL == constants.TAG_CLUSTER: 1375 self.name = None 1376 else: 1377 self.name = items[0]
1378
1379 - def GET(self):
1380 """Returns a list of tags. 1381 1382 Example: ["tag1", "tag2", "tag3"] 1383 1384 """ 1385 # pylint: disable=W0212 1386 return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
1387
1388 - def PUT(self):
1389 """Add a set of tags. 1390 1391 The request as a list of strings should be PUT to this URI. And 1392 you'll have back a job id. 1393 1394 """ 1395 # pylint: disable=W0212 1396 if "tag" not in self.queryargs: 1397 raise http.HttpBadRequest("Please specify tag(s) to add using the" 1398 " the 'tag' parameter") 1399 return baserlib._Tags_PUT(self.TAG_LEVEL, 1400 self.queryargs["tag"], name=self.name, 1401 dry_run=bool(self.dryRun()))
1402
1403 - def DELETE(self):
1404 """Delete a tag. 1405 1406 In order to delete a set of tags, the DELETE 1407 request should be addressed to URI like: 1408 /tags?tag=[tag]&tag=[tag] 1409 1410 """ 1411 # pylint: disable=W0212 1412 if "tag" not in self.queryargs: 1413 # no we not gonna delete all tags 1414 raise http.HttpBadRequest("Cannot delete all tags - please specify" 1415 " tag(s) using the 'tag' parameter") 1416 return baserlib._Tags_DELETE(self.TAG_LEVEL, 1417 self.queryargs["tag"], 1418 name=self.name, 1419 dry_run=bool(self.dryRun()))
1420
1421 1422 -class R_2_instances_name_tags(_R_Tags):
1423 """ /2/instances/[instance_name]/tags resource. 1424 1425 Manages per-instance tags. 1426 1427 """ 1428 TAG_LEVEL = constants.TAG_INSTANCE
1429
1430 1431 -class R_2_nodes_name_tags(_R_Tags):
1432 """ /2/nodes/[node_name]/tags resource. 1433 1434 Manages per-node tags. 1435 1436 """ 1437 TAG_LEVEL = constants.TAG_NODE
1438
1439 1440 -class R_2_groups_name_tags(_R_Tags):
1441 """ /2/groups/[group_name]/tags resource. 1442 1443 Manages per-nodegroup tags. 1444 1445 """ 1446 TAG_LEVEL = constants.TAG_NODEGROUP
1447
1448 1449 -class R_2_tags(_R_Tags):
1450 """ /2/tags resource. 1451 1452 Manages cluster tags. 1453 1454 """ 1455 TAG_LEVEL = constants.TAG_CLUSTER
1456