Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ 18cb43a2

History | View | Annotate | Download (12.8 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", "os", "pnode", "snodes", "admin_state", "disk_template",
34
            "ip", "mac", "bridge", "sda_size", "sdb_size", "beparams",
35
            "oper_state", "status", "tags"]
36

    
37
N_FIELDS = ["name", "dtotal", "dfree",
38
            "mtotal", "mnode", "mfree",
39
            "pinst_cnt", "sinst_cnt", "tags"]
40

    
41

    
42
class R_version(baserlib.R_Generic):
43
  """/version resource.
44

45
  This resource should be used to determine the remote API version and
46
  to adapt clients accordingly.
47

48
  """
49
  DOC_URI = "/version"
50

    
51
  def GET(self):
52
    """Returns the remote API version.
53

54
    """
55
    return constants.RAPI_VERSION
56

    
57

    
58
class R_2_info(baserlib.R_Generic):
59
  """Cluster info.
60

61
  """
62
  DOC_URI = "/2/info"
63

    
64
  def GET(self):
65
    """Returns cluster information.
66

67
    Example::
68

69
      {
70
        "config_version": 3,
71
        "name": "cluster1.example.com",
72
        "software_version": "1.2.4",
73
        "os_api_version": 5,
74
        "export_version": 0,
75
        "master": "node1.example.com",
76
        "architecture": [
77
          "64bit",
78
          "x86_64"
79
        ],
80
        "hypervisor_type": "xen-pvm",
81
        "protocol_version": 12
82
      }
83

84
    """
85
    op = ganeti.opcodes.OpQueryClusterInfo()
86
    return ganeti.cli.SubmitOpCode(op)
87

    
88

    
89
class R_2_os(baserlib.R_Generic):
90
  """/2/os resource.
91

92
  """
93
  DOC_URI = "/2/os"
94

    
95
  def GET(self):
96
    """Return a list of all OSes.
97

98
    Can return error 500 in case of a problem.
99

100
    Example: ["debian-etch"]
101

102
    """
103
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
104
                                     names=[])
105
    diagnose_data = ganeti.cli.SubmitOpCode(op)
106

    
107
    if not isinstance(diagnose_data, list):
108
      raise http.HttpInternalServerError(message="Can't get OS list")
109

    
110
    return [row[0] for row in diagnose_data if row[1]]
111

    
112

    
113
class R_2_jobs(baserlib.R_Generic):
114
  """/2/jobs resource.
115

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

    
119
  def GET(self):
120
    """Returns a dictionary of jobs.
121

122
    @return: a dictionary with jobs id and uri.
123

124
    """
125
    fields = ["id"]
126
    # Convert the list of lists to the list of ids
127
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
128
    return baserlib.BuildUriList(result, "/2/jobs/%s", uri_fields=("id", "uri"))
129

    
130

    
131
class R_2_jobs_id(baserlib.R_Generic):
132
  """/2/jobs/[job_id] resource.
133

134
  """
135
  DOC_URI = "/2/jobs/[job_id]"
136

    
137
  def GET(self):
138
    """Returns a job status.
139

140
    @return: a dictionary with job parameters.
141
        The result includes:
142
            - id: job ID as a number
143
            - status: current job status as a string
144
            - ops: involved OpCodes as a list of dictionaries for each
145
              opcodes in the job
146
            - opstatus: OpCodes status as a list
147
            - opresult: OpCodes results as a list of lists
148

149
    """
150
    fields = ["id", "ops", "status", "opstatus", "opresult"]
151
    job_id = self.items[0]
152
    result = luxi.Client().QueryJobs([job_id, ], fields)[0]
153
    return baserlib.MapFields(fields, result)
154

    
155
  def DELETE(self):
156
    """Cancel not-yet-started job.
157

158
    """
159
    job_id = self.items[0]
160
    result = luxi.Client().CancelJob(job_id)
161
    return result
162

    
163

    
164
class R_2_nodes(baserlib.R_Generic):
165
  """/2/nodes resource.
166

167
  """
168
  DOC_URI = "/2/nodes"
169

    
170
  def GET(self):
171
    """Returns a list of all nodes.
172

173
    Example::
174

175
      [
176
        {
177
          "id": "node1.example.com",
178
          "uri": "\/instances\/node1.example.com"
179
        },
180
        {
181
          "id": "node2.example.com",
182
          "uri": "\/instances\/node2.example.com"
183
        }
184
      ]
185

186
    If the optional 'bulk' argument is provided and set to 'true'
187
    value (i.e '?bulk=1'), the output contains detailed
188
    information about nodes as a list.
189

190
    Example::
191

192
      [
193
        {
194
          "pinst_cnt": 1,
195
          "mfree": 31280,
196
          "mtotal": 32763,
197
          "name": "www.example.com",
198
          "tags": [],
199
          "mnode": 512,
200
          "dtotal": 5246208,
201
          "sinst_cnt": 2,
202
          "dfree": 5171712
203
        },
204
        ...
205
      ]
206

207
    @return: a dictionary with 'name' and 'uri' keys for each of them
208

209
    """
210
    client = luxi.Client()
211
    nodesdata = client.QueryNodes([], ["name"])
212
    nodeslist = [row[0] for row in nodesdata]
213

    
214
    if 'bulk' in self.queryargs:
215
      bulkdata = client.QueryNodes(nodeslist, N_FIELDS)
216
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
217

    
218
    return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
219
                                 uri_fields=("id", "uri"))
220

    
221

    
222
class R_2_nodes_name(baserlib.R_Generic):
223
  """/2/nodes/[node_name] resources.
224

225
  """
226
  DOC_URI = "/nodes/[node_name]"
227

    
228
  def GET(self):
229
    """Send information about a node.
230

231
    """
232
    node_name = self.items[0]
233
    op = ganeti.opcodes.OpQueryNodes(output_fields=N_FIELDS,
234
                                     names=[node_name])
235
    result = ganeti.cli.SubmitOpCode(op)
236

    
237
    return baserlib.MapFields(N_FIELDS, result[0])
238

    
239

    
240
class R_2_instances(baserlib.R_Generic):
241
  """/2/instances resource.
242

243
  """
244
  DOC_URI = "/2/instances"
245

    
246
  def GET(self):
247
    """Returns a list of all available instances.
248

249

250
    Example::
251

252
      [
253
        {
254
          "name": "web.example.com",
255
          "uri": "\/instances\/web.example.com"
256
        },
257
        {
258
          "name": "mail.example.com",
259
          "uri": "\/instances\/mail.example.com"
260
        }
261
      ]
262

263
    If the optional 'bulk' argument is provided and set to 'true'
264
    value (i.e '?bulk=1'), the output contains detailed
265
    information about instances as a list.
266

267
    Example::
268

269
      [
270
        {
271
           "status": "running",
272
           "bridge": "xen-br0",
273
           "name": "web.example.com",
274
           "tags": ["tag1", "tag2"],
275
           "admin_ram": 512,
276
           "sda_size": 20480,
277
           "pnode": "node1.example.com",
278
           "mac": "01:23:45:67:89:01",
279
           "sdb_size": 4096,
280
           "snodes": ["node2.example.com"],
281
           "disk_template": "drbd",
282
           "ip": null,
283
           "admin_state": true,
284
           "os": "debian-etch",
285
           "vcpus": 2,
286
           "oper_state": true
287
        },
288
        ...
289
      ]
290

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

293
    """
294
    client = luxi.Client()
295
    instancesdata = client.QueryInstances([], ["name"])
296
    instanceslist = [row[0] for row in instancesdata]
297

    
298

    
299
    if 'bulk' in self.queryargs:
300
      bulkdata = client.QueryInstances(instanceslist, I_FIELDS)
301
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
302

    
303
    else:
304
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
305
                                   uri_fields=("id", "uri"))
306

    
307
  def POST(self):
308
    """Create an instance.
309

310
    @returns: a job id
311

312
    """
313
    opts = self.req.request_post_data
314

    
315
    beparams = baserlib.MakeParamsDict(opts, constants.BES_PARAMETERS)
316
    hvparams = baserlib.MakeParamsDict(opts, constants.HVS_PARAMETERS)
317

    
318
    op = ganeti.opcodes.OpCreateInstance(
319
        instance_name=opts.get('name'),
320
        disk_size=opts.get('size', 20 * 1024),
321
        swap_size=opts.get('swap', 4 * 1024),
322
        disk_template=opts.get('disk_template', None),
323
        mode=constants.INSTANCE_CREATE,
324
        os_type=opts.get('os'),
325
        pnode=opts.get('pnode'),
326
        snode=opts.get('snode'),
327
        ip=opts.get('ip', 'none'),
328
        bridge=opts.get('bridge', None),
329
        start=opts.get('start', True),
330
        ip_check=opts.get('ip_check', True),
331
        wait_for_sync=opts.get('wait_for_sync', True),
332
        mac=opts.get('mac', 'auto'),
333
        hypervisor=opts.get('hypervisor', None),
334
        hvparams=hvparams,
335
        beparams=beparams,
336
        iallocator=opts.get('iallocator', None),
337
        file_storage_dir=opts.get('file_storage_dir', None),
338
        file_driver=opts.get('file_driver', 'loop'),
339
        )
340

    
341
    job_id = ganeti.cli.SendJob([op])
342
    return job_id
343

    
344

    
345
class R_2_instances_name(baserlib.R_Generic):
346
  """/2/instances/[instance_name] resources.
347

348
  """
349
  DOC_URI = "/2/instances/[instance_name]"
350

    
351
  def GET(self):
352
    """Send information about an instance.
353

354
    """
355
    instance_name = self.items[0]
356
    op = ganeti.opcodes.OpQueryInstances(output_fields=I_FIELDS,
357
                                         names=[instance_name])
358
    result = ganeti.cli.SubmitOpCode(op)
359

    
360
    return baserlib.MapFields(I_FIELDS, result[0])
361

    
362

    
363
class R_2_instances_name_reboot(baserlib.R_Generic):
364
  """/2/instances/[instance_name]/reboot resource.
365

366
  Implements an instance reboot.
367

368
  """
369

    
370
  DOC_URI = "/2/instances/[instance_name]/reboot"
371

    
372
  def POST(self):
373
    """Reboot an instance.
374

375
    The URI takes type=[hard|soft|full] and
376
    ignore_secondaries=[False|True] parameters.
377

378
    """
379
    instance_name = self.items[0]
380
    reboot_type = self.queryargs.get('type',
381
                                     [constants.INSTANCE_REBOOT_HARD])[0]
382
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
383
                                                 [False])[0])
384
    op = ganeti.opcodes.OpRebootInstance(
385
        instance_name=instance_name,
386
        reboot_type=reboot_type,
387
        ignore_secondaries=ignore_secondaries)
388

    
389
    job_id = ganeti.cli.SendJob([op])
390

    
391
    return job_id
392

    
393

    
394
class R_2_instances_name_startup(baserlib.R_Generic):
395
  """/2/instances/[instance_name]/startup resource.
396

397
  Implements an instance startup.
398

399
  """
400

    
401
  DOC_URI = "/2/instances/[instance_name]/startup"
402

    
403
  def PUT(self):
404
    """Startup an instance.
405

406
    The URI takes force=[False|True] parameter to start the instance
407
    if even if secondary disks are failing.
408

409
    """
410
    instance_name = self.items[0]
411
    force_startup = bool(self.queryargs.get('force', [False])[0])
412
    op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
413
                                          force=force_startup)
414

    
415
    job_id = ganeti.cli.SendJob([op])
416

    
417
    return job_id
418

    
419

    
420
class R_2_instances_name_shutdown(baserlib.R_Generic):
421
  """/2/instances/[instance_name]/shutdown resource.
422

423
  Implements an instance shutdown.
424

425
  """
426

    
427
  DOC_URI = "/2/instances/[instance_name]/shutdown"
428

    
429
  def PUT(self):
430
    """Shutdown an instance.
431

432
    """
433
    instance_name = self.items[0]
434
    op = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)
435

    
436
    job_id = ganeti.cli.SendJob([op])
437

    
438
    return job_id
439

    
440

    
441
class _R_Tags(baserlib.R_Generic):
442
  """ Quasiclass for tagging resources
443

444
  Manages tags. Inheriting this class you suppose to define DOC_URI and
445
  TAG_LEVEL for it.
446

447
  """
448

    
449
  def __init__(self, items, queryargs, req):
450
    """A tag resource constructor.
451

452
    We have to override the default to sort out cluster naming case.
453

454
    """
455
    baserlib.R_Generic.__init__(self, items, queryargs, req)
456

    
457
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
458
      self.name = items[0]
459
    else:
460
      self.name = ""
461

    
462
  def GET(self):
463
    """Returns a list of tags.
464

465
    Example: ["tag1", "tag2", "tag3"]
466

467
    """
468
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
469

    
470
  def PUT(self):
471
    """Add a set of tags.
472

473
    The request as a list of strings should be PUT to this URI. And
474
    you'll have back a job id.
475

476
    """
477
    return baserlib._Tags_PUT(self.TAG_LEVEL,
478
                              self.req.request_post_data, name=self.name)
479

    
480
  def DELETE(self):
481
    """Delete a tag.
482

483
    In order to delete a set of tags, the DELETE
484
    request should be addressed to URI like:
485
    /tags?tag=[tag]&tag=[tag]
486

487
    """
488
    if 'tag' not in self.queryargs:
489
      # no we not gonna delete all tags
490
      raise http.HttpNotImplemented()
491
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
492
                                 self.queryargs['tag'],
493
                                 name=self.name)
494

    
495

    
496
class R_2_instances_name_tags(_R_Tags):
497
  """ /2/instances/[instance_name]/tags resource.
498

499
  Manages per-instance tags.
500

501
  """
502
  DOC_URI = "/2/instances/[instance_name]/tags"
503
  TAG_LEVEL = constants.TAG_INSTANCE
504

    
505

    
506
class R_2_nodes_name_tags(_R_Tags):
507
  """ /2/nodes/[node_name]/tags resource.
508

509
  Manages per-node tags.
510

511
  """
512
  DOC_URI = "/2/nodes/[node_name]/tags"
513
  TAG_LEVEL = constants.TAG_NODE
514

    
515

    
516
class R_2_tags(_R_Tags):
517
  """ /2/instances/tags resource.
518

519
  Manages cluster tags.
520

521
  """
522
  DOC_URI = "/2/tags"
523
  TAG_LEVEL = constants.TAG_CLUSTER