Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / rlib2.py @ c8e0a534

History | View | Annotate | Download (11.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
            "network_port",
38
            "disk.sizes", "disk_usage",
39
            "beparams", "hvparams",
40
            "oper_state", "oper_ram", "status",
41
            "tags"]
42

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

    
50

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

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

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

61
    """
62
    return constants.RAPI_VERSION
63

    
64

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

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

72
    """
73
    client = luxi.Client()
74
    return client.QueryClusterInfo()
75

    
76

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

80
  """
81
  def GET(self):
82
    """Return a list of all OSes.
83

84
    Can return error 500 in case of a problem.
85

86
    Example: ["debian-etch"]
87

88
    """
89
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
90
                                     names=[])
91
    diagnose_data = ganeti.cli.SubmitOpCode(op)
92

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

    
96
    return [row[0] for row in diagnose_data if row[1]]
97

    
98

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

102
  """
103
  def GET(self):
104
    """Returns a dictionary of jobs.
105

106
    @return: a dictionary with jobs id and uri.
107

108
    """
109
    fields = ["id"]
110
    # Convert the list of lists to the list of ids
111
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
112
    return baserlib.BuildUriList(result, "/2/jobs/%s",
113
                                 uri_fields=("id", "uri"))
114

    
115

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

119
  """
120
  def GET(self):
121
    """Returns a job status.
122

123
    @return: a dictionary with job parameters.
124
        The result includes:
125
            - id: job ID as a number
126
            - status: current job status as a string
127
            - ops: involved OpCodes as a list of dictionaries for each
128
              opcodes in the job
129
            - opstatus: OpCodes status as a list
130
            - opresult: OpCodes results as a list of lists
131

132
    """
133
    fields = ["id", "ops", "status", "summary",
134
              "opstatus", "opresult", "oplog",
135
              "received_ts", "start_ts", "end_ts",
136
              ]
137
    job_id = self.items[0]
138
    result = luxi.Client().QueryJobs([job_id, ], fields)[0]
139
    if result is None:
140
      raise http.HttpNotFound()
141
    return baserlib.MapFields(fields, result)
142

    
143
  def DELETE(self):
144
    """Cancel not-yet-started job.
145

146
    """
147
    job_id = self.items[0]
148
    result = luxi.Client().CancelJob(job_id)
149
    return result
150

    
151

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

155
  """
156
  def GET(self):
157
    """Returns a list of all nodes.
158

159
    """
160
    client = luxi.Client()
161

    
162
    if self.useBulk():
163
      bulkdata = client.QueryNodes([], N_FIELDS, False)
164
      return baserlib.MapBulkFields(bulkdata, N_FIELDS)
165
    else:
166
      nodesdata = client.QueryNodes([], ["name"], False)
167
      nodeslist = [row[0] for row in nodesdata]
168
      return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
169
                                   uri_fields=("id", "uri"))
170

    
171

    
172
class R_2_nodes_name(baserlib.R_Generic):
173
  """/2/nodes/[node_name] resources.
174

175
  """
176
  def GET(self):
177
    """Send information about a node.
178

179
    """
180
    node_name = self.items[0]
181
    client = luxi.Client()
182
    result = client.QueryNodes(names=[node_name], fields=N_FIELDS,
183
                               use_locking=self.useLocking())
184

    
185
    return baserlib.MapFields(N_FIELDS, result[0])
186

    
187

    
188
class R_2_instances(baserlib.R_Generic):
189
  """/2/instances resource.
190

191
  """
192
  def GET(self):
193
    """Returns a list of all available instances.
194

195
    """
196
    client = luxi.Client()
197

    
198
    use_locking = self.useLocking()
199
    if self.useBulk():
200
      bulkdata = client.QueryInstances([], I_FIELDS, use_locking)
201
      return baserlib.MapBulkFields(bulkdata, I_FIELDS)
202
    else:
203
      instancesdata = client.QueryInstances([], ["name"], use_locking)
204
      instanceslist = [row[0] for row in instancesdata]
205
      return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
206
                                   uri_fields=("id", "uri"))
207

    
208
  def POST(self):
209
    """Create an instance.
210

211
    @return: a job id
212

213
    """
214
    if not isinstance(self.req.request_body, dict):
215
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
216

    
217
    beparams = baserlib.MakeParamsDict(self.req.request_body,
218
                                       constants.BES_PARAMETERS)
219
    hvparams = baserlib.MakeParamsDict(self.req.request_body,
220
                                       constants.HVS_PARAMETERS)
221
    fn = self.getBodyParameter
222

    
223
    # disk processing
224
    disk_data = fn('disks')
225
    if not isinstance(disk_data, list):
226
      raise http.HttpBadRequest("The 'disks' parameter should be a list")
227
    disks = []
228
    for idx, d in enumerate(disk_data):
229
      if not isinstance(d, int):
230
        raise http.HttpBadRequest("Disk %d specification wrong: should"
231
                                  " be an integer")
232
      disks.append({"size": d})
233
    # nic processing (one nic only)
234
    nics = [{"mac": fn("mac", constants.VALUE_AUTO),
235
             "ip": fn("ip", None),
236
             "bridge": fn("bridge", None)}]
237

    
238
    op = ganeti.opcodes.OpCreateInstance(
239
        mode=constants.INSTANCE_CREATE,
240
        instance_name=fn('name'),
241
        disks=disks,
242
        disk_template=fn('disk_template'),
243
        os_type=fn('os'),
244
        pnode=fn('pnode', None),
245
        snode=fn('snode', None),
246
        iallocator=fn('iallocator', None),
247
        nics=nics,
248
        start=fn('start', True),
249
        ip_check=fn('ip_check', True),
250
        wait_for_sync=True,
251
        hypervisor=fn('hypervisor', None),
252
        hvparams=hvparams,
253
        beparams=beparams,
254
        file_storage_dir=fn('file_storage_dir', None),
255
        file_driver=fn('file_driver', 'loop'),
256
        )
257

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

    
261

    
262
class R_2_instances_name(baserlib.R_Generic):
263
  """/2/instances/[instance_name] resources.
264

265
  """
266
  def GET(self):
267
    """Send information about an instance.
268

269
    """
270
    client = luxi.Client()
271
    instance_name = self.items[0]
272
    result = client.QueryInstances(names=[instance_name], fields=I_FIELDS,
273
                                   use_locking=self.useLocking())
274

    
275
    return baserlib.MapFields(I_FIELDS, result[0])
276

    
277
  def DELETE(self):
278
    """Delete an instance.
279

280
    """
281
    op = ganeti.opcodes.OpRemoveInstance(instance_name=self.items[0],
282
                                         ignore_failures=False)
283
    job_id = ganeti.cli.SendJob([op])
284
    return job_id
285

    
286

    
287
class R_2_instances_name_reboot(baserlib.R_Generic):
288
  """/2/instances/[instance_name]/reboot resource.
289

290
  Implements an instance reboot.
291

292
  """
293
  def POST(self):
294
    """Reboot an instance.
295

296
    The URI takes type=[hard|soft|full] and
297
    ignore_secondaries=[False|True] parameters.
298

299
    """
300
    instance_name = self.items[0]
301
    reboot_type = self.queryargs.get('type',
302
                                     [constants.INSTANCE_REBOOT_HARD])[0]
303
    ignore_secondaries = bool(self.queryargs.get('ignore_secondaries',
304
                                                 [False])[0])
305
    op = ganeti.opcodes.OpRebootInstance(
306
        instance_name=instance_name,
307
        reboot_type=reboot_type,
308
        ignore_secondaries=ignore_secondaries)
309

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

    
312
    return job_id
313

    
314

    
315
class R_2_instances_name_startup(baserlib.R_Generic):
316
  """/2/instances/[instance_name]/startup resource.
317

318
  Implements an instance startup.
319

320
  """
321
  def PUT(self):
322
    """Startup an instance.
323

324
    The URI takes force=[False|True] parameter to start the instance
325
    if even if secondary disks are failing.
326

327
    """
328
    instance_name = self.items[0]
329
    force_startup = bool(self.queryargs.get('force', [False])[0])
330
    op = ganeti.opcodes.OpStartupInstance(instance_name=instance_name,
331
                                          force=force_startup)
332

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

    
335
    return job_id
336

    
337

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

341
  Implements an instance shutdown.
342

343
  """
344
  def PUT(self):
345
    """Shutdown an instance.
346

347
    """
348
    instance_name = self.items[0]
349
    op = ganeti.opcodes.OpShutdownInstance(instance_name=instance_name)
350

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

    
353
    return job_id
354

    
355

    
356
class _R_Tags(baserlib.R_Generic):
357
  """ Quasiclass for tagging resources
358

359
  Manages tags. When inheriting this class you must define the
360
  TAG_LEVEL for it.
361

362
  """
363
  TAG_LEVEL = None
364

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

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

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

    
373
    if self.TAG_LEVEL != constants.TAG_CLUSTER:
374
      self.name = items[0]
375
    else:
376
      self.name = ""
377

    
378
  def GET(self):
379
    """Returns a list of tags.
380

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

383
    """
384
    return baserlib._Tags_GET(self.TAG_LEVEL, name=self.name)
385

    
386
  def PUT(self):
387
    """Add a set of tags.
388

389
    The request as a list of strings should be PUT to this URI. And
390
    you'll have back a job id.
391

392
    """
393
    if 'tag' not in self.queryargs:
394
      raise http.HttpBadRequest("Please specify tag(s) to add using the"
395
                                " the 'tag' parameter")
396
    return baserlib._Tags_PUT(self.TAG_LEVEL,
397
                              self.queryargs['tag'], name=self.name)
398

    
399
  def DELETE(self):
400
    """Delete a tag.
401

402
    In order to delete a set of tags, the DELETE
403
    request should be addressed to URI like:
404
    /tags?tag=[tag]&tag=[tag]
405

406
    """
407
    if 'tag' not in self.queryargs:
408
      # no we not gonna delete all tags
409
      raise http.HttpBadRequest("Cannot delete all tags - please specify"
410
                                " tag(s) using the 'tag' parameter")
411
    return baserlib._Tags_DELETE(self.TAG_LEVEL,
412
                                 self.queryargs['tag'],
413
                                 name=self.name)
414

    
415

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

419
  Manages per-instance tags.
420

421
  """
422
  TAG_LEVEL = constants.TAG_INSTANCE
423

    
424

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

428
  Manages per-node tags.
429

430
  """
431
  TAG_LEVEL = constants.TAG_NODE
432

    
433

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

437
  Manages cluster tags.
438

439
  """
440
  TAG_LEVEL = constants.TAG_CLUSTER