Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 1e82bc80

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

    
533
  DOC_URI = "/2/instances/[instance_name]/reinstall"
534

    
535
  def POST(self):
536
    """Reinstall an instance.
537

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

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

    
555

    
556
class _R_Tags(baserlib.R_Generic):
557
  """ Quasiclass for tagging resources
558

559
  Manages tags. When inheriting this class you must define the
560
  TAG_LEVEL for it.
561

562
  """
563
  TAG_LEVEL = None
564

    
565
  def __init__(self, items, queryargs, req):
566
    """A tag resource constructor.
567

568
    We have to override the default to sort out cluster naming case.
569

570
    """
571
    baserlib.R_Generic.__init__(self, items, queryargs, req)
572

    
573
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
574
      self.name = items[0]
575
    else:
576
      self.name = ""
577

    
578
  def GET(self):
579
    """Returns a list of tags.
580

581
    Example: ["tag1", "tag2", "tag3"]
582

583
    """
584
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
585

    
586
  def PUT(self):
587
    """Add a set of tags.
588

589
    The request as a list of strings should be PUT to this URI. And
590
    you'll have back a job id.
591

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

    
600
  def DELETE(self):
601
    """Delete a tag.
602

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

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

    
617

    
618
class R_2_instances_name_tags(_R_Tags):
619
  """ /2/instances/[instance_name]/tags resource.
620

621
  Manages per-instance tags.
622

623
  """
624
  TAG_LEVEL = constants.TAG_INSTANCE
625

    
626

    
627
class R_2_nodes_name_tags(_R_Tags):
628
  """ /2/nodes/[node_name]/tags resource.
629

630
  Manages per-node tags.
631

632
  """
633
  TAG_LEVEL = constants.TAG_NODE
634

    
635

    
636
class R_2_tags(_R_Tags):
637
  """ /2/instances/tags resource.
638

639
  Manages cluster tags.
640

641
  """
642
  TAG_LEVEL = constants.TAG_CLUSTER