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 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 version 2 baserlib.library. 
 23   
 24  """ 
 25   
 26  # pylint: disable-msg=C0103 
 27   
 28  # C0103: Invalid name, since the R_* names are not conforming 
 29   
 30  from ganeti import opcodes 
 31  from ganeti import http 
 32  from ganeti import constants 
 33  from ganeti import cli 
 34  from ganeti.rapi import baserlib 
 35   
 36   
 37  I_FIELDS = ["name", "admin_state", "os", 
 38              "pnode", "snodes", 
 39              "disk_template", 
 40              "nic.ips", "nic.macs", "nic.bridges", 
 41              "network_port", 
 42              "disk.sizes", "disk_usage", 
 43              "beparams", "hvparams", 
 44              "oper_state", "oper_ram", "status", 
 45              "tags"] 
 46   
 47  N_FIELDS = ["name", "offline", "master_candidate", "drained", 
 48              "dtotal", "dfree", 
 49              "mtotal", "mnode", "mfree", 
 50              "pinst_cnt", "sinst_cnt", "tags", 
 51              "ctotal", "cnodes", "csockets", 
 52              "pip", "sip", "serial_no", "role", 
 53              "pinst_list", "sinst_list", 
 54              ] 
 55   
 56   
57 -class R_version(baserlib.R_Generic):
58 """/version resource. 59 60 This resource should be used to determine the remote API version and 61 to adapt clients accordingly. 62 63 """ 64 DOC_URI = "/version" 65
66 - def GET(self):
67 """Returns the remote API version. 68 69 """ 70 return constants.RAPI_VERSION
71 72
73 -class R_2_info(baserlib.R_Generic):
74 """Cluster info. 75 76 """ 77 DOC_URI = "/2/info" 78
79 - def GET(self):
80 """Returns cluster information. 81 82 Example:: 83 84 { 85 "config_version": 2000000, 86 "name": "cluster", 87 "software_version": "2.0.0~beta2", 88 "os_api_version": 10, 89 "export_version": 0, 90 "candidate_pool_size": 10, 91 "enabled_hypervisors": [ 92 "fake" 93 ], 94 "hvparams": { 95 "fake": {} 96 }, 97 "default_hypervisor": "fake", 98 "master": "node1.example.com", 99 "architecture": [ 100 "64bit", 101 "x86_64" 102 ], 103 "protocol_version": 20, 104 "beparams": { 105 "default": { 106 "auto_balance": true, 107 "vcpus": 1, 108 "memory": 128 109 } 110 } 111 } 112 113 """ 114 client = baserlib.GetClient() 115 return client.QueryClusterInfo()
116 117
118 -class R_2_os(baserlib.R_Generic):
119 """/2/os resource. 120 121 """ 122 DOC_URI = "/2/os" 123
124 - def GET(self):
125 """Return a list of all OSes. 126 127 Can return error 500 in case of a problem. 128 129 Example: ["debian-etch"] 130 131 """ 132 cl = baserlib.GetClient() 133 op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[]) 134 job_id = baserlib.SubmitJob([op], cl) 135 # we use custom feedback function, instead of print we log the status 136 result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn) 137 diagnose_data = result[0] 138 139 if not isinstance(diagnose_data, list): 140 raise http.HttpBadGateway(message="Can't get OS list") 141 142 return [row[0] for row in diagnose_data if row[1]]
143 144
145 -class R_2_jobs(baserlib.R_Generic):
146 """/2/jobs resource. 147 148 """ 149 DOC_URI = "/2/jobs" 150
151 - def GET(self):
152 """Returns a dictionary of jobs. 153 154 @return: a dictionary with jobs id and uri. 155 156 """ 157 fields = ["id"] 158 cl = baserlib.GetClient() 159 # Convert the list of lists to the list of ids 160 result = [job_id for [job_id] in cl.QueryJobs(None, fields)] 161 return baserlib.BuildUriList(result, "/2/jobs/%s", 162 uri_fields=("id", "uri"))
163 164
165 -class R_2_jobs_id(baserlib.R_Generic):
166 """/2/jobs/[job_id] resource. 167 168 """ 169 DOC_URI = "/2/jobs/[job_id]" 170
171 - def GET(self):
172 """Returns a job status. 173 174 @return: a dictionary with job parameters. 175 The result includes: 176 - id: job ID as a number 177 - status: current job status as a string 178 - ops: involved OpCodes as a list of dictionaries for each 179 opcodes in the job 180 - opstatus: OpCodes status as a list 181 - opresult: OpCodes results as a list of lists 182 183 """ 184 fields = ["id", "ops", "status", "summary", 185 "opstatus", "opresult", "oplog", 186 "received_ts", "start_ts", "end_ts", 187 ] 188 job_id = self.items[0] 189 result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0] 190 if result is None: 191 raise http.HttpNotFound() 192 return baserlib.MapFields(fields, result)
193
194 - def DELETE(self):
195 """Cancel not-yet-started job. 196 197 """ 198 job_id = self.items[0] 199 result = baserlib.GetClient().CancelJob(job_id) 200 return result
201 202
203 -class R_2_nodes(baserlib.R_Generic):
204 """/2/nodes resource. 205 206 """ 207 DOC_URI = "/2/nodes" 208
209 - def GET(self):
210 """Returns a list of all nodes. 211 212 Example:: 213 214 [ 215 { 216 "id": "node1.example.com", 217 "uri": "\/instances\/node1.example.com" 218 }, 219 { 220 "id": "node2.example.com", 221 "uri": "\/instances\/node2.example.com" 222 } 223 ] 224 225 If the optional 'bulk' argument is provided and set to 'true' 226 value (i.e '?bulk=1'), the output contains detailed 227 information about nodes as a list. 228 229 Example:: 230 231 [ 232 { 233 "pinst_cnt": 1, 234 "mfree": 31280, 235 "mtotal": 32763, 236 "name": "www.example.com", 237 "tags": [], 238 "mnode": 512, 239 "dtotal": 5246208, 240 "sinst_cnt": 2, 241 "dfree": 5171712, 242 "offline": false 243 }, 244 ... 245 ] 246 247 @return: a dictionary with 'name' and 'uri' keys for each of them 248 249 """ 250 client = baserlib.GetClient() 251 252 if self.useBulk(): 253 bulkdata = client.QueryNodes([], N_FIELDS, False) 254 return baserlib.MapBulkFields(bulkdata, N_FIELDS) 255 else: 256 nodesdata = client.QueryNodes([], ["name"], False) 257 nodeslist = [row[0] for row in nodesdata] 258 return baserlib.BuildUriList(nodeslist, "/2/nodes/%s", 259 uri_fields=("id", "uri"))
260 261
262 -class R_2_nodes_name(baserlib.R_Generic):
263 """/2/nodes/[node_name] resources. 264 265 """ 266 DOC_URI = "/nodes/[node_name]" 267
268 - def GET(self):
269 """Send information about a node. 270 271 """ 272 node_name = self.items[0] 273 client = baserlib.GetClient() 274 result = client.QueryNodes(names=[node_name], fields=N_FIELDS, 275 use_locking=self.useLocking()) 276 277 return baserlib.MapFields(N_FIELDS, result[0])
278 279
280 -class R_2_instances(baserlib.R_Generic):
281 """/2/instances resource. 282 283 """ 284 DOC_URI = "/2/instances" 285
286 - def GET(self):
287 """Returns a list of all available instances. 288 289 290 Example:: 291 292 [ 293 { 294 "name": "web.example.com", 295 "uri": "\/instances\/web.example.com" 296 }, 297 { 298 "name": "mail.example.com", 299 "uri": "\/instances\/mail.example.com" 300 } 301 ] 302 303 If the optional 'bulk' argument is provided and set to 'true' 304 value (i.e '?bulk=1'), the output contains detailed 305 information about instances as a list. 306 307 Example:: 308 309 [ 310 { 311 "status": "running", 312 "disk_usage": 20480, 313 "nic.bridges": [ 314 "xen-br0" 315 ], 316 "name": "web.example.com", 317 "tags": ["tag1", "tag2"], 318 "beparams": { 319 "vcpus": 2, 320 "memory": 512 321 }, 322 "disk.sizes": [ 323 20480 324 ], 325 "pnode": "node1.example.com", 326 "nic.macs": ["01:23:45:67:89:01"], 327 "snodes": ["node2.example.com"], 328 "disk_template": "drbd", 329 "admin_state": true, 330 "os": "debian-etch", 331 "oper_state": true 332 }, 333 ... 334 ] 335 336 @return: a dictionary with 'name' and 'uri' keys for each of them. 337 338 """ 339 client = baserlib.GetClient() 340 341 use_locking = self.useLocking() 342 if self.useBulk(): 343 bulkdata = client.QueryInstances([], I_FIELDS, use_locking) 344 return baserlib.MapBulkFields(bulkdata, I_FIELDS) 345 else: 346 instancesdata = client.QueryInstances([], ["name"], use_locking) 347 instanceslist = [row[0] for row in instancesdata] 348 return baserlib.BuildUriList(instanceslist, "/2/instances/%s", 349 uri_fields=("id", "uri"))
350
351 - def POST(self):
352 """Create an instance. 353 354 @return: a job id 355 356 """ 357 if not isinstance(self.req.request_body, dict): 358 raise http.HttpBadRequest("Invalid body contents, not a dictionary") 359 360 beparams = baserlib.MakeParamsDict(self.req.request_body, 361 constants.BES_PARAMETERS) 362 hvparams = baserlib.MakeParamsDict(self.req.request_body, 363 constants.HVS_PARAMETERS) 364 fn = self.getBodyParameter 365 366 # disk processing 367 disk_data = fn('disks') 368 if not isinstance(disk_data, list): 369 raise http.HttpBadRequest("The 'disks' parameter should be a list") 370 disks = [] 371 for idx, d in enumerate(disk_data): 372 if not isinstance(d, int): 373 raise http.HttpBadRequest("Disk %d specification wrong: should" 374 " be an integer") 375 disks.append({"size": d}) 376 # nic processing (one nic only) 377 nics = [{"mac": fn("mac", constants.VALUE_AUTO), 378 "ip": fn("ip", None), 379 "bridge": fn("bridge", None)}] 380 381 op = opcodes.OpCreateInstance( 382 mode=constants.INSTANCE_CREATE, 383 instance_name=fn('name'), 384 disks=disks, 385 disk_template=fn('disk_template'), 386 os_type=fn('os'), 387 pnode=fn('pnode', None), 388 snode=fn('snode', None), 389 iallocator=fn('iallocator', None), 390 nics=nics, 391 start=fn('start', True), 392 ip_check=fn('ip_check', True), 393 wait_for_sync=True, 394 hypervisor=fn('hypervisor', None), 395 hvparams=hvparams, 396 beparams=beparams, 397 file_storage_dir=fn('file_storage_dir', None), 398 file_driver=fn('file_driver', 'loop'), 399 ) 400 401 return baserlib.SubmitJob([op])
402 403
404 -class R_2_instances_name(baserlib.R_Generic):
405 """/2/instances/[instance_name] resources. 406 407 """ 408 DOC_URI = "/2/instances/[instance_name]" 409
410 - def GET(self):
411 """Send information about an instance. 412 413 """ 414 client = baserlib.GetClient() 415 instance_name = self.items[0] 416 result = client.QueryInstances(names=[instance_name], fields=I_FIELDS, 417 use_locking=self.useLocking()) 418 419 return baserlib.MapFields(I_FIELDS, result[0])
420
421 - def DELETE(self):
422 """Delete an instance. 423 424 """ 425 op = opcodes.OpRemoveInstance(instance_name=self.items[0], 426 ignore_failures=False) 427 return baserlib.SubmitJob([op])
428 429
430 -class R_2_instances_name_reboot(baserlib.R_Generic):
431 """/2/instances/[instance_name]/reboot resource. 432 433 Implements an instance reboot. 434 435 """ 436 437 DOC_URI = "/2/instances/[instance_name]/reboot" 438
439 - def POST(self):
440 """Reboot an instance. 441 442 The URI takes type=[hard|soft|full] and 443 ignore_secondaries=[False|True] parameters. 444 445 """ 446 instance_name = self.items[0] 447 reboot_type = self.queryargs.get('type', 448 [constants.INSTANCE_REBOOT_HARD])[0] 449 ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries')) 450 op = opcodes.OpRebootInstance(instance_name=instance_name, 451 reboot_type=reboot_type, 452 ignore_secondaries=ignore_secondaries) 453 454 return baserlib.SubmitJob([op])
455 456
457 -class R_2_instances_name_startup(baserlib.R_Generic):
458 """/2/instances/[instance_name]/startup resource. 459 460 Implements an instance startup. 461 462 """ 463 464 DOC_URI = "/2/instances/[instance_name]/startup" 465
466 - def PUT(self):
467 """Startup an instance. 468 469 The URI takes force=[False|True] parameter to start the instance 470 if even if secondary disks are failing. 471 472 """ 473 instance_name = self.items[0] 474 force_startup = bool(self._checkIntVariable('force')) 475 op = opcodes.OpStartupInstance(instance_name=instance_name, 476 force=force_startup) 477 478 return baserlib.SubmitJob([op])
479 480
481 -class R_2_instances_name_shutdown(baserlib.R_Generic):
482 """/2/instances/[instance_name]/shutdown resource. 483 484 Implements an instance shutdown. 485 486 """ 487 488 DOC_URI = "/2/instances/[instance_name]/shutdown" 489
490 - def PUT(self):
491 """Shutdown an instance. 492 493 """ 494 instance_name = self.items[0] 495 op = opcodes.OpShutdownInstance(instance_name=instance_name) 496 497 return baserlib.SubmitJob([op])
498 499
500 -class R_2_instances_name_reinstall(baserlib.R_Generic):
501 """/2/instances/[instance_name]/reinstall resource. 502 503 Implements an instance reinstall. 504 505 """ 506 507 DOC_URI = "/2/instances/[instance_name]/reinstall" 508
509 - def POST(self):
510 """Reinstall an instance. 511 512 The URI takes os=name and nostartup=[0|1] optional 513 parameters. By default, the instance will be started 514 automatically. 515 516 """ 517 instance_name = self.items[0] 518 ostype = self._checkStringVariable('os') 519 nostartup = self._checkIntVariable('nostartup') 520 ops = [ 521 opcodes.OpShutdownInstance(instance_name=instance_name), 522 opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype), 523 ] 524 if not nostartup: 525 ops.append(opcodes.OpStartupInstance(instance_name=instance_name, 526 force=False)) 527 return baserlib.SubmitJob(ops)
528 529
530 -class _R_Tags(baserlib.R_Generic):
531 """ Quasiclass for tagging resources 532 533 Manages tags. Inheriting this class you suppose to define DOC_URI and 534 TAG_LEVEL for it. 535 536 """ 537 TAG_LEVEL = None 538
539 - def __init__(self, items, queryargs, req):
540 """A tag resource constructor. 541 542 We have to override the default to sort out cluster naming case. 543 544 """ 545 baserlib.R_Generic.__init__(self, items, queryargs, req) 546 547 if self.TAG_LEVEL != constants.TAG_CLUSTER: 548 self.name = items[0] 549 else: 550 self.name = ""
551
552 - def GET(self):
553 """Returns a list of tags. 554 555 Example: ["tag1", "tag2", "tag3"] 556 557 """ 558 return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
559
560 - def PUT(self):
561 """Add a set of tags. 562 563 The request as a list of strings should be PUT to this URI. And 564 you'll have back a job id. 565 566 """ 567 if 'tag' not in self.queryargs: 568 raise http.HttpBadRequest("Please specify tag(s) to add using the" 569 " the 'tag' parameter") 570 return baserlib._Tags_PUT(self.TAG_LEVEL, 571 self.queryargs['tag'], name=self.name)
572
573 - def DELETE(self):
574 """Delete a tag. 575 576 In order to delete a set of tags, the DELETE 577 request should be addressed to URI like: 578 /tags?tag=[tag]&tag=[tag] 579 580 """ 581 if 'tag' not in self.queryargs: 582 # no we not gonna delete all tags 583 raise http.HttpBadRequest("Cannot delete all tags - please specify" 584 " tag(s) using the 'tag' parameter") 585 return baserlib._Tags_DELETE(self.TAG_LEVEL, 586 self.queryargs['tag'], 587 name=self.name)
588 589
590 -class R_2_instances_name_tags(_R_Tags):
591 """ /2/instances/[instance_name]/tags resource. 592 593 Manages per-instance tags. 594 595 """ 596 DOC_URI = "/2/instances/[instance_name]/tags" 597 TAG_LEVEL = constants.TAG_INSTANCE
598 599
600 -class R_2_nodes_name_tags(_R_Tags):
601 """ /2/nodes/[node_name]/tags resource. 602 603 Manages per-node tags. 604 605 """ 606 DOC_URI = "/2/nodes/[node_name]/tags" 607 TAG_LEVEL = constants.TAG_NODE
608 609
610 -class R_2_tags(_R_Tags):
611 """ /2/instances/tags resource. 612 613 Manages cluster tags. 614 615 """ 616 DOC_URI = "/2/tags" 617 TAG_LEVEL = constants.TAG_CLUSTER
618