Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ a5b9d725

History | View | Annotate | Download (13 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",
38
            "beparams", "hvparams",
39
            "oper_state", "oper_ram", "status",
40
            "tags"]
41

    
42
N_FIELDS = ["name", "offline", "master_candidate",
43
            "dtotal", "dfree",
44
            "mtotal", "mnode", "mfree",
45
            "pinst_cnt", "sinst_cnt", "tags"]
46

    
47

    
48
class R_version(baserlib.R_Generic):
49
  """/version resource.
50

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

54
  """
55
  DOC_URI = "/version"
56

    
57
  def GET(self):
58
    """Returns the remote API version.
59

60
    """
61
    return constants.RAPI_VERSION
62

    
63

    
64
class R_2_info(baserlib.R_Generic):
65
  """Cluster info.
66

67
  """
68
  DOC_URI = "/2/info"
69

    
70
  def GET(self):
71
    """Returns cluster information.
72

73
    Example::
74

75
      {
76
        "config_version": 3,
77
        "name": "cluster1.example.com",
78
        "software_version": "1.2.4",
79
        "os_api_version": 5,
80
        "export_version": 0,
81
        "master": "node1.example.com",
82
        "architecture": [
83
          "64bit",
84
          "x86_64"
85
        ],
86
        "hypervisor_type": "xen-pvm",
87
        "protocol_version": 12
88
      }
89

90
    """
91
    client = luxi.Client()
92
    return client.QueryClusterInfo()
93

    
94

    
95
class R_2_os(baserlib.R_Generic):
96
  """/2/os resource.
97

98
  """
99
  DOC_URI = "/2/os"
100

    
101
  def GET(self):
102
    """Return a list of all OSes.
103

104
    Can return error 500 in case of a problem.
105

106
    Example: ["debian-etch"]
107

108
    """
109
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
110
                                     names=[])
111
    diagnose_data = ganeti.cli.SubmitOpCode(op)
112

    
113
    if not isinstance(diagnose_data, list):
114
      raise http.HttpInternalServerError(message="Can't get OS list")
115

    
116
    return [row[0] for row in diagnose_data if row[1]]
117

    
118

    
119
class R_2_jobs(baserlib.R_Generic):
120
  """/2/jobs resource.
121

122
  """
123
  DOC_URI = "/2/jobs"
124

    
125
  def GET(self):
126
    """Returns a dictionary of jobs.
127

128
    @return: a dictionary with jobs id and uri.
129

130
    """
131
    fields = ["id"]
132
    # Convert the list of lists to the list of ids
133
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
134
    return baserlib.BuildUriList(result, "/2/jobs/%s",
135
                                 uri_fields=("id", "uri"))
136

    
137

    
138
class R_2_jobs_id(baserlib.R_Generic):
139
  """/2/jobs/[job_id] resource.
140

141
  """
142
  DOC_URI = "/2/jobs/[job_id]"
143

    
144
  def GET(self):
145
    """Returns a job status.
146

147
    @return: a dictionary with job parameters.
148
        The result includes:
149
            - id: job ID as a number
150
            - status: current job status as a string
151
            - ops: involved OpCodes as a list of dictionaries for each
152
              opcodes in the job
153
            - opstatus: OpCodes status as a list
154
            - opresult: OpCodes results as a list of lists
155

156
    """
157
    fields = ["id", "ops", "status", "opstatus", "opresult"]
158
    job_id = self.items[0]
159
    result = luxi.Client().QueryJobs([job_id, ], fields)[0]
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 = luxi.Client().CancelJob(job_id)
168
    return result
169

    
170

    
171
class R_2_nodes(baserlib.R_Generic):
172
  """/2/nodes resource.
173

174
  """
175
  DOC_URI = "/2/nodes"
176

    
177
  def GET(self):
178
    """Returns a list of all nodes.
179

180
    Example::
181

182
      [
183
        {
184
          "id": "node1.example.com",
185
          "uri": "\/instances\/node1.example.com"
186
        },
187
        {
188
          "id": "node2.example.com",
189
          "uri": "\/instances\/node2.example.com"
190
        }
191
      ]
192

193
    If the optional 'bulk' argument is provided and set to 'true'
194
    value (i.e '?bulk=1'), the output contains detailed
195
    information about nodes as a list.
196

197
    Example::
198

199
      [
200
        {
201
          "pinst_cnt": 1,
202
          "mfree": 31280,
203
          "mtotal": 32763,
204
          "name": "www.example.com",
205
          "tags": [],
206
          "mnode": 512,
207
          "dtotal": 5246208,
208
          "sinst_cnt": 2,
209
          "dfree": 5171712,
210
          "offline": false
211
        },
212
        ...
213
      ]
214

215
    @return: a dictionary with 'name' and 'uri' keys for each of them
216

217
    """
218
    client = luxi.Client()
219

    
220
    if self.useBulk():
221
      bulkdata = client.QueryNodes([], N_FIELDS, False)
222
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
223
    else:
224
      nodesdata = client.QueryNodes([], ["name"], False)
225
      nodeslist = [row[0] for row in nodesdata]
226
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
227
                                   uri_fields=("id", "uri"))
228

    
229

    
230
class R_2_nodes_name(baserlib.R_Generic):
231
  """/2/nodes/[node_name] resources.
232

233
  """
234
  DOC_URI = "/nodes/[node_name]"
235

    
236
  def GET(self):
237
    """Send information about a node.
238

239
    """
240
    node_name = self.items[0]
241
    client = luxi.Client()
242
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
243
                               use_locking=self.useLocking())
244

    
245
    return baserlib.MapFields(N_FIELDS, result[0])
246

    
247

    
248
class R_2_instances(baserlib.R_Generic):
249
  """/2/instances resource.
250

251
  """
252
  DOC_URI = "/2/instances"
253

    
254
  def GET(self):
255
    """Returns a list of all available instances.
256

257

258
    Example::
259

260
      [
261
        {
262
          "name": "web.example.com",
263
          "uri": "\/instances\/web.example.com"
264
        },
265
        {
266
          "name": "mail.example.com",
267
          "uri": "\/instances\/mail.example.com"
268
        }
269
      ]
270

271
    If the optional 'bulk' argument is provided and set to 'true'
272
    value (i.e '?bulk=1'), the output contains detailed
273
    information about instances as a list.
274

275
    Example::
276

277
      [
278
        {
279
           "status": "running",
280
           "bridge": "xen-br0",
281
           "name": "web.example.com",
282
           "tags": ["tag1", "tag2"],
283
           "admin_ram": 512,
284
           "sda_size": 20480,
285
           "pnode": "node1.example.com",
286
           "mac": "01:23:45:67:89:01",
287
           "sdb_size": 4096,
288
           "snodes": ["node2.example.com"],
289
           "disk_template": "drbd",
290
           "ip": null,
291
           "admin_state": true,
292
           "os": "debian-etch",
293
           "vcpus": 2,
294
           "oper_state": true
295
        },
296
        ...
297
      ]
298

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

301
    """
302
    client = luxi.Client()
303

    
304
    use_locking = self.useLocking()
305
    if self.useBulk():
306
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
307
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
308
    else:
309
      instancesdata = client.QueryInstances([], ["name"], use_locking)
310
      instanceslist = [row[0] for row in instancesdata]
311
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
312
                                   uri_fields=("id", "uri"))
313

    
314
  def POST(self):
315
    """Create an instance.
316

317
    @returns: a job id
318

319
    """
320
    opts = self.req.request_post_data
321

    
322
    beparams = baserlib.MakeParamsDict(opts, constants.BES_PARAMETERS)
323
    hvparams = baserlib.MakeParamsDict(opts, constants.HVS_PARAMETERS)
324

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

    
348
    job_id = ganeti.cli.SendJob([op])
349
    return job_id
350

    
351

    
352
class R_2_instances_name(baserlib.R_Generic):
353
  """/2/instances/[instance_name] resources.
354

355
  """
356
  DOC_URI = "/2/instances/[instance_name]"
357

    
358
  def GET(self):
359
    """Send information about an instance.
360

361
    """
362
    client = luxi.Client()
363
    instance_name = self.items[0]
364
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
365
                                   use_locking=self.useLocking())
366

    
367
    return baserlib.MapFields(I_FIELDS, result[0])
368

    
369

    
370
class R_2_instances_name_reboot(baserlib.R_Generic):
371
  """/2/instances/[instance_name]/reboot resource.
372

373
  Implements an instance reboot.
374

375
  """
376

    
377
  DOC_URI = "/2/instances/[instance_name]/reboot"
378

    
379
  def POST(self):
380
    """Reboot an instance.
381

382
    The URI takes type=[hard|soft|full] and
383
    ignore_secondaries=[False|True] parameters.
384

385
    """
386
    instance_name = self.items[0]
387
    reboot_type = self.queryargs.get('type',
388
                                     [constants.INSTANCE_REBOOT_HARD])[0]
389
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
390
                                                 [False])[0])
391
    op = ganeti.opcodes.OpRebootInstance(
392
        instance_name=instance_name,
393
        reboot_type=reboot_type,
394
        ignore_secondaries=ignore_secondaries)
395

    
396
    job_id = ganeti.cli.SendJob([op])
397

    
398
    return job_id
399

    
400

    
401
class R_2_instances_name_startup(baserlib.R_Generic):
402
  """/2/instances/[instance_name]/startup resource.
403

404
  Implements an instance startup.
405

406
  """
407

    
408
  DOC_URI = "/2/instances/[instance_name]/startup"
409

    
410
  def PUT(self):
411
    """Startup an instance.
412

413
    The URI takes force=[False|True] parameter to start the instance
414
    if even if secondary disks are failing.
415

416
    """
417
    instance_name = self.items[0]
418
    force_startup = bool(self.queryargs.get('force', [False])[0])
419
    op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
420
                                          force=force_startup)
421

    
422
    job_id = ganeti.cli.SendJob([op])
423

    
424
    return job_id
425

    
426

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

430
  Implements an instance shutdown.
431

432
  """
433

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

    
436
  def PUT(self):
437
    """Shutdown an instance.
438

439
    """
440
    instance_name = self.items[0]
441
    op = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)
442

    
443
    job_id = ganeti.cli.SendJob([op])
444

    
445
    return job_id
446

    
447

    
448
class _R_Tags(baserlib.R_Generic):
449
  """ Quasiclass for tagging resources
450

451
  Manages tags. Inheriting this class you suppose to define DOC_URI and
452
  TAG_LEVEL for it.
453

454
  """
455

    
456
  def __init__(self, items, queryargs, req):
457
    """A tag resource constructor.
458

459
    We have to override the default to sort out cluster naming case.
460

461
    """
462
    baserlib.R_Generic.__init__(self, items, queryargs, req)
463

    
464
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
465
      self.name = items[0]
466
    else:
467
      self.name = ""
468

    
469
  def GET(self):
470
    """Returns a list of tags.
471

472
    Example: ["tag1", "tag2", "tag3"]
473

474
    """
475
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
476

    
477
  def PUT(self):
478
    """Add a set of tags.
479

480
    The request as a list of strings should be PUT to this URI. And
481
    you'll have back a job id.
482

483
    """
484
    return baserlib._Tags_PUT(self.TAG_LEVEL,
485
                              self.req.request_post_data, name=self.name)
486

    
487
  def DELETE(self):
488
    """Delete a tag.
489

490
    In order to delete a set of tags, the DELETE
491
    request should be addressed to URI like:
492
    /tags?tag=[tag]&tag=[tag]
493

494
    """
495
    if 'tag' not in self.queryargs:
496
      # no we not gonna delete all tags
497
      raise http.HttpNotImplemented()
498
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
499
                                 self.queryargs['tag'],
500
                                 name=self.name)
501

    
502

    
503
class R_2_instances_name_tags(_R_Tags):
504
  """ /2/instances/[instance_name]/tags resource.
505

506
  Manages per-instance tags.
507

508
  """
509
  DOC_URI = "/2/instances/[instance_name]/tags"
510
  TAG_LEVEL = constants.TAG_INSTANCE
511

    
512

    
513
class R_2_nodes_name_tags(_R_Tags):
514
  """ /2/nodes/[node_name]/tags resource.
515

516
  Manages per-node tags.
517

518
  """
519
  DOC_URI = "/2/nodes/[node_name]/tags"
520
  TAG_LEVEL = constants.TAG_NODE
521

    
522

    
523
class R_2_tags(_R_Tags):
524
  """ /2/instances/tags resource.
525

526
  Manages cluster tags.
527

528
  """
529
  DOC_URI = "/2/tags"
530
  TAG_LEVEL = constants.TAG_CLUSTER