Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 73086975

History | View | Annotate | Download (17.5 kB)

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
from ganeti import opcodes
27
from ganeti import http
28
from ganeti import constants
29
from ganeti import cli
30
from ganeti import rapi
31
from ganeti.rapi import baserlib
32

    
33

    
34
I_FIELDS = ["name", "admin_state", "os",
35
            "pnode", "snodes",
36
            "disk_template",
37
            "nic.ips", "nic.macs", "nic.modes", "nic.links",
38
            "network_port",
39
            "disk.sizes", "disk_usage",
40
            "beparams", "hvparams",
41
            "oper_state", "oper_ram", "status",
42
            "tags"]
43

    
44
N_FIELDS = ["name", "offline", "master_candidate", "drained",
45
            "dtotal", "dfree",
46
            "mtotal", "mnode", "mfree",
47
            "pinst_cnt", "sinst_cnt", "tags",
48
            "ctotal", "cnodes", "csockets",
49
            ]
50

    
51
_NR_DRAINED = "drained"
52
_NR_MASTER_CANDIATE = "master-candidate"
53
_NR_MASTER = "master"
54
_NR_OFFLINE = "offline"
55
_NR_REGULAR = "regular"
56

    
57
_NR_MAP = {
58
  "M": _NR_MASTER,
59
  "C": _NR_MASTER_CANDIATE,
60
  "D": _NR_DRAINED,
61
  "O": _NR_OFFLINE,
62
  "R": _NR_REGULAR,
63
  }
64

    
65

    
66
class R_version(baserlib.R_Generic):
67
  """/version resource.
68

69
  This resource should be used to determine the remote API version and
70
  to adapt clients accordingly.
71

72
  """
73
  def GET(self):
74
    """Returns the remote API version.
75

76
    """
77
    return constants.RAPI_VERSION
78

    
79

    
80
class R_2_info(baserlib.R_Generic):
81
  """Cluster info.
82

83
  """
84
  def GET(self):
85
    """Returns cluster information.
86

87
    """
88
    client = baserlib.GetClient()
89
    return client.QueryClusterInfo()
90

    
91

    
92
class R_2_os(baserlib.R_Generic):
93
  """/2/os resource.
94

95
  """
96
  def GET(self):
97
    """Return a list of all OSes.
98

99
    Can return error 500 in case of a problem.
100

101
    Example: ["debian-etch"]
102

103
    """
104
    cl = baserlib.GetClient()
105
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
106
    job_id = baserlib.SubmitJob([op], cl)
107
    # we use custom feedback function, instead of print we log the status
108
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
109
    diagnose_data = result[0]
110

    
111
    if not isinstance(diagnose_data, list):
112
      raise http.HttpBadGateway(message="Can't get OS list")
113

    
114
    return [row[0] for row in diagnose_data if row[1]]
115

    
116

    
117
class R_2_jobs(baserlib.R_Generic):
118
  """/2/jobs resource.
119

120
  """
121
  def GET(self):
122
    """Returns a dictionary of jobs.
123

124
    @return: a dictionary with jobs id and uri.
125

126
    """
127
    fields = ["id"]
128
    cl = baserlib.GetClient()
129
    # Convert the list of lists to the list of ids
130
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
131
    return baserlib.BuildUriList(result, "/2/jobs/%s",
132
                                 uri_fields=("id", "uri"))
133

    
134

    
135
class R_2_jobs_id(baserlib.R_Generic):
136
  """/2/jobs/[job_id] resource.
137

138
  """
139
  def GET(self):
140
    """Returns a job status.
141

142
    @return: a dictionary with job parameters.
143
        The result includes:
144
            - id: job ID as a number
145
            - status: current job status as a string
146
            - ops: involved OpCodes as a list of dictionaries for each
147
              opcodes in the job
148
            - opstatus: OpCodes status as a list
149
            - opresult: OpCodes results as a list of lists
150

151
    """
152
    fields = ["id", "ops", "status", "summary",
153
              "opstatus", "opresult", "oplog",
154
              "received_ts", "start_ts", "end_ts",
155
              ]
156
    job_id = self.items[0]
157
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
158
    if result is None:
159
      raise http.HttpNotFound()
160
    return baserlib.MapFields(fields, result)
161

    
162
  def DELETE(self):
163
    """Cancel not-yet-started job.
164

165
    """
166
    job_id = self.items[0]
167
    result = baserlib.GetClient().CancelJob(job_id)
168
    return result
169

    
170

    
171
class R_2_nodes(baserlib.R_Generic):
172
  """/2/nodes resource.
173

174
  """
175
  def GET(self):
176
    """Returns a list of all nodes.
177

178
    """
179
    client = baserlib.GetClient()
180

    
181
    if self.useBulk():
182
      bulkdata = client.QueryNodes([], N_FIELDS, False)
183
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
184
    else:
185
      nodesdata = client.QueryNodes([], ["name"], False)
186
      nodeslist = [row[0] for row in nodesdata]
187
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
188
                                   uri_fields=("id", "uri"))
189

    
190

    
191
class R_2_nodes_name(baserlib.R_Generic):
192
  """/2/nodes/[node_name] resources.
193

194
  """
195
  def GET(self):
196
    """Send information about a node.
197

198
    """
199
    node_name = self.items[0]
200
    client = baserlib.GetClient()
201
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
202
                               use_locking=self.useLocking())
203

    
204
    return baserlib.MapFields(N_FIELDS, result[0])
205

    
206

    
207
class R_2_nodes_name_role(baserlib.R_Generic):
208
  """ /2/nodes/[node_name]/role resource.
209

210
  """
211
  def GET(self):
212
    """Returns the current node role.
213

214
    @return: Node role
215

216
    """
217
    node_name = self.items[0]
218
    client = baserlib.GetClient()
219
    result = client.QueryNodes(names=[node_name], fields=["role"],
220
                               use_locking=self.useLocking())
221

    
222
    return _NR_MAP[result[0][0]]
223

    
224
  def PUT(self):
225
    """Sets the node role.
226

227
    @return: a job id
228

229
    """
230
    if not isinstance(self.req.request_body, basestring):
231
      raise http.HttpBadRequest("Invalid body contents, not a string")
232

    
233
    node_name = self.items[0]
234
    role = self.req.request_body
235

    
236
    if role == _NR_REGULAR:
237
      candidate = False
238
      offline = False
239
      drained = False
240

    
241
    elif role == _NR_MASTER_CANDIATE:
242
      candidate = True
243
      offline = drained = None
244

    
245
    elif role == _NR_DRAINED:
246
      drained = True
247
      candidate = offline = None
248

    
249
    elif role == _NR_OFFLINE:
250
      offline = True
251
      candidate = drained = None
252

    
253
    else:
254
      raise http.HttpBadRequest("Can't set '%s' role" % role)
255

    
256
    op = opcodes.OpSetNodeParams(node_name=node_name,
257
                                 master_candidate=candidate,
258
                                 offline=offline,
259
                                 drained=drained,
260
                                 force=bool(self.useForce()))
261

    
262
    return baserlib.SubmitJob([op])
263

    
264

    
265
class R_2_nodes_name_evacuate(baserlib.R_Generic):
266
  """/2/nodes/[node_name]/evacuate resource.
267

268
  """
269
  def POST(self):
270
    """Evacuate all secondary instances off a node.
271

272
    """
273
    node_name = self.items[0]
274
    remote_node = self._checkStringVariable("remote_node", default=None)
275
    iallocator = self._checkStringVariable("iallocator", default=None)
276

    
277
    op = opcodes.OpEvacuateNode(node_name=node_name,
278
                                remote_node=remote_node,
279
                                iallocator=iallocator)
280

    
281
    return baserlib.SubmitJob([op])
282

    
283

    
284
class R_2_nodes_name_migrate(baserlib.R_Generic):
285
  """/2/nodes/[node_name]/migrate resource.
286

287
  """
288
  def POST(self):
289
    """Migrate all primary instances from a node.
290

291
    """
292
    node_name = self.items[0]
293
    live = bool(self._checkIntVariable("live", default=1))
294

    
295
    op = opcodes.OpMigrateNode(node_name=node_name, live=live)
296

    
297
    return baserlib.SubmitJob([op])
298

    
299

    
300
class R_2_nodes_name_storage(baserlib.R_Generic):
301
  """/2/nodes/[node_name]/storage ressource.
302

303
  """
304
  # LUQueryNodeStorage acquires locks, hence restricting access to GET
305
  GET_ACCESS = [rapi.RAPI_ACCESS_WRITE]
306

    
307
  def GET(self):
308
    node_name = self.items[0]
309

    
310
    storage_type = self._checkStringVariable("storage_type", None)
311
    if not storage_type:
312
      raise http.HttpBadRequest("Missing the required 'storage_type'"
313
                                " parameter")
314

    
315
    output_fields = self._checkStringVariable("output_fields", None)
316
    if not output_fields:
317
      raise http.HttpBadRequest("Missing the required 'output_fields'"
318
                                " parameter")
319

    
320
    op = opcodes.OpQueryNodeStorage(nodes=[node_name],
321
                                    storage_type=storage_type,
322
                                    output_fields=output_fields.split(","))
323
    return baserlib.SubmitJob([op])
324

    
325

    
326
class R_2_nodes_name_storage_modify(baserlib.R_Generic):
327
  """/2/nodes/[node_name]/storage/modify ressource.
328

329
  """
330
  def PUT(self):
331
    node_name = self.items[0]
332

    
333
    storage_type = self._checkStringVariable("storage_type", None)
334
    if not storage_type:
335
      raise http.HttpBadRequest("Missing the required 'storage_type'"
336
                                " parameter")
337

    
338
    name = self._checkStringVariable("name", None)
339
    if not name:
340
      raise http.HttpBadRequest("Missing the required 'name'"
341
                                " parameter")
342

    
343
    changes = {}
344

    
345
    if "allocatable" in self.queryargs:
346
      changes[constants.SF_ALLOCATABLE] = \
347
        bool(self._checkIntVariable("allocatable", default=1))
348

    
349
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
350
                                     storage_type=storage_type,
351
                                     name=name,
352
                                     changes=changes)
353
    return baserlib.SubmitJob([op])
354

    
355

    
356
class R_2_instances(baserlib.R_Generic):
357
  """/2/instances resource.
358

359
  """
360
  def GET(self):
361
    """Returns a list of all available instances.
362

363
    """
364
    client = baserlib.GetClient()
365

    
366
    use_locking = self.useLocking()
367
    if self.useBulk():
368
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
369
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
370
    else:
371
      instancesdata = client.QueryInstances([], ["name"], use_locking)
372
      instanceslist = [row[0] for row in instancesdata]
373
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
374
                                   uri_fields=("id", "uri"))
375

    
376
  def POST(self):
377
    """Create an instance.
378

379
    @return: a job id
380

381
    """
382
    if not isinstance(self.req.request_body, dict):
383
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
384

    
385
    beparams = baserlib.MakeParamsDict(self.req.request_body,
386
                                       constants.BES_PARAMETERS)
387
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
388
                                       constants.HVS_PARAMETERS)
389
    fn = self.getBodyParameter
390

    
391
    # disk processing
392
    disk_data = fn('disks')
393
    if not isinstance(disk_data, list):
394
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
395
    disks = []
396
    for idx, d in enumerate(disk_data):
397
      if not isinstance(d, int):
398
        raise http.HttpBadRequest("Disk %d specification wrong: should"
399
                                  " be an integer")
400
      disks.append({"size": d})
401
    # nic processing (one nic only)
402
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
403
    if fn("ip", None) is not None:
404
      nics[0]["ip"] = fn("ip")
405
    if fn("mode", None) is not None:
406
      nics[0]["mode"] = fn("mode")
407
    if fn("link", None) is not None:
408
      nics[0]["link"] = fn("link")
409
    if fn("bridge", None) is not None:
410
       nics[0]["bridge"] = fn("bridge")
411

    
412
    op = opcodes.OpCreateInstance(
413
      mode=constants.INSTANCE_CREATE,
414
      instance_name=fn('name'),
415
      disks=disks,
416
      disk_template=fn('disk_template'),
417
      os_type=fn('os'),
418
      pnode=fn('pnode', None),
419
      snode=fn('snode', None),
420
      iallocator=fn('iallocator', None),
421
      nics=nics,
422
      start=fn('start', True),
423
      ip_check=fn('ip_check', True),
424
      wait_for_sync=True,
425
      hypervisor=fn('hypervisor', None),
426
      hvparams=hvparams,
427
      beparams=beparams,
428
      file_storage_dir=fn('file_storage_dir', None),
429
      file_driver=fn('file_driver', 'loop'),
430
      dry_run=bool(self.dryRun()),
431
      )
432

    
433
    return baserlib.SubmitJob([op])
434

    
435

    
436
class R_2_instances_name(baserlib.R_Generic):
437
  """/2/instances/[instance_name] resources.
438

439
  """
440
  def GET(self):
441
    """Send information about an instance.
442

443
    """
444
    client = baserlib.GetClient()
445
    instance_name = self.items[0]
446
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
447
                                   use_locking=self.useLocking())
448

    
449
    return baserlib.MapFields(I_FIELDS, result[0])
450

    
451
  def DELETE(self):
452
    """Delete an instance.
453

454
    """
455
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
456
                                  ignore_failures=False,
457
                                  dry_run=bool(self.dryRun()))
458
    return baserlib.SubmitJob([op])
459

    
460

    
461
class R_2_instances_name_reboot(baserlib.R_Generic):
462
  """/2/instances/[instance_name]/reboot resource.
463

464
  Implements an instance reboot.
465

466
  """
467
  def POST(self):
468
    """Reboot an instance.
469

470
    The URI takes type=[hard|soft|full] and
471
    ignore_secondaries=[False|True] parameters.
472

473
    """
474
    instance_name = self.items[0]
475
    reboot_type = self.queryargs.get('type',
476
                                     [constants.INSTANCE_REBOOT_HARD])[0]
477
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
478
                                                 [False])[0])
479
    op = opcodes.OpRebootInstance(instance_name=instance_name,
480
                                  reboot_type=reboot_type,
481
                                  ignore_secondaries=ignore_secondaries,
482
                                  dry_run=bool(self.dryRun()))
483

    
484
    return baserlib.SubmitJob([op])
485

    
486

    
487
class R_2_instances_name_startup(baserlib.R_Generic):
488
  """/2/instances/[instance_name]/startup resource.
489

490
  Implements an instance startup.
491

492
  """
493
  def PUT(self):
494
    """Startup an instance.
495

496
    The URI takes force=[False|True] parameter to start the instance
497
    if even if secondary disks are failing.
498

499
    """
500
    instance_name = self.items[0]
501
    force_startup = bool(self.queryargs.get('force', [False])[0])
502
    op = opcodes.OpStartupInstance(instance_name=instance_name,
503
                                   force=force_startup,
504
                                   dry_run=bool(self.dryRun()))
505

    
506
    return baserlib.SubmitJob([op])
507

    
508

    
509
class R_2_instances_name_shutdown(baserlib.R_Generic):
510
  """/2/instances/[instance_name]/shutdown resource.
511

512
  Implements an instance shutdown.
513

514
  """
515
  def PUT(self):
516
    """Shutdown an instance.
517

518
    """
519
    instance_name = self.items[0]
520
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
521
                                    dry_run=bool(self.dryRun()))
522

    
523
    return baserlib.SubmitJob([op])
524

    
525

    
526
class R_2_instances_name_reinstall(baserlib.R_Generic):
527
  """/2/instances/[instance_name]/reinstall resource.
528

529
  Implements an instance reinstall.
530

531
  """
532
  def POST(self):
533
    """Reinstall an instance.
534

535
    The URI takes os=name and nostartup=[0|1] optional
536
    parameters. By default, the instance will be started
537
    automatically.
538

539
    """
540
    instance_name = self.items[0]
541
    ostype = self._checkStringVariable('os')
542
    nostartup = self._checkIntVariable('nostartup')
543
    ops = [
544
      opcodes.OpShutdownInstance(instance_name=instance_name),
545
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
546
      ]
547
    if not nostartup:
548
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
549
                                           force=False))
550
    return baserlib.SubmitJob(ops)
551

    
552

    
553
class _R_Tags(baserlib.R_Generic):
554
  """ Quasiclass for tagging resources
555

556
  Manages tags. When inheriting this class you must define the
557
  TAG_LEVEL for it.
558

559
  """
560
  TAG_LEVEL = None
561

    
562
  def __init__(self, items, queryargs, req):
563
    """A tag resource constructor.
564

565
    We have to override the default to sort out cluster naming case.
566

567
    """
568
    baserlib.R_Generic.__init__(self, items, queryargs, req)
569

    
570
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
571
      self.name = items[0]
572
    else:
573
      self.name = ""
574

    
575
  def GET(self):
576
    """Returns a list of tags.
577

578
    Example: ["tag1", "tag2", "tag3"]
579

580
    """
581
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
582

    
583
  def PUT(self):
584
    """Add a set of tags.
585

586
    The request as a list of strings should be PUT to this URI. And
587
    you'll have back a job id.
588

589
    """
590
    if 'tag' not in self.queryargs:
591
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
592
                                " the 'tag' parameter")
593
    return baserlib._Tags_PUT(self.TAG_LEVEL,
594
                              self.queryargs['tag'], name=self.name,
595
                              dry_run=bool(self.dryRun()))
596

    
597
  def DELETE(self):
598
    """Delete a tag.
599

600
    In order to delete a set of tags, the DELETE
601
    request should be addressed to URI like:
602
    /tags?tag=[tag]&tag=[tag]
603

604
    """
605
    if 'tag' not in self.queryargs:
606
      # no we not gonna delete all tags
607
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
608
                                " tag(s) using the 'tag' parameter")
609
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
610
                                 self.queryargs['tag'],
611
                                 name=self.name,
612
                                 dry_run=bool(self.dryRun()))
613

    
614

    
615
class R_2_instances_name_tags(_R_Tags):
616
  """ /2/instances/[instance_name]/tags resource.
617

618
  Manages per-instance tags.
619

620
  """
621
  TAG_LEVEL = constants.TAG_INSTANCE
622

    
623

    
624
class R_2_nodes_name_tags(_R_Tags):
625
  """ /2/nodes/[node_name]/tags resource.
626

627
  Manages per-node tags.
628

629
  """
630
  TAG_LEVEL = constants.TAG_NODE
631

    
632

    
633
class R_2_tags(_R_Tags):
634
  """ /2/instances/tags resource.
635

636
  Manages cluster tags.
637

638
  """
639
  TAG_LEVEL = constants.TAG_CLUSTER