Revision 10b207d4

b/Makefile.am
94 94
	lib/rapi/__init__.py \
95 95
	lib/rapi/RESTHTTPServer.py \
96 96
	lib/rapi/httperror.py \
97
	lib/rapi/resources.py
97
	lib/rapi/baserlib.py \
98
	lib/rapi/connector.py \
99
	lib/rapi/rlib1.py \
100
	lib/rapi/rlib2.py
98 101

  
99 102

  
100 103
docsgml = \
......
226 229

  
227 230
doc/rapi.pdf doc/rapi.html: doc/rapi-resources.sgml
228 231

  
229
doc/rapi-resources.sgml: $(BUILD_RAPI_RESOURCE_DOC) lib/rapi/resources.py
232
doc/rapi-resources.sgml: $(BUILD_RAPI_RESOURCE_DOC) lib/rapi/connector.py
230 233
	PYTHONPATH=.:$(top_builddir) $(BUILD_RAPI_RESOURCE_DOC) > $@ || rm -f $@
231 234

  
232 235
man/%.7: man/%.in man/footer.sgml $(DOCBOOK_WRAPPER)
b/doc/build-rapi-resources-doc
26 26
import cgi
27 27
import inspect
28 28

  
29
from ganeti.rapi import resources
29
from ganeti.rapi import rlib1
30
from ganeti.rapi import rlib2
31
from ganeti.rapi import connector 
30 32

  
31 33

  
32 34
CHECKED_COMMANDS = ["GET", "POST", "PUT", "DELETE"]
......
34 36

  
35 37
def main():
36 38
  # Get list of all resources
37
  all = list(resources._CONNECTOR.itervalues())
39
  all = list(connector.CONNECTOR.itervalues())
38 40

  
39
  # Sort resources by URI
41
  # Sort rlib1 by URI
40 42
  all.sort(cmp=lambda a, b: cmp(a.DOC_URI, b.DOC_URI))
41 43

  
42 44
  print "<!-- Automatically generated, do not edit -->"
b/lib/rapi/RESTHTTPServer.py
20 20

  
21 21
"""
22 22

  
23
import socket
24 23
import BaseHTTPServer
25 24
import OpenSSL
25
import re
26
import socket
26 27
import time
27 28

  
28 29
from ganeti import constants
......
30 31
from ganeti import logger
31 32
from ganeti import rpc
32 33
from ganeti import serializer
33
from ganeti.rapi import resources
34

  
35
from ganeti.rapi import connector
34 36
from ganeti.rapi import httperror
35 37

  
36 38

  
......
158 160
    self.connection = self.request
159 161
    self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
160 162
    self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
161
    self._resmap = resources.Mapper()
163
    self._resmap = connector.Mapper()
162 164

  
163 165
  def handle_one_request(self):
164 166
    """Handle a single REST request.
b/lib/rapi/connector.py
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
"""Remote API connection map.
22

  
23
"""
24

  
25
import cgi
26
import re
27

  
28
from ganeti import constants 
29

  
30
from ganeti.rapi import baserlib 
31
from ganeti.rapi import httperror 
32
from ganeti.rapi import rlib1
33
from ganeti.rapi import rlib2
34

  
35
# the connection map created at the end of this file
36
CONNECTOR = {}
37

  
38

  
39
class Mapper:
40
  """Map resource to method.
41

  
42
  """
43
  def __init__(self, connector=CONNECTOR):
44
    """Resource mapper constructor.
45

  
46
    Args:
47
      con: a dictionary, mapping method name with URL path regexp
48

  
49
    """
50
    self._connector = connector
51

  
52
  def getController(self, uri):
53
    """Find method for a given URI.
54

  
55
    Args:
56
      uri: string with URI
57

  
58
    Returns:
59
      None if no method is found or a tuple containing the following fields:
60
        methd: name of method mapped to URI
61
        items: a list of variable intems in the path
62
        args: a dictionary with additional parameters from URL
63

  
64
    """
65
    if '?' in uri:
66
      (path, query) = uri.split('?', 1)
67
      args = cgi.parse_qs(query)
68
    else:
69
      path = uri
70
      query = None
71
      args = {}
72

  
73
    result = None
74

  
75
    for key, handler in self._connector.iteritems():
76
      # Regex objects
77
      if hasattr(key, "match"):
78
        m = key.match(path)
79
        if m:
80
          result = (handler, list(m.groups()), args)
81
          break
82

  
83
      # String objects
84
      elif key == path:
85
        result = (handler, [], args)
86
        break
87

  
88
    if result is not None:
89
      return result
90
    else:
91
      raise httperror.HTTPNotFound()
92

  
93

  
94
class R_root(baserlib.R_Generic):
95
  """/ resource.
96

  
97
  """
98
  DOC_URI = "/"
99

  
100
  def GET(self):
101
    """Show the list of mapped resources.
102
    
103
    Returns:
104
      A dictionary with 'name' and 'uri' keys for each of them.
105

  
106
    """
107
    root_pattern = re.compile('^R_([a-zA-Z0-9]+)$')
108

  
109
    rootlist = []
110
    for handler in CONNECTOR.values():
111
      m = root_pattern.match(handler.__name__)
112
      if m:
113
        name = m.group(1)
114
        if name != 'root':
115
          rootlist.append(name)
116

  
117
    return baserlib.BuildUriList(rootlist, "/%s")
118

  
119

  
120
CONNECTOR.update({
121
  "/": R_root,
122

  
123
  "/version": rlib1.R_version,
124

  
125
  "/tags": rlib1.R_tags,
126
  "/info": rlib1.R_info,
127

  
128
  "/nodes": rlib1.R_nodes,
129
  re.compile(r'^/nodes/([\w\._-]+)$'): rlib1.R_nodes_name,
130
  re.compile(r'^/nodes/([\w\._-]+)/tags$'): rlib1.R_nodes_name_tags,
131

  
132
  "/instances": rlib1.R_instances,
133
  re.compile(r'^/instances/([\w\._-]+)$'): rlib1.R_instances_name,
134
  re.compile(r'^/instances/([\w\._-]+)/tags$'): rlib1.R_instances_name_tags,
135

  
136
  "/os": rlib1.R_os,
137

  
138
  "/2/jobs": rlib2.R_2_jobs,
139
  "/2/nodes": rlib2.R_2_nodes,
140
  re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): rlib2.R_2_jobs_id,
141
  })
/dev/null
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 resources.
23

  
24
"""
25

  
26
import cgi
27
import re
28

  
29
import ganeti.opcodes
30
import ganeti.errors
31
import ganeti.cli
32

  
33
from ganeti import constants
34
from ganeti import luxi
35
from ganeti import utils
36
from ganeti.rapi import httperror
37

  
38

  
39
# Initialized at the end of this file.
40
_CONNECTOR = {}
41

  
42

  
43
def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
44
  """Builds a URI list as used by index resources.
45

  
46
  Args:
47
  - ids: List of ids as strings
48
  - uri_format: Format to be applied for URI
49
  - uri_fields: Optional parameter for field ids
50

  
51
  """
52
  (field_id, field_uri) = uri_fields
53
  
54
  def _MapId(m_id):
55
    return { field_id: m_id, field_uri: uri_format % m_id, }
56

  
57
  # Make sure the result is sorted, makes it nicer to look at and simplifies
58
  # unittests.
59
  ids.sort()
60

  
61
  return map(_MapId, ids)
62

  
63

  
64
def ExtractField(sequence, index):
65
  """Creates a list containing one column out of a list of lists.
66

  
67
  Args:
68
  - sequence: Sequence of lists
69
  - index: Index of field
70

  
71
  """
72
  return map(lambda item: item[index], sequence)
73

  
74

  
75
def MapFields(names, data):
76
  """Maps two lists into one dictionary.
77

  
78
  Args:
79
  - names: Field names (list of strings)
80
  - data: Field data (list)
81

  
82
  Example:
83
  >>> MapFields(["a", "b"], ["foo", 123])
84
  {'a': 'foo', 'b': 123}
85

  
86
  """
87
  if len(names) != len(data):
88
    raise AttributeError("Names and data must have the same length")
89
  return dict([(names[i], data[i]) for i in range(len(names))])
90

  
91

  
92
def _Tags_GET(kind, name=None):
93
  """Helper function to retrieve tags.
94

  
95
  """
96
  if name is None:
97
    # Do not cause "missing parameter" error, which happens if a parameter
98
    # is None.
99
    name = ""
100
  op = ganeti.opcodes.OpGetTags(kind=kind, name=name)
101
  tags = ganeti.cli.SubmitOpCode(op)
102
  return list(tags)
103

  
104

  
105
class Mapper:
106
  """Map resource to method.
107

  
108
  """
109
  def __init__(self, connector=_CONNECTOR):
110
    """Resource mapper constructor.
111

  
112
    Args:
113
      con: a dictionary, mapping method name with URL path regexp
114

  
115
    """
116
    self._connector = connector
117

  
118
  def getController(self, uri):
119
    """Find method for a given URI.
120

  
121
    Args:
122
      uri: string with URI
123

  
124
    Returns:
125
      None if no method is found or a tuple containing the following fields:
126
        methd: name of method mapped to URI
127
        items: a list of variable intems in the path
128
        args: a dictionary with additional parameters from URL
129

  
130
    """
131
    if '?' in uri:
132
      (path, query) = uri.split('?', 1)
133
      args = cgi.parse_qs(query)
134
    else:
135
      path = uri
136
      query = None
137
      args = {}
138

  
139
    result = None
140

  
141
    for key, handler in self._connector.iteritems():
142
      # Regex objects
143
      if hasattr(key, "match"):
144
        m = key.match(path)
145
        if m:
146
          result = (handler, list(m.groups()), args)
147
          break
148

  
149
      # String objects
150
      elif key == path:
151
        result = (handler, [], args)
152
        break
153

  
154
    if result is not None:
155
      return result
156
    else:
157
      raise httperror.HTTPNotFound()
158

  
159

  
160
class R_Generic(object):
161
  """Generic class for resources.
162

  
163
  """
164
  def __init__(self, request, items, queryargs):
165
    """Generic resource constructor.
166

  
167
    Args:
168
      request: HTTPRequestHandler object
169
      items: a list with variables encoded in the URL
170
      queryargs: a dictionary with additional options from URL
171

  
172
    """
173
    self.request = request
174
    self.items = items
175
    self.queryargs = queryargs
176

  
177

  
178
class R_root(R_Generic):
179
  """/ resource.
180

  
181
  """
182
  DOC_URI = "/"
183

  
184
  def GET(self):
185
    """Show the list of mapped resources.
186
    
187
    Returns:
188
      A dictionary with 'name' and 'uri' keys for each of them.
189

  
190
    """
191
    root_pattern = re.compile('^R_([a-zA-Z0-9]+)$')
192

  
193
    rootlist = []
194
    for handler in _CONNECTOR.values():
195
      m = root_pattern.match(handler.__name__)
196
      if m:
197
        name = m.group(1)
198
        if name != 'root':
199
          rootlist.append(name)
200

  
201
    return BuildUriList(rootlist, "/%s")
202

  
203

  
204
class R_version(R_Generic):
205
  """/version resource.
206

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

  
210
  """
211
  DOC_URI = "/version"
212

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

  
216
    """
217
    return constants.RAPI_VERSION
218

  
219

  
220
class R_tags(R_Generic):
221
  """/tags resource.
222

  
223
  Manages cluster tags.
224

  
225
  """
226
  DOC_URI = "/tags"
227

  
228
  def GET(self):
229
    """Returns a list of all cluster tags.
230

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

  
233
    """
234
    return _Tags_GET(constants.TAG_CLUSTER)
235

  
236

  
237
class R_info(R_Generic):
238
  """Cluster info.
239

  
240
  """
241
  DOC_URI = "/info"
242

  
243
  def GET(self):
244
    """Returns cluster information.
245

  
246
    Example: {
247
      "config_version": 3,
248
      "name": "cluster1.example.com",
249
      "software_version": "1.2.4",
250
      "os_api_version": 5,
251
      "export_version": 0,
252
      "master": "node1.example.com",
253
      "architecture": [
254
        "64bit",
255
        "x86_64"
256
      ],
257
      "hypervisor_type": "xen-3.0",
258
      "protocol_version": 12
259
    }
260

  
261
    """
262
    op = ganeti.opcodes.OpQueryClusterInfo()
263
    return ganeti.cli.SubmitOpCode(op)
264

  
265

  
266
class R_nodes(R_Generic):
267
  """/nodes resource.
268

  
269
  """
270
  DOC_URI = "/nodes"
271

  
272
  def _GetDetails(self, nodeslist):
273
    """Returns detailed instance data for bulk output.
274

  
275
    Args:
276
      instance: A list of nodes names.
277

  
278
    Returns:
279
      A list of nodes properties
280

  
281
    """
282
    fields = ["name","dtotal", "dfree",
283
              "mtotal", "mnode", "mfree",
284
              "pinst_cnt", "sinst_cnt", "tags"]
285

  
286
    op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
287
                                     names=nodeslist)
288
    result = ganeti.cli.SubmitOpCode(op)
289

  
290
    nodes_details = []
291
    for node in result:
292
      mapped = MapFields(fields, node)
293
      nodes_details.append(mapped)
294
    return nodes_details
295
 
296
  def GET(self):
297
    """Returns a list of all nodes.
298
    
299
    Returns:
300
      A dictionary with 'name' and 'uri' keys for each of them.
301

  
302
    Example: [
303
        {
304
          "name": "node1.example.com",
305
          "uri": "\/instances\/node1.example.com"
306
        },
307
        {
308
          "name": "node2.example.com",
309
          "uri": "\/instances\/node2.example.com"
310
        }]
311

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

  
316
    Example: [
317
        {
318
          "pinst_cnt": 1,
319
          "mfree": 31280,
320
          "mtotal": 32763,
321
          "name": "www.example.com",
322
          "tags": [],
323
          "mnode": 512,
324
          "dtotal": 5246208,
325
          "sinst_cnt": 2,
326
          "dfree": 5171712
327
        },
328
        ...
329
    ]
330

  
331
    """
332
    op = ganeti.opcodes.OpQueryNodes(output_fields=["name"], names=[])
333
    nodeslist = ExtractField(ganeti.cli.SubmitOpCode(op), 0)
334
    
335
    if 'bulk' in self.queryargs:
336
      return self._GetDetails(nodeslist)
337

  
338
    return BuildUriList(nodeslist, "/nodes/%s")
339

  
340

  
341
class R_nodes_name(R_Generic):
342
  """/nodes/[node_name] resources.
343

  
344
  """
345
  DOC_URI = "/nodes/[node_name]"
346

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

  
350
    """
351
    node_name = self.items[0]
352
    fields = ["name","dtotal", "dfree",
353
              "mtotal", "mnode", "mfree",
354
              "pinst_cnt", "sinst_cnt", "tags"]
355

  
356
    op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
357
                                     names=[node_name])
358
    result = ganeti.cli.SubmitOpCode(op)
359

  
360
    return MapFields(fields, result[0])
361

  
362

  
363
class R_nodes_name_tags(R_Generic):
364
  """/nodes/[node_name]/tags resource.
365

  
366
  Manages per-node tags.
367

  
368
  """
369
  DOC_URI = "/nodes/[node_name]/tags"
370

  
371
  def GET(self):
372
    """Returns a list of node tags.
373

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

  
376
    """
377
    return _Tags_GET(constants.TAG_NODE, name=self.items[0])
378

  
379

  
380
class R_instances(R_Generic):
381
  """/instances resource.
382

  
383
  """
384
  DOC_URI = "/instances"
385

  
386
  def _GetDetails(self, instanceslist):
387
    """Returns detailed instance data for bulk output.
388

  
389
    Args:
390
      instance: A list of instances names.
391

  
392
    Returns:
393
      A list with instances properties.
394

  
395
    """
396
    fields = ["name", "os", "pnode", "snodes",
397
              "admin_state", "admin_ram",
398
              "disk_template", "ip", "mac", "bridge",
399
              "sda_size", "sdb_size", "vcpus",
400
              "oper_state", "status", "tags"]
401

  
402
    op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
403
                                         names=instanceslist)
404
    result = ganeti.cli.SubmitOpCode(op)
405

  
406
    instances_details = []
407
    for instance in result:
408
      mapped = MapFields(fields, instance)
409
      instances_details.append(mapped)
410
    return instances_details
411
   
412
  def GET(self):
413
    """Returns a list of all available instances.
414
    
415
    Returns:
416
       A dictionary with 'name' and 'uri' keys for each of them.
417

  
418
    Example: [
419
        {
420
          "name": "web.example.com",
421
          "uri": "\/instances\/web.example.com"
422
        },
423
        {
424
          "name": "mail.example.com",
425
          "uri": "\/instances\/mail.example.com"
426
        }]
427

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

  
432
    Example: [
433
        {
434
           "status": "running",
435
           "bridge": "xen-br0",
436
           "name": "web.example.com",
437
           "tags": ["tag1", "tag2"],
438
           "admin_ram": 512,
439
           "sda_size": 20480,
440
           "pnode": "node1.example.com",
441
           "mac": "01:23:45:67:89:01",
442
           "sdb_size": 4096,
443
           "snodes": ["node2.example.com"],
444
           "disk_template": "drbd",
445
           "ip": null,
446
           "admin_state": true,
447
           "os": "debian-etch",
448
           "vcpus": 2,
449
           "oper_state": true
450
        },
451
        ...
452
    ]
453

  
454
    """
455
    op = ganeti.opcodes.OpQueryInstances(output_fields=["name"], names=[])
456
    instanceslist = ExtractField(ganeti.cli.SubmitOpCode(op), 0)
457
    
458
    if 'bulk' in self.queryargs:
459
      return self._GetDetails(instanceslist)  
460

  
461
    else:
462
      return BuildUriList(instanceslist, "/instances/%s")
463

  
464

  
465
class R_instances_name(R_Generic):
466
  """/instances/[instance_name] resources.
467

  
468
  """
469
  DOC_URI = "/instances/[instance_name]"
470

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

  
474
    """
475
    instance_name = self.items[0]
476
    fields = ["name", "os", "pnode", "snodes",
477
              "admin_state", "admin_ram",
478
              "disk_template", "ip", "mac", "bridge",
479
              "sda_size", "sdb_size", "vcpus",
480
              "oper_state", "status", "tags"]
481

  
482
    op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
483
                                         names=[instance_name])
484
    result = ganeti.cli.SubmitOpCode(op)
485

  
486
    return MapFields(fields, result[0])
487

  
488

  
489
class R_instances_name_tags(R_Generic):
490
  """/instances/[instance_name]/tags resource.
491

  
492
  Manages per-instance tags.
493

  
494
  """
495
  DOC_URI = "/instances/[instance_name]/tags"
496

  
497
  def GET(self):
498
    """Returns a list of instance tags.
499

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

  
502
    """
503
    return _Tags_GET(constants.TAG_INSTANCE, name=self.items[0])
504

  
505

  
506
class R_os(R_Generic):
507
  """/os resource.
508

  
509
  """
510
  DOC_URI = "/os"
511

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

  
515
    Can return error 500 in case of a problem.
516

  
517
    Example: ["debian-etch"]
518

  
519
    """
520
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
521
                                     names=[])
522
    diagnose_data = ganeti.cli.SubmitOpCode(op)
523

  
524
    if not isinstance(diagnose_data, list):
525
      raise httperror.HTTPInternalError(message="Can't get OS list")
526

  
527
    return [row[0] for row in diagnose_data if row[1]]
528

  
529

  
530
class R_2_jobs(R_Generic):
531
  """/2/jobs resource.
532

  
533
  """
534
  DOC_URI = "/2/jobs"
535

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

  
539
    Returns:
540
      A dictionary with jobs id and uri.
541
    
542
    """
543
    fields = ["id"]
544
    # Convert the list of lists to the list of ids
545
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
546
    return BuildUriList(result, "/2/jobs/%s", uri_fields=("id", "uri"))
547

  
548

  
549
class R_2_jobs_id(R_Generic):
550
  """/2/jobs/[job_id] resource.
551

  
552
  """
553
  DOC_URI = "/2/jobs/[job_id]"
554

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

  
558
    Returns: 
559
      A dictionary with job parameters.
560

  
561
    The result includes:
562
      id - job ID as a number
563
      status - current job status as a string
564
      ops - involved OpCodes as a list of dictionaries for each opcodes in 
565
        the job
566
      opstatus - OpCodes status as a list
567
      opresult - OpCodes results as a list of lists
568
    
569
    """
570
    fields = ["id", "ops", "status", "opstatus", "opresult"]
571
    job_id = self.items[0]
572
    result = luxi.Client().QueryJobs([job_id,], fields)[0]
573
    return MapFields(fields, result)
574

  
575

  
576
_CONNECTOR.update({
577
  "/": R_root,
578

  
579
  "/version": R_version,
580

  
581
  "/tags": R_tags,
582
  "/info": R_info,
583

  
584
  "/nodes": R_nodes,
585
  re.compile(r'^/nodes/([\w\._-]+)$'): R_nodes_name,
586
  re.compile(r'^/nodes/([\w\._-]+)/tags$'): R_nodes_name_tags,
587

  
588
  "/instances": R_instances,
589
  re.compile(r'^/instances/([\w\._-]+)$'): R_instances_name,
590
  re.compile(r'^/instances/([\w\._-]+)/tags$'): R_instances_name_tags,
591

  
592
  "/os": R_os,
593

  
594
  "/2/jobs": R_2_jobs,
595
  re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): R_2_jobs_id,
596
  })
b/lib/rapi/rlib1.py
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 1 resources library.
23

  
24
"""
25

  
26
import re
27

  
28
import ganeti.cli
29
import ganeti.errors
30
import ganeti.opcodes
31

  
32
from ganeti import constants
33
from ganeti import utils
34

  
35
from ganeti.rapi import baserlib 
36
from ganeti.rapi import httperror 
37

  
38

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

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

  
45
  """
46
  DOC_URI = "/version"
47

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

  
51
    """
52
    return constants.RAPI_VERSION
53

  
54

  
55
class R_tags(baserlib.R_Generic):
56
  """/tags resource.
57

  
58
  Manages cluster tags.
59

  
60
  """
61
  DOC_URI = "/tags"
62

  
63
  def GET(self):
64
    """Returns a list of all cluster tags.
65

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

  
68
    """
69
    return baserlib._Tags_GET(constants.TAG_CLUSTER)
70

  
71

  
72
class R_info(baserlib.R_Generic):
73
  """Cluster info.
74

  
75
  """
76
  DOC_URI = "/info"
77

  
78
  def GET(self):
79
    """Returns cluster information.
80

  
81
    Example: {
82
      "config_version": 3,
83
      "name": "cluster1.example.com",
84
      "software_version": "1.2.4",
85
      "os_api_version": 5,
86
      "export_version": 0,
87
      "master": "node1.example.com",
88
      "architecture": [
89
        "64bit",
90
        "x86_64"
91
      ],
92
      "hypervisor_type": "xen-3.0",
93
      "protocol_version": 12
94
    }
95

  
96
    """
97
    op = ganeti.opcodes.OpQueryClusterInfo()
98
    return ganeti.cli.SubmitOpCode(op)
99

  
100

  
101
class R_nodes(baserlib.R_Generic):
102
  """/nodes resource.
103

  
104
  """
105
  DOC_URI = "/nodes"
106

  
107
  def _GetDetails(self, nodeslist):
108
    """Returns detailed instance data for bulk output.
109

  
110
    Args:
111
      instance: A list of nodes names.
112

  
113
    Returns:
114
      A list of nodes properties
115

  
116
    """
117
    fields = ["name","dtotal", "dfree",
118
              "mtotal", "mnode", "mfree",
119
              "pinst_cnt", "sinst_cnt", "tags"]
120

  
121
    op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
122
                                     names=nodeslist)
123
    result = ganeti.cli.SubmitOpCode(op)
124

  
125
    nodes_details = []
126
    for node in result:
127
      mapped = baserlib.MapFields(fields, node)
128
      nodes_details.append(mapped)
129
    return nodes_details
130
 
131
  def GET(self):
132
    """Returns a list of all nodes.
133
    
134
    Returns:
135
      A dictionary with 'name' and 'uri' keys for each of them.
136

  
137
    Example: [
138
        {
139
          "name": "node1.example.com",
140
          "uri": "\/instances\/node1.example.com"
141
        },
142
        {
143
          "name": "node2.example.com",
144
          "uri": "\/instances\/node2.example.com"
145
        }]
146

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

  
151
    Example: [
152
        {
153
          "pinst_cnt": 1,
154
          "mfree": 31280,
155
          "mtotal": 32763,
156
          "name": "www.example.com",
157
          "tags": [],
158
          "mnode": 512,
159
          "dtotal": 5246208,
160
          "sinst_cnt": 2,
161
          "dfree": 5171712
162
        },
163
        ...
164
    ]
165

  
166
    """
167
    op = ganeti.opcodes.OpQueryNodes(output_fields=["name"], names=[])
168
    nodeslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0)
169
    
170
    if 'bulk' in self.queryargs:
171
      return self._GetDetails(nodeslist)
172

  
173
    return baserlib.BuildUriList(nodeslist, "/nodes/%s")
174

  
175

  
176
class R_nodes_name(baserlib.R_Generic):
177
  """/nodes/[node_name] resources.
178

  
179
  """
180
  DOC_URI = "/nodes/[node_name]"
181

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

  
185
    """
186
    node_name = self.items[0]
187
    fields = ["name","dtotal", "dfree",
188
              "mtotal", "mnode", "mfree",
189
              "pinst_cnt", "sinst_cnt", "tags"]
190

  
191
    op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
192
                                     names=[node_name])
193
    result = ganeti.cli.SubmitOpCode(op)
194

  
195
    return baserlib.MapFields(fields, result[0])
196

  
197

  
198
class R_nodes_name_tags(baserlib.R_Generic):
199
  """/nodes/[node_name]/tags resource.
200

  
201
  Manages per-node tags.
202

  
203
  """
204
  DOC_URI = "/nodes/[node_name]/tags"
205

  
206
  def GET(self):
207
    """Returns a list of node tags.
208

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

  
211
    """
212
    return baserlib._Tags_GET(constants.TAG_NODE, name=self.items[0])
213

  
214

  
215
class R_instances(baserlib.R_Generic):
216
  """/instances resource.
217

  
218
  """
219
  DOC_URI = "/instances"
220

  
221
  def _GetDetails(self, instanceslist):
222
    """Returns detailed instance data for bulk output.
223

  
224
    Args:
225
      instance: A list of instances names.
226

  
227
    Returns:
228
      A list with instances properties.
229

  
230
    """
231
    fields = ["name", "os", "pnode", "snodes",
232
              "admin_state", "admin_ram",
233
              "disk_template", "ip", "mac", "bridge",
234
              "sda_size", "sdb_size", "vcpus",
235
              "oper_state", "status", "tags"]
236

  
237
    op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
238
                                         names=instanceslist)
239
    result = ganeti.cli.SubmitOpCode(op)
240

  
241
    instances_details = []
242
    for instance in result:
243
      mapped = baserlib.MapFields(fields, instance)
244
      instances_details.append(mapped)
245
    return instances_details
246
   
247
  def GET(self):
248
    """Returns a list of all available instances.
249
    
250
    Returns:
251
       A dictionary with 'name' and 'uri' keys for each of them.
252

  
253
    Example: [
254
        {
255
          "name": "web.example.com",
256
          "uri": "\/instances\/web.example.com"
257
        },
258
        {
259
          "name": "mail.example.com",
260
          "uri": "\/instances\/mail.example.com"
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
           "status": "running",
270
           "bridge": "xen-br0",
271
           "name": "web.example.com",
272
           "tags": ["tag1", "tag2"],
273
           "admin_ram": 512,
274
           "sda_size": 20480,
275
           "pnode": "node1.example.com",
276
           "mac": "01:23:45:67:89:01",
277
           "sdb_size": 4096,
278
           "snodes": ["node2.example.com"],
279
           "disk_template": "drbd",
280
           "ip": null,
281
           "admin_state": true,
282
           "os": "debian-etch",
283
           "vcpus": 2,
284
           "oper_state": true
285
        },
286
        ...
287
    ]
288

  
289
    """
290
    op = ganeti.opcodes.OpQueryInstances(output_fields=["name"], names=[])
291
    instanceslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0)
292
    
293
    if 'bulk' in self.queryargs:
294
      return self._GetDetails(instanceslist)  
295

  
296
    else:
297
      return baserlib.BuildUriList(instanceslist, "/instances/%s")
298

  
299

  
300
class R_instances_name(baserlib.R_Generic):
301
  """/instances/[instance_name] resources.
302

  
303
  """
304
  DOC_URI = "/instances/[instance_name]"
305

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

  
309
    """
310
    instance_name = self.items[0]
311
    fields = ["name", "os", "pnode", "snodes",
312
              "admin_state", "admin_ram",
313
              "disk_template", "ip", "mac", "bridge",
314
              "sda_size", "sdb_size", "vcpus",
315
              "oper_state", "status", "tags"]
316

  
317
    op = ganeti.opcodes.OpQueryInstances(output_fields=fields,
318
                                         names=[instance_name])
319
    result = ganeti.cli.SubmitOpCode(op)
320

  
321
    return baserlib.MapFields(fields, result[0])
322

  
323

  
324
class R_instances_name_tags(baserlib.R_Generic):
325
  """/instances/[instance_name]/tags resource.
326

  
327
  Manages per-instance tags.
328

  
329
  """
330
  DOC_URI = "/instances/[instance_name]/tags"
331

  
332
  def GET(self):
333
    """Returns a list of instance tags.
334

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

  
337
    """
338
    return baserlib._Tags_GET(constants.TAG_INSTANCE, name=self.items[0])
339

  
340

  
341
class R_os(baserlib.R_Generic):
342
  """/os resource.
343

  
344
  """
345
  DOC_URI = "/os"
346

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

  
350
    Can return error 500 in case of a problem.
351

  
352
    Example: ["debian-etch"]
353

  
354
    """
355
    op = ganeti.opcodes.OpDiagnoseOS(output_fields=["name", "valid"],
356
                                     names=[])
357
    diagnose_data = ganeti.cli.SubmitOpCode(op)
358

  
359
    if not isinstance(diagnose_data, list):
360
      raise httperror.HTTPInternalError(message="Can't get OS list")
361

  
362
    return [row[0] for row in diagnose_data if row[1]]
b/lib/rapi/rlib2.py
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 re
27

  
28
import ganeti.opcodes
29

  
30
from ganeti import constants
31
from ganeti import luxi
32

  
33
from ganeti.rapi import baserlib 
34

  
35

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

  
39
  """
40
  DOC_URI = "/2/jobs"
41

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

  
45
    Returns:
46
      A dictionary with jobs id and uri.
47
    
48
    """
49
    fields = ["id"]
50
    # Convert the list of lists to the list of ids
51
    result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
52
    return baserlib.BuildUriList(result, "/2/jobs/%s", uri_fields=("id", "uri"))
53

  
54

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

  
58
  """
59
  DOC_URI = "/2/jobs/[job_id]"
60

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

  
64
    Returns: 
65
      A dictionary with job parameters.
66

  
67
    The result includes:
68
      id - job ID as a number
69
      status - current job status as a string
70
      ops - involved OpCodes as a list of dictionaries for each opcodes in 
71
        the job
72
      opstatus - OpCodes status as a list
73
      opresult - OpCodes results as a list of lists
74
    
75
    """
76
    fields = ["id", "ops", "status", "opstatus", "opresult"]
77
    job_id = self.items[0]
78
    result = luxi.Client().QueryJobs([job_id,], fields)[0]
79
    return baserlib.MapFields(fields, result)
80

  
81

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

  
85
  """
86
  DOC_URI = "/2/nodes"
87
 
88
  def _GetDetails(self, nodeslist):
89
    """Returns detailed instance data for bulk output.
90

  
91
    Args:
92
      instance: A list of nodes names.
93

  
94
    Returns:
95
      A list of nodes properties
96

  
97
    """
98
    fields = ["name","dtotal", "dfree",
99
              "mtotal", "mnode", "mfree",
100
              "pinst_cnt", "sinst_cnt", "tags"]
101

  
102
    op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
103
                                     names=nodeslist)
104
    result = ganeti.cli.SubmitOpCode(op)
105

  
106
    nodes_details = []
107
    for node in result:
108
      mapped = baserlib.MapFields(fields, node)
109
      nodes_details.append(mapped)
110
    return nodes_details
111
 
112
  def GET(self):
113
    """Returns a list of all nodes.
114
    
115
    Returns:
116
      A dictionary with 'name' and 'uri' keys for each of them.
117

  
118
    Example: [
119
        {
120
          "id": "node1.example.com",
121
          "uri": "\/instances\/node1.example.com"
122
        },
123
        {
124
          "id": "node2.example.com",
125
          "uri": "\/instances\/node2.example.com"
126
        }]
127

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

  
132
    Example: [
133
        {
134
          "pinst_cnt": 1,
135
          "mfree": 31280,
136
          "mtotal": 32763,
137
          "name": "www.example.com",
138
          "tags": [],
139
          "mnode": 512,
140
          "dtotal": 5246208,
141
          "sinst_cnt": 2,
142
          "dfree": 5171712
143
        },
144
        ...
145
    ]
146

  
147
    """
148
    op = ganeti.opcodes.OpQueryNodes(output_fields=["name"], names=[])
149
    nodeslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0)
150
    
151
    if 'bulk' in self.queryargs:
152
      return self._GetDetails(nodeslist)
153

  
154
    return baserlib.BuildUriList(nodeslist, "/nodes/%s", uri_fields=("id", "uri"))
b/test/ganeti.rapi.resources_unittest.py
19 19
# 02110-1301, USA.
20 20

  
21 21

  
22
"""Script for unittesting the rapi.resources module"""
22
"""Script for unittesting the RAPI resources module"""
23 23

  
24 24

  
25 25
import os
......
28 28
import time
29 29

  
30 30
from ganeti import errors
31
from ganeti.rapi import connector 
31 32
from ganeti.rapi import httperror
32
from ganeti.rapi import resources
33 33
from ganeti.rapi import RESTHTTPServer
34
from ganeti.rapi import rlib1 
34 35

  
35 36

  
36 37
class MapperTests(unittest.TestCase):
37 38
  """Tests for remote API URI mapper."""
38 39

  
39 40
  def setUp(self):
40
    self.map = resources.Mapper()
41
    self.map = connector.Mapper()
41 42

  
42 43
  def _TestUri(self, uri, result):
43 44
    self.assertEquals(self.map.getController(uri), result)
......
46 47
    self.failUnlessRaises(httperror.HTTPNotFound, self.map.getController, uri)
47 48

  
48 49
  def testMapper(self):
49
    """Testing resources.Mapper"""
50
    """Testing Mapper"""
50 51

  
51
    self._TestUri("/tags", (resources.R_tags, [], {}))
52
    self._TestUri("/tags", (rlib1.R_tags, [], {}))
53
    self._TestUri("/instances", (rlib1.R_instances, [], {}))
52 54

  
53 55
    self._TestUri('/instances/www.test.com',
54
                  (resources.R_instances_name,
56
                  (rlib1.R_instances_name,
55 57
                   ['www.test.com'],
56 58
                   {}))
57 59

  
58 60
    self._TestUri('/instances/www.test.com/tags?f=5&f=6&alt=html',
59
                  (resources.R_instances_name_tags,
61
                  (rlib1.R_instances_name_tags,
60 62
                   ['www.test.com'],
61 63
                   {'alt': ['html'],
62 64
                    'f': ['5', '6'],
......
70 72
  """Testing for R_root class."""
71 73

  
72 74
  def setUp(self):
73
    self.root = resources.R_root(None, None, None)
75
    self.root = connector.R_root(None, None, None)
74 76

  
75 77
  def testGet(self):
76 78
    expected = [

Also available in: Unified diff