Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 59b4eeef

History | View | Annotate | Download (14.7 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
            ]
50

    
51

    
52
class R_version(baserlib.R_Generic):
53
  """/version resource.
54

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

58
  """
59
  DOC_URI = "/version"
60

    
61
  def GET(self):
62
    """Returns the remote API version.
63

64
    """
65
    return constants.RAPI_VERSION
66

    
67

    
68
class R_2_info(baserlib.R_Generic):
69
  """Cluster info.
70

71
  """
72
  DOC_URI = "/2/info"
73

    
74
  def GET(self):
75
    """Returns cluster information.
76

77
    Example::
78

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

108
    """
109
    client = baserlib.GetClient()
110
    return client.QueryClusterInfo()
111

    
112

    
113
class R_2_os(baserlib.R_Generic):
114
  """/2/os resource.
115

116
  """
117
  DOC_URI = "/2/os"
118

    
119
  def GET(self):
120
    """Return a list of all OSes.
121

122
    Can return error 500 in case of a problem.
123

124
    Example: ["debian-etch"]
125

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

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

    
137
    return [row[0] for row in diagnose_data if row[1]]
138

    
139

    
140
class R_2_jobs(baserlib.R_Generic):
141
  """/2/jobs resource.
142

143
  """
144
  DOC_URI = "/2/jobs"
145

    
146
  def GET(self):
147
    """Returns a dictionary of jobs.
148

149
    @return: a dictionary with jobs id and uri.
150

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

    
159

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

163
  """
164
  DOC_URI = "/2/jobs/[job_id]"
165

    
166
  def GET(self):
167
    """Returns a job status.
168

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

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

    
189
  def DELETE(self):
190
    """Cancel not-yet-started job.
191

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

    
197

    
198
class R_2_nodes(baserlib.R_Generic):
199
  """/2/nodes resource.
200

201
  """
202
  DOC_URI = "/2/nodes"
203

    
204
  def GET(self):
205
    """Returns a list of all nodes.
206

207
    Example::
208

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

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

224
    Example::
225

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

242
    @return: a dictionary with 'name' and 'uri' keys for each of them
243

244
    """
245
    client = baserlib.GetClient()
246

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

    
256

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

260
  """
261
  DOC_URI = "/nodes/[node_name]"
262

    
263
  def GET(self):
264
    """Send information about a node.
265

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

    
272
    return baserlib.MapFields(N_FIELDS, result[0])
273

    
274

    
275
class R_2_instances(baserlib.R_Generic):
276
  """/2/instances resource.
277

278
  """
279
  DOC_URI = "/2/instances"
280

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

284

285
    Example::
286

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

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

302
    Example::
303

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

331
    @return: a dictionary with 'name' and 'uri' keys for each of them.
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
             "ip": fn("ip", None),
374
             "bridge": fn("bridge", None)}]
375

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

    
396
    return baserlib.SubmitJob([op])
397

    
398

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

402
  """
403
  DOC_URI = "/2/instances/[instance_name]"
404

    
405
  def GET(self):
406
    """Send information about an instance.
407

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

    
414
    return baserlib.MapFields(I_FIELDS, result[0])
415

    
416
  def DELETE(self):
417
    """Delete an instance.
418

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

    
424

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

428
  Implements an instance reboot.
429

430
  """
431

    
432
  DOC_URI = "/2/instances/[instance_name]/reboot"
433

    
434
  def POST(self):
435
    """Reboot an instance.
436

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

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

    
450
    return baserlib.SubmitJob([op])
451

    
452

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

456
  Implements an instance startup.
457

458
  """
459

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

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

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

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

    
474
    return baserlib.SubmitJob([op])
475

    
476

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

480
  Implements an instance shutdown.
481

482
  """
483

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

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

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

    
493
    return baserlib.SubmitJob([op])
494

    
495

    
496
class _R_Tags(baserlib.R_Generic):
497
  """ Quasiclass for tagging resources
498

499
  Manages tags. Inheriting this class you suppose to define DOC_URI and
500
  TAG_LEVEL for it.
501

502
  """
503
  TAG_LEVEL = None
504

    
505
  def __init__(self, items, queryargs, req):
506
    """A tag resource constructor.
507

508
    We have to override the default to sort out cluster naming case.
509

510
    """
511
    baserlib.R_Generic.__init__(self, items, queryargs, req)
512

    
513
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
514
      self.name = items[0]
515
    else:
516
      self.name = ""
517

    
518
  def GET(self):
519
    """Returns a list of tags.
520

521
    Example: ["tag1", "tag2", "tag3"]
522

523
    """
524
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
525

    
526
  def PUT(self):
527
    """Add a set of tags.
528

529
    The request as a list of strings should be PUT to this URI. And
530
    you'll have back a job id.
531

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

    
539
  def DELETE(self):
540
    """Delete a tag.
541

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

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

    
555

    
556
class R_2_instances_name_tags(_R_Tags):
557
  """ /2/instances/[instance_name]/tags resource.
558

559
  Manages per-instance tags.
560

561
  """
562
  DOC_URI = "/2/instances/[instance_name]/tags"
563
  TAG_LEVEL = constants.TAG_INSTANCE
564

    
565

    
566
class R_2_nodes_name_tags(_R_Tags):
567
  """ /2/nodes/[node_name]/tags resource.
568

569
  Manages per-node tags.
570

571
  """
572
  DOC_URI = "/2/nodes/[node_name]/tags"
573
  TAG_LEVEL = constants.TAG_NODE
574

    
575

    
576
class R_2_tags(_R_Tags):
577
  """ /2/instances/tags resource.
578

579
  Manages cluster tags.
580

581
  """
582
  DOC_URI = "/2/tags"
583
  TAG_LEVEL = constants.TAG_CLUSTER