rlib2.py 13.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#
#

# Copyright (C) 2006, 2007, 2008 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Remote API version 2 baserlib.library.

"""

import ganeti.opcodes
27
from ganeti import http
28
from ganeti import luxi
29
from ganeti import constants
Iustin Pop's avatar
Iustin Pop committed
30
from ganeti.rapi import baserlib
31

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
32

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

N_FIELDS = ["name", "offline", "master_candidate",
            "dtotal", "dfree",
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
44
            "mtotal", "mnode", "mfree",
45
46
47
            "pinst_cnt", "sinst_cnt", "tags",
            "ctotal", "cnodes", "csockets",
            ]
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92


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

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

  """
  DOC_URI = "/version"

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

    """
    return constants.RAPI_VERSION


class R_2_info(baserlib.R_Generic):
  """Cluster info.

  """
  DOC_URI = "/2/info"

  def GET(self):
    """Returns cluster information.

    Example::

      {
        "config_version": 3,
        "name": "cluster1.example.com",
        "software_version": "1.2.4",
        "os_api_version": 5,
        "export_version": 0,
        "master": "node1.example.com",
        "architecture": [
          "64bit",
          "x86_64"
        ],
        "hypervisor_type": "xen-pvm",
        "protocol_version": 12
      }

    """
93
94
    client = luxi.Client()
    return client.QueryClusterInfo()
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118


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

  """
  DOC_URI = "/2/os"

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

    Can return error 500 in case of a problem.

    Example: ["debian-etch"]

    """
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
                                     names=[])
    diagnose_data = ganeti.cli.SubmitOpCode(op)

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

    return [row[0] for row in diagnose_data if row[1]]
119

120
121
122
123
124
125
126
127
128
129

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

  """
  DOC_URI = "/2/jobs"

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

Iustin Pop's avatar
Iustin Pop committed
130
    @return: a dictionary with jobs id and uri.
Iustin Pop's avatar
Iustin Pop committed
131

132
133
134
135
    """
    fields = ["id"]
    # Convert the list of lists to the list of ids
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
136
137
    return baserlib.BuildUriList(result, "/2/jobs/%s",
                                 uri_fields=("id", "uri"))
138
139
140
141
142
143
144
145
146
147
148


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

  """
  DOC_URI = "/2/jobs/[job_id]"

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

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

158
    """
Iustin Pop's avatar
Iustin Pop committed
159
160
161
162
    fields = ["id", "ops", "status", "summary",
              "opstatus", "opresult", "oplog",
              "received_ts", "start_ts", "end_ts",
              ]
163
    job_id = self.items[0]
Iustin Pop's avatar
Iustin Pop committed
164
    result = luxi.Client().QueryJobs([job_id, ], fields)[0]
Iustin Pop's avatar
Iustin Pop committed
165
166
    if result is None:
      raise http.HttpNotFound()
167
168
    return baserlib.MapFields(fields, result)

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
169
170
171
172
173
174
175
176
  def DELETE(self):
    """Cancel not-yet-started job.

    """
    job_id = self.items[0]
    result = luxi.Client().CancelJob(job_id)
    return result

177
178
179
180
181
182

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

  """
  DOC_URI = "/2/nodes"
Iustin Pop's avatar
Iustin Pop committed
183

184
185
  def GET(self):
    """Returns a list of all nodes.
Iustin Pop's avatar
Iustin Pop committed
186

Iustin Pop's avatar
Iustin Pop committed
187
    Example::
188

Iustin Pop's avatar
Iustin Pop committed
189
      [
190
191
192
193
194
195
196
        {
          "id": "node1.example.com",
          "uri": "\/instances\/node1.example.com"
        },
        {
          "id": "node2.example.com",
          "uri": "\/instances\/node2.example.com"
Iustin Pop's avatar
Iustin Pop committed
197
198
        }
      ]
199

Iustin Pop's avatar
Iustin Pop committed
200
    If the optional 'bulk' argument is provided and set to 'true'
201
202
203
    value (i.e '?bulk=1'), the output contains detailed
    information about nodes as a list.

Iustin Pop's avatar
Iustin Pop committed
204
205
206
    Example::

      [
207
208
209
210
211
212
213
214
215
        {
          "pinst_cnt": 1,
          "mfree": 31280,
          "mtotal": 32763,
          "name": "www.example.com",
          "tags": [],
          "mnode": 512,
          "dtotal": 5246208,
          "sinst_cnt": 2,
216
217
          "dfree": 5171712,
          "offline": false
218
219
        },
        ...
Iustin Pop's avatar
Iustin Pop committed
220
221
222
      ]

    @return: a dictionary with 'name' and 'uri' keys for each of them
223
224

    """
225
    client = luxi.Client()
Iustin Pop's avatar
Iustin Pop committed
226

227
    if self.useBulk():
228
      bulkdata = client.QueryNodes([], N_FIELDS, False)
229
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
230
231
232
233
234
    else:
      nodesdata = client.QueryNodes([], ["name"], False)
      nodeslist = [row[0] for row in nodesdata]
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
                                   uri_fields=("id", "uri"))
235
236


Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
237
238
class R_2_nodes_name(baserlib.R_Generic):
  """/2/nodes/[node_name] resources.
239
240

  """
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
241
242
243
244
245
246
247
  DOC_URI = "/nodes/[node_name]"

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

    """
    node_name = self.items[0]
248
249
    client = luxi.Client()
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
250
                               use_locking=self.useLocking())
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
251
252

    return baserlib.MapFields(N_FIELDS, result[0])
253
254


255
256
257
258
259
260
261
262
263
264
class R_2_instances(baserlib.R_Generic):
  """/2/instances resource.

  """
  DOC_URI = "/2/instances"

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


Iustin Pop's avatar
Iustin Pop committed
265
266
267
    Example::

      [
268
269
270
271
272
273
274
        {
          "name": "web.example.com",
          "uri": "\/instances\/web.example.com"
        },
        {
          "name": "mail.example.com",
          "uri": "\/instances\/mail.example.com"
Iustin Pop's avatar
Iustin Pop committed
275
276
        }
      ]
277
278
279
280
281

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

Iustin Pop's avatar
Iustin Pop committed
282
283
284
    Example::

      [
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
        {
           "status": "running",
           "bridge": "xen-br0",
           "name": "web.example.com",
           "tags": ["tag1", "tag2"],
           "admin_ram": 512,
           "sda_size": 20480,
           "pnode": "node1.example.com",
           "mac": "01:23:45:67:89:01",
           "sdb_size": 4096,
           "snodes": ["node2.example.com"],
           "disk_template": "drbd",
           "ip": null,
           "admin_state": true,
           "os": "debian-etch",
           "vcpus": 2,
           "oper_state": true
        },
        ...
Iustin Pop's avatar
Iustin Pop committed
304
305
306
      ]

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

    """
309
    client = luxi.Client()
310

311
312
313
    use_locking = self.useLocking()
    if self.useBulk():
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
314
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
315
    else:
316
      instancesdata = client.QueryInstances([], ["name"], use_locking)
317
      instanceslist = [row[0] for row in instancesdata]
318
319
320
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
                                   uri_fields=("id", "uri"))

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
321
  def POST(self):
322
323
    """Create an instance.

Iustin Pop's avatar
Iustin Pop committed
324
    @returns: a job id
325
326

    """
327
    opts = self.req.request_post_data
328

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
329
330
    beparams = baserlib.MakeParamsDict(opts, constants.BES_PARAMETERS)
    hvparams = baserlib.MakeParamsDict(opts, constants.HVS_PARAMETERS)
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347

    op = ganeti.opcodes.OpCreateInstance(
        instance_name=opts.get('name'),
        disk_size=opts.get('size', 20 * 1024),
        swap_size=opts.get('swap', 4 * 1024),
        disk_template=opts.get('disk_template', None),
        mode=constants.INSTANCE_CREATE,
        os_type=opts.get('os'),
        pnode=opts.get('pnode'),
        snode=opts.get('snode'),
        ip=opts.get('ip', 'none'),
        bridge=opts.get('bridge', None),
        start=opts.get('start', True),
        ip_check=opts.get('ip_check', True),
        wait_for_sync=opts.get('wait_for_sync', True),
        mac=opts.get('mac', 'auto'),
        hypervisor=opts.get('hypervisor', None),
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
348
        hvparams=hvparams,
349
350
351
352
353
354
355
356
357
        beparams=beparams,
        iallocator=opts.get('iallocator', None),
        file_storage_dir=opts.get('file_storage_dir', None),
        file_driver=opts.get('file_driver', 'loop'),
        )

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

358

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
359
360
class R_2_instances_name(baserlib.R_Generic):
  """/2/instances/[instance_name] resources.
361
362

  """
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
363
364
365
366
367
368
  DOC_URI = "/2/instances/[instance_name]"

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

    """
369
    client = luxi.Client()
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
370
    instance_name = self.items[0]
371
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
372
                                   use_locking=self.useLocking())
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
373
374

    return baserlib.MapFields(I_FIELDS, result[0])
375
376


377
378
379
380
381
382
383
384
385
class R_2_instances_name_reboot(baserlib.R_Generic):
  """/2/instances/[instance_name]/reboot resource.

  Implements an instance reboot.

  """

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
386
  def POST(self):
387
388
    """Reboot an instance.

389
390
391
    The URI takes type=[hard|soft|full] and
    ignore_secondaries=[False|True] parameters.

392
393
    """
    instance_name = self.items[0]
394
395
396
397
    reboot_type = self.queryargs.get('type',
                                     [constants.INSTANCE_REBOOT_HARD])[0]
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
                                                 [False])[0])
398
399
400
401
402
403
404
405
406
407
    op = ganeti.opcodes.OpRebootInstance(
        instance_name=instance_name,
        reboot_type=reboot_type,
        ignore_secondaries=ignore_secondaries)

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

    return job_id


408
409
410
411
412
413
414
415
416
class R_2_instances_name_startup(baserlib.R_Generic):
  """/2/instances/[instance_name]/startup resource.

  Implements an instance startup.

  """

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
417
  def PUT(self):
418
419
    """Startup an instance.

Iustin Pop's avatar
Iustin Pop committed
420
421
    The URI takes force=[False|True] parameter to start the instance
    if even if secondary disks are failing.
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442

    """
    instance_name = self.items[0]
    force_startup = bool(self.queryargs.get('force', [False])[0])
    op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
                                          force=force_startup)

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

    return job_id


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

  Implements an instance shutdown.

  """

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

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
443
  def PUT(self):
444
445
446
447
448
449
450
451
452
453
454
    """Shutdown an instance.

    """
    instance_name = self.items[0]
    op = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)

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

    return job_id


Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
455
456
class _R_Tags(baserlib.R_Generic):
  """ Quasiclass for tagging resources
457

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
458
459
  Manages tags. Inheriting this class you suppose to define DOC_URI and
  TAG_LEVEL for it.
460
461

  """
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
462
463
464
465
466
467
468
469
470
471
472
473
474

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

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

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

    if self.TAG_LEVEL != constants.TAG_CLUSTER:
      self.name = items[0]
    else:
      self.name = ""
475
476

  def GET(self):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
477
    """Returns a list of tags.
478
479
480
481

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

    """
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
482
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
483

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
484
  def PUT(self):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
485
    """Add a set of tags.
486

Iustin Pop's avatar
Iustin Pop committed
487
488
    The request as a list of strings should be PUT to this URI. And
    you'll have back a job id.
489
490

    """
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
491
492
    return baserlib._Tags_PUT(self.TAG_LEVEL,
                              self.req.request_post_data, name=self.name)
493
494
495
496

  def DELETE(self):
    """Delete a tag.

Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
497
    In order to delete a set of tags, the DELETE
Iustin Pop's avatar
Iustin Pop committed
498
    request should be addressed to URI like:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
499
    /tags?tag=[tag]&tag=[tag]
500
501
502

    """
    if 'tag' not in self.queryargs:
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
503
      # no we not gonna delete all tags
504
      raise http.HttpNotImplemented()
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
505
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
506
                                 self.queryargs['tag'],
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
                                 name=self.name)


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

  Manages per-instance tags.

  """
  DOC_URI = "/2/instances/[instance_name]/tags"
  TAG_LEVEL = constants.TAG_INSTANCE


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

  Manages per-node tags.

  """
  DOC_URI = "/2/nodes/[node_name]/tags"
  TAG_LEVEL = constants.TAG_NODE


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

  Manages cluster tags.

  """
  DOC_URI = "/2/tags"
  TAG_LEVEL = constants.TAG_CLUSTER