Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 93962b80

History | View | Annotate | Download (15.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.rapi import baserlib
31

    
32

    
33

    
34
I_FIELDS = ["name", "admin_state", "os",
35
            "pnode", "snodes",
36
            "disk_template",
37
            "nic.ips", "nic.macs", "nic.bridges",
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
            "pip", "sip", "serial_no", "role",
50
            "pinst_list", "sinst_list",
51
            ]
52

    
53

    
54
class R_version(baserlib.R_Generic):
55
  """/version resource.
56

57
  This resource should be used to determine the remote API version and
58
  to adapt clients accordingly.
59

60
  """
61
  DOC_URI = "/version"
62

    
63
  def GET(self):
64
    """Returns the remote API version.
65

66
    """
67
    return constants.RAPI_VERSION
68

    
69

    
70
class R_2_info(baserlib.R_Generic):
71
  """Cluster info.
72

73
  """
74
  DOC_URI = "/2/info"
75

    
76
  def GET(self):
77
    """Returns cluster information.
78

79
    Example::
80

81
    {
82
      "config_version": 2000000,
83
      "name": "cluster",
84
      "software_version": "2.0.0~beta2",
85
      "os_api_version": 10,
86
      "export_version": 0,
87
      "candidate_pool_size": 10,
88
      "enabled_hypervisors": [
89
        "fake"
90
      ],
91
      "hvparams": {
92
        "fake": {}
93
       },
94
      "default_hypervisor": "fake",
95
      "master": "node1.example.com",
96
      "architecture": [
97
        "64bit",
98
        "x86_64"
99
      ],
100
      "protocol_version": 20,
101
      "beparams": {
102
        "default": {
103
          "auto_balance": true,
104
          "vcpus": 1,
105
          "memory": 128
106
         }
107
        }
108
      }
109

110
    """
111
    client = baserlib.GetClient()
112
    return client.QueryClusterInfo()
113

    
114

    
115
class R_2_os(baserlib.R_Generic):
116
  """/2/os resource.
117

118
  """
119
  DOC_URI = "/2/os"
120

    
121
  def GET(self):
122
    """Return a list of all OSes.
123

124
    Can return error 500 in case of a problem.
125

126
    Example: ["debian-etch"]
127

128
    """
129
    cl = baserlib.GetClient()
130
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
131
    job_id = baserlib.SubmitJob([op], cl)
132
    # we use custom feedback function, instead of print we log the status
133
    result = cli.PollJob(job_id, cl, feedback_fn=baserlib.FeedbackFn)
134
    diagnose_data = result[0]
135

    
136
    if not isinstance(diagnose_data, list):
137
      raise http.HttpBadGateway(message="Can't get OS list")
138

    
139
    return [row[0] for row in diagnose_data if row[1]]
140

    
141

    
142
class R_2_jobs(baserlib.R_Generic):
143
  """/2/jobs resource.
144

145
  """
146
  DOC_URI = "/2/jobs"
147

    
148
  def GET(self):
149
    """Returns a dictionary of jobs.
150

151
    @return: a dictionary with jobs id and uri.
152

153
    """
154
    fields = ["id"]
155
    cl = baserlib.GetClient()
156
    # Convert the list of lists to the list of ids
157
    result = [job_id for [job_id] in cl.QueryJobs(None, fields)]
158
    return baserlib.BuildUriList(result, "/2/jobs/%s",
159
                                 uri_fields=("id", "uri"))
160

    
161

    
162
class R_2_jobs_id(baserlib.R_Generic):
163
  """/2/jobs/[job_id] resource.
164

165
  """
166
  DOC_URI = "/2/jobs/[job_id]"
167

    
168
  def GET(self):
169
    """Returns a job status.
170

171
    @return: a dictionary with job parameters.
172
        The result includes:
173
            - id: job ID as a number
174
            - status: current job status as a string
175
            - ops: involved OpCodes as a list of dictionaries for each
176
              opcodes in the job
177
            - opstatus: OpCodes status as a list
178
            - opresult: OpCodes results as a list of lists
179

180
    """
181
    fields = ["id", "ops", "status", "summary",
182
              "opstatus", "opresult", "oplog",
183
              "received_ts", "start_ts", "end_ts",
184
              ]
185
    job_id = self.items[0]
186
    result = baserlib.GetClient().QueryJobs([job_id, ], fields)[0]
187
    if result is None:
188
      raise http.HttpNotFound()
189
    return baserlib.MapFields(fields, result)
190

    
191
  def DELETE(self):
192
    """Cancel not-yet-started job.
193

194
    """
195
    job_id = self.items[0]
196
    result = baserlib.GetClient().CancelJob(job_id)
197
    return result
198

    
199

    
200
class R_2_nodes(baserlib.R_Generic):
201
  """/2/nodes resource.
202

203
  """
204
  DOC_URI = "/2/nodes"
205

    
206
  def GET(self):
207
    """Returns a list of all nodes.
208

209
    Example::
210

211
      [
212
        {
213
          "id": "node1.example.com",
214
          "uri": "\/instances\/node1.example.com"
215
        },
216
        {
217
          "id": "node2.example.com",
218
          "uri": "\/instances\/node2.example.com"
219
        }
220
      ]
221

222
    If the optional 'bulk' argument is provided and set to 'true'
223
    value (i.e '?bulk=1'), the output contains detailed
224
    information about nodes as a list.
225

226
    Example::
227

228
      [
229
        {
230
          "pinst_cnt": 1,
231
          "mfree": 31280,
232
          "mtotal": 32763,
233
          "name": "www.example.com",
234
          "tags": [],
235
          "mnode": 512,
236
          "dtotal": 5246208,
237
          "sinst_cnt": 2,
238
          "dfree": 5171712,
239
          "offline": false
240
        },
241
        ...
242
      ]
243

244
    @return: a dictionary with 'name' and 'uri' keys for each of them
245

246
    """
247
    client = baserlib.GetClient()
248

    
249
    if self.useBulk():
250
      bulkdata = client.QueryNodes([], N_FIELDS, False)
251
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
252
    else:
253
      nodesdata = client.QueryNodes([], ["name"], False)
254
      nodeslist = [row[0] for row in nodesdata]
255
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
256
                                   uri_fields=("id", "uri"))
257

    
258

    
259
class R_2_nodes_name(baserlib.R_Generic):
260
  """/2/nodes/[node_name] resources.
261

262
  """
263
  DOC_URI = "/nodes/[node_name]"
264

    
265
  def GET(self):
266
    """Send information about a node.
267

268
    """
269
    node_name = self.items[0]
270
    client = baserlib.GetClient()
271
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
272
                               use_locking=self.useLocking())
273

    
274
    return baserlib.MapFields(N_FIELDS, result[0])
275

    
276

    
277
class R_2_instances(baserlib.R_Generic):
278
  """/2/instances resource.
279

280
  """
281
  DOC_URI = "/2/instances"
282

    
283
  def GET(self):
284
    """Returns a list of all available instances.
285

286

287
    Example::
288

289
      [
290
        {
291
          "name": "web.example.com",
292
          "uri": "\/instances\/web.example.com"
293
        },
294
        {
295
          "name": "mail.example.com",
296
          "uri": "\/instances\/mail.example.com"
297
        }
298
      ]
299

300
    If the optional 'bulk' argument is provided and set to 'true'
301
    value (i.e '?bulk=1'), the output contains detailed
302
    information about instances as a list.
303

304
    Example::
305

306
      [
307
        {
308
           "status": "running",
309
           "disk_usage": 20480,
310
           "nic.bridges": [
311
             "xen-br0"
312
            ],
313
           "name": "web.example.com",
314
           "tags": ["tag1", "tag2"],
315
           "beparams": {
316
             "vcpus": 2,
317
             "memory": 512
318
           },
319
           "disk.sizes": [
320
               20480
321
           ],
322
           "pnode": "node1.example.com",
323
           "nic.macs": ["01:23:45:67:89:01"],
324
           "snodes": ["node2.example.com"],
325
           "disk_template": "drbd",
326
           "admin_state": true,
327
           "os": "debian-etch",
328
           "oper_state": true
329
        },
330
        ...
331
      ]
332

333
    @return: a dictionary with 'name' and 'uri' keys for each of them.
334

335
    """
336
    client = baserlib.GetClient()
337

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

    
348
  def POST(self):
349
    """Create an instance.
350

351
    @return: a job id
352

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

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

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

    
378
    op = opcodes.OpCreateInstance(
379
      mode=constants.INSTANCE_CREATE,
380
      instance_name=fn('name'),
381
      disks=disks,
382
      disk_template=fn('disk_template'),
383
      os_type=fn('os'),
384
      pnode=fn('pnode', None),
385
      snode=fn('snode', None),
386
      iallocator=fn('iallocator', None),
387
      nics=nics,
388
      start=fn('start', True),
389
      ip_check=fn('ip_check', True),
390
      wait_for_sync=True,
391
      hypervisor=fn('hypervisor', None),
392
      hvparams=hvparams,
393
      beparams=beparams,
394
      file_storage_dir=fn('file_storage_dir', None),
395
      file_driver=fn('file_driver', 'loop'),
396
      )
397

    
398
    return baserlib.SubmitJob([op])
399

    
400

    
401
class R_2_instances_name(baserlib.R_Generic):
402
  """/2/instances/[instance_name] resources.
403

404
  """
405
  DOC_URI = "/2/instances/[instance_name]"
406

    
407
  def GET(self):
408
    """Send information about an instance.
409

410
    """
411
    client = baserlib.GetClient()
412
    instance_name = self.items[0]
413
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
414
                                   use_locking=self.useLocking())
415

    
416
    return baserlib.MapFields(I_FIELDS, result[0])
417

    
418
  def DELETE(self):
419
    """Delete an instance.
420

421
    """
422
    op = opcodes.OpRemoveInstance(instance_name=self.items[0],
423
                                  ignore_failures=False)
424
    return baserlib.SubmitJob([op])
425

    
426

    
427
class R_2_instances_name_reboot(baserlib.R_Generic):
428
  """/2/instances/[instance_name]/reboot resource.
429

430
  Implements an instance reboot.
431

432
  """
433

    
434
  DOC_URI = "/2/instances/[instance_name]/reboot"
435

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

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

442
    """
443
    instance_name = self.items[0]
444
    reboot_type = self.queryargs.get('type',
445
                                     [constants.INSTANCE_REBOOT_HARD])[0]
446
    ignore_secondaries = bool(self._checkIntVariable('ignore_secondaries'))
447
    op = opcodes.OpRebootInstance(instance_name=instance_name,
448
                                  reboot_type=reboot_type,
449
                                  ignore_secondaries=ignore_secondaries)
450

    
451
    return baserlib.SubmitJob([op])
452

    
453

    
454
class R_2_instances_name_startup(baserlib.R_Generic):
455
  """/2/instances/[instance_name]/startup resource.
456

457
  Implements an instance startup.
458

459
  """
460

    
461
  DOC_URI = "/2/instances/[instance_name]/startup"
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._checkIntVariable('force'))
472
    op = opcodes.OpStartupInstance(instance_name=instance_name,
473
                                   force=force_startup)
474

    
475
    return baserlib.SubmitJob([op])
476

    
477

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

481
  Implements an instance shutdown.
482

483
  """
484

    
485
  DOC_URI = "/2/instances/[instance_name]/shutdown"
486

    
487
  def PUT(self):
488
    """Shutdown an instance.
489

490
    """
491
    instance_name = self.items[0]
492
    op = opcodes.OpShutdownInstance(instance_name=instance_name)
493

    
494
    return baserlib.SubmitJob([op])
495

    
496

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

500
  Implements an instance reinstall.
501

502
  """
503

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

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

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

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

    
526

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

530
  Manages tags. Inheriting this class you suppose to define DOC_URI and
531
  TAG_LEVEL for it.
532

533
  """
534
  TAG_LEVEL = None
535

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

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

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

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

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

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

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

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

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

563
    """
564
    if 'tag' not in self.queryargs:
565
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
566
                                " the 'tag' parameter")
567
    return baserlib._Tags_PUT(self.TAG_LEVEL,
568
                              self.queryargs['tag'], name=self.name)
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

    
586

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

590
  Manages per-instance tags.
591

592
  """
593
  DOC_URI = "/2/instances/[instance_name]/tags"
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
  DOC_URI = "/2/nodes/[node_name]/tags"
604
  TAG_LEVEL = constants.TAG_NODE
605

    
606

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

610
  Manages cluster tags.
611

612
  """
613
  DOC_URI = "/2/tags"
614
  TAG_LEVEL = constants.TAG_CLUSTER