Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 74aa2478

History | View | Annotate | Download (14.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
import ganeti.opcodes
27
from ganeti import http
28
from ganeti import luxi
29
from ganeti import constants
30
from ganeti.rapi import baserlib
31

    
32

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

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

    
49

    
50
class R_version(baserlib.R_Generic):
51
  """/version resource.
52

53
  This resource should be used to determine the remote API version and
54
  to adapt clients accordingly.
55

56
  """
57
  DOC_URI = "/version"
58

    
59
  def GET(self):
60
    """Returns the remote API version.
61

62
    """
63
    return constants.RAPI_VERSION
64

    
65

    
66
class R_2_info(baserlib.R_Generic):
67
  """Cluster info.
68

69
  """
70
  DOC_URI = "/2/info"
71

    
72
  def GET(self):
73
    """Returns cluster information.
74

75
    Example::
76

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

106
    """
107
    client = luxi.Client()
108
    return client.QueryClusterInfo()
109

    
110

    
111
class R_2_os(baserlib.R_Generic):
112
  """/2/os resource.
113

114
  """
115
  DOC_URI = "/2/os"
116

    
117
  def GET(self):
118
    """Return a list of all OSes.
119

120
    Can return error 500 in case of a problem.
121

122
    Example: ["debian-etch"]
123

124
    """
125
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
126
                                     names=[])
127
    diagnose_data = ganeti.cli.SubmitOpCode(op)
128

    
129
    if not isinstance(diagnose_data, list):
130
      raise http.HttpInternalServerError(message="Can't get OS list")
131

    
132
    return [row[0] for row in diagnose_data if row[1]]
133

    
134

    
135
class R_2_jobs(baserlib.R_Generic):
136
  """/2/jobs resource.
137

138
  """
139
  DOC_URI = "/2/jobs"
140

    
141
  def GET(self):
142
    """Returns a dictionary of jobs.
143

144
    @return: a dictionary with jobs id and uri.
145

146
    """
147
    fields = ["id"]
148
    # Convert the list of lists to the list of ids
149
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
150
    return baserlib.BuildUriList(result, "/2/jobs/%s",
151
                                 uri_fields=("id", "uri"))
152

    
153

    
154
class R_2_jobs_id(baserlib.R_Generic):
155
  """/2/jobs/[job_id] resource.
156

157
  """
158
  DOC_URI = "/2/jobs/[job_id]"
159

    
160
  def GET(self):
161
    """Returns a job status.
162

163
    @return: a dictionary with job parameters.
164
        The result includes:
165
            - id: job ID as a number
166
            - status: current job status as a string
167
            - ops: involved OpCodes as a list of dictionaries for each
168
              opcodes in the job
169
            - opstatus: OpCodes status as a list
170
            - opresult: OpCodes results as a list of lists
171

172
    """
173
    fields = ["id", "ops", "status", "summary",
174
              "opstatus", "opresult", "oplog",
175
              "received_ts", "start_ts", "end_ts",
176
              ]
177
    job_id = self.items[0]
178
    result = luxi.Client().QueryJobs([job_id, ], fields)[0]
179
    if result is None:
180
      raise http.HttpNotFound()
181
    return baserlib.MapFields(fields, result)
182

    
183
  def DELETE(self):
184
    """Cancel not-yet-started job.
185

186
    """
187
    job_id = self.items[0]
188
    result = luxi.Client().CancelJob(job_id)
189
    return result
190

    
191

    
192
class R_2_nodes(baserlib.R_Generic):
193
  """/2/nodes resource.
194

195
  """
196
  DOC_URI = "/2/nodes"
197

    
198
  def GET(self):
199
    """Returns a list of all nodes.
200

201
    Example::
202

203
      [
204
        {
205
          "id": "node1.example.com",
206
          "uri": "\/instances\/node1.example.com"
207
        },
208
        {
209
          "id": "node2.example.com",
210
          "uri": "\/instances\/node2.example.com"
211
        }
212
      ]
213

214
    If the optional 'bulk' argument is provided and set to 'true'
215
    value (i.e '?bulk=1'), the output contains detailed
216
    information about nodes as a list.
217

218
    Example::
219

220
      [
221
        {
222
          "pinst_cnt": 1,
223
          "mfree": 31280,
224
          "mtotal": 32763,
225
          "name": "www.example.com",
226
          "tags": [],
227
          "mnode": 512,
228
          "dtotal": 5246208,
229
          "sinst_cnt": 2,
230
          "dfree": 5171712,
231
          "offline": false
232
        },
233
        ...
234
      ]
235

236
    @return: a dictionary with 'name' and 'uri' keys for each of them
237

238
    """
239
    client = luxi.Client()
240

    
241
    if self.useBulk():
242
      bulkdata = client.QueryNodes([], N_FIELDS, False)
243
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
244
    else:
245
      nodesdata = client.QueryNodes([], ["name"], False)
246
      nodeslist = [row[0] for row in nodesdata]
247
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
248
                                   uri_fields=("id", "uri"))
249

    
250

    
251
class R_2_nodes_name(baserlib.R_Generic):
252
  """/2/nodes/[node_name] resources.
253

254
  """
255
  DOC_URI = "/nodes/[node_name]"
256

    
257
  def GET(self):
258
    """Send information about a node.
259

260
    """
261
    node_name = self.items[0]
262
    client = luxi.Client()
263
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
264
                               use_locking=self.useLocking())
265

    
266
    return baserlib.MapFields(N_FIELDS, result[0])
267

    
268

    
269
class R_2_instances(baserlib.R_Generic):
270
  """/2/instances resource.
271

272
  """
273
  DOC_URI = "/2/instances"
274

    
275
  def GET(self):
276
    """Returns a list of all available instances.
277

278

279
    Example::
280

281
      [
282
        {
283
          "name": "web.example.com",
284
          "uri": "\/instances\/web.example.com"
285
        },
286
        {
287
          "name": "mail.example.com",
288
          "uri": "\/instances\/mail.example.com"
289
        }
290
      ]
291

292
    If the optional 'bulk' argument is provided and set to 'true'
293
    value (i.e '?bulk=1'), the output contains detailed
294
    information about instances as a list.
295

296
    Example::
297

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

325
    @returns: a dictionary with 'name' and 'uri' keys for each of them.
326

327
    """
328
    client = luxi.Client()
329

    
330
    use_locking = self.useLocking()
331
    if self.useBulk():
332
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
333
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
334
    else:
335
      instancesdata = client.QueryInstances([], ["name"], use_locking)
336
      instanceslist = [row[0] for row in instancesdata]
337
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
338
                                   uri_fields=("id", "uri"))
339

    
340
  def POST(self):
341
    """Create an instance.
342

343
    @returns: a job id
344

345
    """
346
    if not isinstance(self.req.request_body, dict):
347
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
348

    
349
    beparams = baserlib.MakeParamsDict(self.req.request_body,
350
                                       constants.BES_PARAMETERS)
351
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
352
                                       constants.HVS_PARAMETERS)
353
    fn = self.getBodyParameter
354

    
355
    # disk processing
356
    disk_data = fn('disks')
357
    if not isinstance(disk_data, list):
358
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
359
    disks = []
360
    for idx, d in enumerate(disk_data):
361
      if not isinstance(d, int):
362
        raise http.HttpBadRequest("Disk %d specification wrong: should"
363
                                  " be an integer")
364
      disks.append({"size": d})
365
    # nic processing (one nic only)
366
    nics = [{"mac": fn("mac", constants.VALUE_AUTO),
367
             "ip": fn("ip", None),
368
             "bridge": fn("bridge", None)}]
369

    
370
    op = ganeti.opcodes.OpCreateInstance(
371
        mode=constants.INSTANCE_CREATE,
372
        instance_name=fn('name'),
373
        disks=disks,
374
        disk_template=fn('disk_template'),
375
        os_type=fn('os'),
376
        pnode=fn('pnode', None),
377
        snode=fn('snode', None),
378
        iallocator=fn('iallocator', None),
379
        nics=nics,
380
        start=fn('start', True),
381
        ip_check=fn('ip_check', True),
382
        wait_for_sync=True,
383
        hypervisor=fn('hypervisor', None),
384
        hvparams=hvparams,
385
        beparams=beparams,
386
        file_storage_dir=fn('file_storage_dir', None),
387
        file_driver=fn('file_driver', 'loop'),
388
        )
389

    
390
    job_id = ganeti.cli.SendJob([op])
391
    return job_id
392

    
393

    
394
class R_2_instances_name(baserlib.R_Generic):
395
  """/2/instances/[instance_name] resources.
396

397
  """
398
  DOC_URI = "/2/instances/[instance_name]"
399

    
400
  def GET(self):
401
    """Send information about an instance.
402

403
    """
404
    client = luxi.Client()
405
    instance_name = self.items[0]
406
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
407
                                   use_locking=self.useLocking())
408

    
409
    return baserlib.MapFields(I_FIELDS, result[0])
410

    
411
  def DELETE(self):
412
    """Delete an instance.
413

414
    """
415
    op = ganeti.opcodes.OpRemoveInstance(instance_name=self.items[0],
416
                                         ignore_failures=False)
417
    job_id = ganeti.cli.SendJob([op])
418
    return job_id
419

    
420

    
421
class R_2_instances_name_reboot(baserlib.R_Generic):
422
  """/2/instances/[instance_name]/reboot resource.
423

424
  Implements an instance reboot.
425

426
  """
427

    
428
  DOC_URI = "/2/instances/[instance_name]/reboot"
429

    
430
  def POST(self):
431
    """Reboot an instance.
432

433
    The URI takes type=[hard|soft|full] and
434
    ignore_secondaries=[False|True] parameters.
435

436
    """
437
    instance_name = self.items[0]
438
    reboot_type = self.queryargs.get('type',
439
                                     [constants.INSTANCE_REBOOT_HARD])[0]
440
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
441
                                                 [False])[0])
442
    op = ganeti.opcodes.OpRebootInstance(
443
        instance_name=instance_name,
444
        reboot_type=reboot_type,
445
        ignore_secondaries=ignore_secondaries)
446

    
447
    job_id = ganeti.cli.SendJob([op])
448

    
449
    return job_id
450

    
451

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

455
  Implements an instance startup.
456

457
  """
458

    
459
  DOC_URI = "/2/instances/[instance_name]/startup"
460

    
461
  def PUT(self):
462
    """Startup an instance.
463

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

467
    """
468
    instance_name = self.items[0]
469
    force_startup = bool(self.queryargs.get('force', [False])[0])
470
    op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
471
                                          force=force_startup)
472

    
473
    job_id = ganeti.cli.SendJob([op])
474

    
475
    return job_id
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 = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)
493

    
494
    job_id = ganeti.cli.SendJob([op])
495

    
496
    return job_id
497

    
498

    
499
class _R_Tags(baserlib.R_Generic):
500
  """ Quasiclass for tagging resources
501

502
  Manages tags. Inheriting this class you suppose to define DOC_URI and
503
  TAG_LEVEL for it.
504

505
  """
506
  TAG_LEVEL = None
507

    
508
  def __init__(self, items, queryargs, req):
509
    """A tag resource constructor.
510

511
    We have to override the default to sort out cluster naming case.
512

513
    """
514
    baserlib.R_Generic.__init__(self, items, queryargs, req)
515

    
516
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
517
      self.name = items[0]
518
    else:
519
      self.name = ""
520

    
521
  def GET(self):
522
    """Returns a list of tags.
523

524
    Example: ["tag1", "tag2", "tag3"]
525

526
    """
527
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
528

    
529
  def PUT(self):
530
    """Add a set of tags.
531

532
    The request as a list of strings should be PUT to this URI. And
533
    you'll have back a job id.
534

535
    """
536
    if 'tag' not in self.queryargs:
537
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
538
                                " the 'tag' parameter")
539
    return baserlib._Tags_PUT(self.TAG_LEVEL,
540
                              self.queryargs['tag'], name=self.name)
541

    
542
  def DELETE(self):
543
    """Delete a tag.
544

545
    In order to delete a set of tags, the DELETE
546
    request should be addressed to URI like:
547
    /tags?tag=[tag]&tag=[tag]
548

549
    """
550
    if 'tag' not in self.queryargs:
551
      # no we not gonna delete all tags
552
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
553
                                " tag(s) using the 'tag' parameter")
554
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
555
                                 self.queryargs['tag'],
556
                                 name=self.name)
557

    
558

    
559
class R_2_instances_name_tags(_R_Tags):
560
  """ /2/instances/[instance_name]/tags resource.
561

562
  Manages per-instance tags.
563

564
  """
565
  DOC_URI = "/2/instances/[instance_name]/tags"
566
  TAG_LEVEL = constants.TAG_INSTANCE
567

    
568

    
569
class R_2_nodes_name_tags(_R_Tags):
570
  """ /2/nodes/[node_name]/tags resource.
571

572
  Manages per-node tags.
573

574
  """
575
  DOC_URI = "/2/nodes/[node_name]/tags"
576
  TAG_LEVEL = constants.TAG_NODE
577

    
578

    
579
class R_2_tags(_R_Tags):
580
  """ /2/instances/tags resource.
581

582
  Manages cluster tags.
583

584
  """
585
  DOC_URI = "/2/tags"
586
  TAG_LEVEL = constants.TAG_CLUSTER