Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 8381fa2d

History | View | Annotate | Download (16.6 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_instances(baserlib.R_Generic):
327
  """/2/instances resource.
328

329
  """
330
  def GET(self):
331
    """Returns a list of all available instances.
332

333
    """
334
    client = baserlib.GetClient()
335

    
336
    use_locking = self.useLocking()
337
    if self.useBulk():
338
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
339
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
340
    else:
341
      instancesdata = client.QueryInstances([], ["name"], use_locking)
342
      instanceslist = [row[0] for row in instancesdata]
343
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
344
                                   uri_fields=("id", "uri"))
345

    
346
  def POST(self):
347
    """Create an instance.
348

349
    @return: a job id
350

351
    """
352
    if not isinstance(self.req.request_body, dict):
353
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
354

    
355
    beparams = baserlib.MakeParamsDict(self.req.request_body,
356
                                       constants.BES_PARAMETERS)
357
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
358
                                       constants.HVS_PARAMETERS)
359
    fn = self.getBodyParameter
360

    
361
    # disk processing
362
    disk_data = fn('disks')
363
    if not isinstance(disk_data, list):
364
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
365
    disks = []
366
    for idx, d in enumerate(disk_data):
367
      if not isinstance(d, int):
368
        raise http.HttpBadRequest("Disk %d specification wrong: should"
369
                                  " be an integer")
370
      disks.append({"size": d})
371
    # nic processing (one nic only)
372
    nics = [{"mac": fn("mac", constants.VALUE_AUTO)}]
373
    if fn("ip", None) is not None:
374
      nics[0]["ip"] = fn("ip")
375
    if fn("mode", None) is not None:
376
      nics[0]["mode"] = fn("mode")
377
    if fn("link", None) is not None:
378
      nics[0]["link"] = fn("link")
379
    if fn("bridge", None) is not None:
380
       nics[0]["bridge"] = fn("bridge")
381

    
382
    op = opcodes.OpCreateInstance(
383
      mode=constants.INSTANCE_CREATE,
384
      instance_name=fn('name'),
385
      disks=disks,
386
      disk_template=fn('disk_template'),
387
      os_type=fn('os'),
388
      pnode=fn('pnode', None),
389
      snode=fn('snode', None),
390
      iallocator=fn('iallocator', None),
391
      nics=nics,
392
      start=fn('start', True),
393
      ip_check=fn('ip_check', True),
394
      wait_for_sync=True,
395
      hypervisor=fn('hypervisor', None),
396
      hvparams=hvparams,
397
      beparams=beparams,
398
      file_storage_dir=fn('file_storage_dir', None),
399
      file_driver=fn('file_driver', 'loop'),
400
      dry_run=bool(self.dryRun()),
401
      )
402

    
403
    return baserlib.SubmitJob([op])
404

    
405

    
406
class R_2_instances_name(baserlib.R_Generic):
407
  """/2/instances/[instance_name] resources.
408

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
                                  dry_run=bool(self.dryRun()))
428
    return baserlib.SubmitJob([op])
429

    
430

    
431
class R_2_instances_name_reboot(baserlib.R_Generic):
432
  """/2/instances/[instance_name]/reboot resource.
433

434
  Implements an instance reboot.
435

436
  """
437
  def POST(self):
438
    """Reboot an instance.
439

440
    The URI takes type=[hard|soft|full] and
441
    ignore_secondaries=[False|True] parameters.
442

443
    """
444
    instance_name = self.items[0]
445
    reboot_type = self.queryargs.get('type',
446
                                     [constants.INSTANCE_REBOOT_HARD])[0]
447
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
448
                                                 [False])[0])
449
    op = opcodes.OpRebootInstance(instance_name=instance_name,
450
                                  reboot_type=reboot_type,
451
                                  ignore_secondaries=ignore_secondaries,
452
                                  dry_run=bool(self.dryRun()))
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
  def PUT(self):
464
    """Startup an instance.
465

466
    The URI takes force=[False|True] parameter to start the instance
467
    if even if secondary disks are failing.
468

469
    """
470
    instance_name = self.items[0]
471
    force_startup = bool(self.queryargs.get('force', [False])[0])
472
    op = opcodes.OpStartupInstance(instance_name=instance_name,
473
                                   force=force_startup,
474
                                   dry_run=bool(self.dryRun()))
475

    
476
    return baserlib.SubmitJob([op])
477

    
478

    
479
class R_2_instances_name_shutdown(baserlib.R_Generic):
480
  """/2/instances/[instance_name]/shutdown resource.
481

482
  Implements an instance shutdown.
483

484
  """
485
  def PUT(self):
486
    """Shutdown an instance.
487

488
    """
489
    instance_name = self.items[0]
490
    op = opcodes.OpShutdownInstance(instance_name=instance_name,
491
                                    dry_run=bool(self.dryRun()))
492

    
493
    return baserlib.SubmitJob([op])
494

    
495

    
496
class R_2_instances_name_reinstall(baserlib.R_Generic):
497
  """/2/instances/[instance_name]/reinstall resource.
498

499
  Implements an instance reinstall.
500

501
  """
502

    
503
  DOC_URI = "/2/instances/[instance_name]/reinstall"
504

    
505
  def POST(self):
506
    """Reinstall an instance.
507

508
    The URI takes os=name and nostartup=[0|1] optional
509
    parameters. By default, the instance will be started
510
    automatically.
511

512
    """
513
    instance_name = self.items[0]
514
    ostype = self._checkStringVariable('os')
515
    nostartup = self._checkIntVariable('nostartup')
516
    ops = [
517
      opcodes.OpShutdownInstance(instance_name=instance_name),
518
      opcodes.OpReinstallInstance(instance_name=instance_name, os_type=ostype),
519
      ]
520
    if not nostartup:
521
      ops.append(opcodes.OpStartupInstance(instance_name=instance_name,
522
                                           force=False))
523
    return baserlib.SubmitJob(ops)
524

    
525

    
526
class _R_Tags(baserlib.R_Generic):
527
  """ Quasiclass for tagging resources
528

529
  Manages tags. When inheriting this class you must define the
530
  TAG_LEVEL for it.
531

532
  """
533
  TAG_LEVEL = None
534

    
535
  def __init__(self, items, queryargs, req):
536
    """A tag resource constructor.
537

538
    We have to override the default to sort out cluster naming case.
539

540
    """
541
    baserlib.R_Generic.__init__(self, items, queryargs, req)
542

    
543
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
544
      self.name = items[0]
545
    else:
546
      self.name = ""
547

    
548
  def GET(self):
549
    """Returns a list of tags.
550

551
    Example: ["tag1", "tag2", "tag3"]
552

553
    """
554
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
555

    
556
  def PUT(self):
557
    """Add a set of tags.
558

559
    The request as a list of strings should be PUT to this URI. And
560
    you'll have back a job id.
561

562
    """
563
    if 'tag' not in self.queryargs:
564
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
565
                                " the 'tag' parameter")
566
    return baserlib._Tags_PUT(self.TAG_LEVEL,
567
                              self.queryargs['tag'], name=self.name,
568
                              dry_run=bool(self.dryRun()))
569

    
570
  def DELETE(self):
571
    """Delete a tag.
572

573
    In order to delete a set of tags, the DELETE
574
    request should be addressed to URI like:
575
    /tags?tag=[tag]&tag=[tag]
576

577
    """
578
    if 'tag' not in self.queryargs:
579
      # no we not gonna delete all tags
580
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
581
                                " tag(s) using the 'tag' parameter")
582
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
583
                                 self.queryargs['tag'],
584
                                 name=self.name,
585
                                 dry_run=bool(self.dryRun()))
586

    
587

    
588
class R_2_instances_name_tags(_R_Tags):
589
  """ /2/instances/[instance_name]/tags resource.
590

591
  Manages per-instance tags.
592

593
  """
594
  TAG_LEVEL = constants.TAG_INSTANCE
595

    
596

    
597
class R_2_nodes_name_tags(_R_Tags):
598
  """ /2/nodes/[node_name]/tags resource.
599

600
  Manages per-node tags.
601

602
  """
603
  TAG_LEVEL = constants.TAG_NODE
604

    
605

    
606
class R_2_tags(_R_Tags):
607
  """ /2/instances/tags resource.
608

609
  Manages cluster tags.
610

611
  """
612
  TAG_LEVEL = constants.TAG_CLUSTER