Commit 10b207d4 authored by Oleksiy Mishchenko's avatar Oleksiy Mishchenko

Split RAPI resources to pieces

Reviewed-by: iustinp
parent 53b1d12b
......@@ -94,7 +94,10 @@ rapi_PYTHON = \
lib/rapi/__init__.py \
lib/rapi/RESTHTTPServer.py \
lib/rapi/httperror.py \
lib/rapi/resources.py
lib/rapi/baserlib.py \
lib/rapi/connector.py \
lib/rapi/rlib1.py \
lib/rapi/rlib2.py
docsgml = \
......@@ -226,7 +229,7 @@ doc/%.html: doc/%.in $(DOCBOOK_WRAPPER)
doc/rapi.pdf doc/rapi.html: doc/rapi-resources.sgml
doc/rapi-resources.sgml: $(BUILD_RAPI_RESOURCE_DOC) lib/rapi/resources.py
doc/rapi-resources.sgml: $(BUILD_RAPI_RESOURCE_DOC) lib/rapi/connector.py
PYTHONPATH=.:$(top_builddir) $(BUILD_RAPI_RESOURCE_DOC) > $@ || rm -f $@
man/%.7: man/%.in man/footer.sgml $(DOCBOOK_WRAPPER)
......
......@@ -26,7 +26,9 @@ import re
import cgi
import inspect
from ganeti.rapi import resources
from ganeti.rapi import rlib1
from ganeti.rapi import rlib2
from ganeti.rapi import connector
CHECKED_COMMANDS = ["GET", "POST", "PUT", "DELETE"]
......@@ -34,9 +36,9 @@ CHECKED_COMMANDS = ["GET", "POST", "PUT", "DELETE"]
def main():
# Get list of all resources
all = list(resources._CONNECTOR.itervalues())
all = list(connector.CONNECTOR.itervalues())
# Sort resources by URI
# Sort rlib1 by URI
all.sort(cmp=lambda a, b: cmp(a.DOC_URI, b.DOC_URI))
print "<!-- Automatically generated, do not edit -->"
......
......@@ -20,9 +20,10 @@
"""
import socket
import BaseHTTPServer
import OpenSSL
import re
import socket
import time
from ganeti import constants
......@@ -30,7 +31,8 @@ from ganeti import errors
from ganeti import logger
from ganeti import rpc
from ganeti import serializer
from ganeti.rapi import resources
from ganeti.rapi import connector
from ganeti.rapi import httperror
......@@ -158,7 +160,7 @@ class RESTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
self._resmap = resources.Mapper()
self._resmap = connector.Mapper()
def handle_one_request(self):
"""Handle a single REST request.
......
#
#
# 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 connection map.
"""
import cgi
import re
from ganeti import constants
from ganeti.rapi import baserlib
from ganeti.rapi import httperror
from ganeti.rapi import rlib1
from ganeti.rapi import rlib2
# the connection map created at the end of this file
CONNECTOR = {}
class Mapper:
"""Map resource to method.
"""
def __init__(self, connector=CONNECTOR):
"""Resource mapper constructor.
Args:
con: a dictionary, mapping method name with URL path regexp
"""
self._connector = connector
def getController(self, uri):
"""Find method for a given URI.
Args:
uri: string with URI
Returns:
None if no method is found or a tuple containing the following fields:
methd: name of method mapped to URI
items: a list of variable intems in the path
args: a dictionary with additional parameters from URL
"""
if '?' in uri:
(path, query) = uri.split('?', 1)
args = cgi.parse_qs(query)
else:
path = uri
query = None
args = {}
result = None
for key, handler in self._connector.iteritems():
# Regex objects
if hasattr(key, "match"):
m = key.match(path)
if m:
result = (handler, list(m.groups()), args)
break
# String objects
elif key == path:
result = (handler, [], args)
break
if result is not None:
return result
else:
raise httperror.HTTPNotFound()
class R_root(baserlib.R_Generic):
"""/ resource.
"""
DOC_URI = "/"
def GET(self):
"""Show the list of mapped resources.
Returns:
A dictionary with 'name' and 'uri' keys for each of them.
"""
root_pattern = re.compile('^R_([a-zA-Z0-9]+)$')
rootlist = []
for handler in CONNECTOR.values():
m = root_pattern.match(handler.__name__)
if m:
name = m.group(1)
if name != 'root':
rootlist.append(name)
return baserlib.BuildUriList(rootlist, "/%s")
CONNECTOR.update({
"/": R_root,
"/version": rlib1.R_version,
"/tags": rlib1.R_tags,
"/info": rlib1.R_info,
"/nodes": rlib1.R_nodes,
re.compile(r'^/nodes/([\w\._-]+)$'): rlib1.R_nodes_name,
re.compile(r'^/nodes/([\w\._-]+)/tags$'): rlib1.R_nodes_name_tags,
"/instances": rlib1.R_instances,
re.compile(r'^/instances/([\w\._-]+)$'): rlib1.R_instances_name,
re.compile(r'^/instances/([\w\._-]+)/tags$'): rlib1.R_instances_name_tags,
"/os": rlib1.R_os,
"/2/jobs": rlib2.R_2_jobs,
"/2/nodes": rlib2.R_2_nodes,
re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): rlib2.R_2_jobs_id,
})
This diff is collapsed.
#
#
# 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 re
import ganeti.opcodes
from ganeti import constants
from ganeti import luxi
from ganeti.rapi import baserlib
class R_2_jobs(baserlib.R_Generic):
"""/2/jobs resource.
"""
DOC_URI = "/2/jobs"
def GET(self):
"""Returns a dictionary of jobs.
Returns:
A dictionary with jobs id and uri.
"""
fields = ["id"]
# Convert the list of lists to the list of ids
result = [job_id for [job_id] in luxi.Client().QueryJobs(None, fields)]
return baserlib.BuildUriList(result, "/2/jobs/%s", uri_fields=("id", "uri"))
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.
Returns:
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
"""
fields = ["id", "ops", "status", "opstatus", "opresult"]
job_id = self.items[0]
result = luxi.Client().QueryJobs([job_id,], fields)[0]
return baserlib.MapFields(fields, result)
class R_2_nodes(baserlib.R_Generic):
"""/2/nodes resource.
"""
DOC_URI = "/2/nodes"
def _GetDetails(self, nodeslist):
"""Returns detailed instance data for bulk output.
Args:
instance: A list of nodes names.
Returns:
A list of nodes properties
"""
fields = ["name","dtotal", "dfree",
"mtotal", "mnode", "mfree",
"pinst_cnt", "sinst_cnt", "tags"]
op = ganeti.opcodes.OpQueryNodes(output_fields=fields,
names=nodeslist)
result = ganeti.cli.SubmitOpCode(op)
nodes_details = []
for node in result:
mapped = baserlib.MapFields(fields, node)
nodes_details.append(mapped)
return nodes_details
def GET(self):
"""Returns a list of all nodes.
Returns:
A dictionary with 'name' and 'uri' keys for each of them.
Example: [
{
"id": "node1.example.com",
"uri": "\/instances\/node1.example.com"
},
{
"id": "node2.example.com",
"uri": "\/instances\/node2.example.com"
}]
If the optional 'bulk' argument is provided and set to 'true'
value (i.e '?bulk=1'), the output contains detailed
information about nodes as a list.
Example: [
{
"pinst_cnt": 1,
"mfree": 31280,
"mtotal": 32763,
"name": "www.example.com",
"tags": [],
"mnode": 512,
"dtotal": 5246208,
"sinst_cnt": 2,
"dfree": 5171712
},
...
]
"""
op = ganeti.opcodes.OpQueryNodes(output_fields=["name"], names=[])
nodeslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0)
if 'bulk' in self.queryargs:
return self._GetDetails(nodeslist)
return baserlib.BuildUriList(nodeslist, "/nodes/%s", uri_fields=("id", "uri"))
......@@ -19,7 +19,7 @@
# 02110-1301, USA.
"""Script for unittesting the rapi.resources module"""
"""Script for unittesting the RAPI resources module"""
import os
......@@ -28,16 +28,17 @@ import tempfile
import time
from ganeti import errors
from ganeti.rapi import connector
from ganeti.rapi import httperror
from ganeti.rapi import resources
from ganeti.rapi import RESTHTTPServer
from ganeti.rapi import rlib1
class MapperTests(unittest.TestCase):
"""Tests for remote API URI mapper."""
def setUp(self):
self.map = resources.Mapper()
self.map = connector.Mapper()
def _TestUri(self, uri, result):
self.assertEquals(self.map.getController(uri), result)
......@@ -46,17 +47,18 @@ class MapperTests(unittest.TestCase):
self.failUnlessRaises(httperror.HTTPNotFound, self.map.getController, uri)
def testMapper(self):
"""Testing resources.Mapper"""
"""Testing Mapper"""
self._TestUri("/tags", (resources.R_tags, [], {}))
self._TestUri("/tags", (rlib1.R_tags, [], {}))
self._TestUri("/instances", (rlib1.R_instances, [], {}))
self._TestUri('/instances/www.test.com',
(resources.R_instances_name,
(rlib1.R_instances_name,
['www.test.com'],
{}))
self._TestUri('/instances/www.test.com/tags?f=5&f=6&alt=html',
(resources.R_instances_name_tags,
(rlib1.R_instances_name_tags,
['www.test.com'],
{'alt': ['html'],
'f': ['5', '6'],
......@@ -70,7 +72,7 @@ class R_RootTests(unittest.TestCase):
"""Testing for R_root class."""
def setUp(self):
self.root = resources.R_root(None, None, None)
self.root = connector.R_root(None, None, None)
def testGet(self):
expected = [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment