Commit 441e7cfd authored by Oleksiy Mishchenko's avatar Oleksiy Mishchenko
Browse files

First write operation (add tag) for Ganeti RAPI

Add instance tag handling, improved error logging.
...oh, yes adopt instance listing for RAPI2!

Reviewed-by: iustinp
parent 140aa4a8
......@@ -22,15 +22,16 @@
import glob
import logging
import optparse
import sys
import os
import signal
from ganeti import logger
from ganeti import constants
from ganeti import errors
from ganeti import http
from ganeti import rpc
from ganeti import ssconf
from ganeti import utils
from ganeti.rapi import connector
......@@ -49,7 +50,7 @@ class RESTRequestHandler(http.HTTPRequestHandler):
(HandlerClass, items, args) = self._resmap.getController(self.path)
handler = HandlerClass(self, items, args)
handler = HandlerClass(self, items, args, self.post_data)
command = self.command.upper()
......@@ -58,7 +59,11 @@ class RESTRequestHandler(http.HTTPRequestHandler):
raise http.HTTPBadRequest()
result = fn()
result = fn()
logging.exception("Error while handling the %s request", command)
except errors.OpPrereqError, err:
# TODO: "Not found" is not always the correct error. Ganeti's core must
......@@ -135,6 +140,9 @@ def main():
if options.fork:
logger.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
stderr_logging=not options.fork)
log_fd = open(constants.LOG_RAPIACCESS, 'a')
......@@ -155,4 +163,5 @@ def main():
if __name__ == '__main__':
......@@ -26,6 +26,8 @@
import ganeti.cli
import ganeti.opcodes
from ganeti import luxi
def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
"""Builds a URI list as used by index resources.
......@@ -89,6 +91,19 @@ def _Tags_GET(kind, name=None):
return list(tags)
def _Tags_POST(kind, tags, name=None):
"""Helper function to set tags.
if name is None:
# Do not cause "missing parameter" error, which happens if a parameter
# is None.
name = ""
cl = luxi.Client()
return cl.SubmitJob([ganeti.opcodes.OpAddTags(kind=kind, name=name,
def MapBulkFields(itemslist, fields):
"""Map value to field name in to one dictionary.
......@@ -110,7 +125,7 @@ class R_Generic(object):
"""Generic class for resources.
def __init__(self, request, items, queryargs):
def __init__(self, request, items, queryargs, post_data):
"""Generic resource constructor.
......@@ -122,3 +137,4 @@ class R_Generic(object):
self.request = request
self.items = items
self.queryargs = queryargs
self.post_data = post_data
......@@ -137,5 +137,8 @@ CONNECTOR.update({
"/2/jobs": rlib2.R_2_jobs,
"/2/nodes": rlib2.R_2_nodes,
"/2/instances": rlib2.R_2_instances,
re.compile(r'^/2/instances/([\w\._-]+)$'): rlib1.R_instances_name,
re.compile(r'^/2/instances/([\w\._-]+)/tags$'): rlib2.R_2_instances_name_tags,
re.compile(r'/2/jobs/(%s)$' % constants.JOB_ID_TEMPLATE): rlib2.R_2_jobs_id,
......@@ -129,5 +129,97 @@ class R_2_nodes(baserlib.R_Generic):
result = ganeti.cli.SubmitOpCode(op)
return baserlib.MapBulkFields(result, N_FIELDS)
return baserlib.BuildUriList(nodeslist, "/nodes/%s",
return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
uri_fields=("id", "uri"))
class R_2_instances(baserlib.R_Generic):
"""/2/instances resource.
DOC_URI = "/2/instances"
def GET(self):
"""Returns a list of all available instances.
A dictionary with 'name' and 'uri' keys for each of them.
Example: [
"name": "",
"uri": "\/instances\/"
"name": "",
"uri": "\/instances\/"
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.
Example: [
"status": "running",
"bridge": "xen-br0",
"name": "",
"tags": ["tag1", "tag2"],
"admin_ram": 512,
"sda_size": 20480,
"pnode": "",
"mac": "01:23:45:67:89:01",
"sdb_size": 4096,
"snodes": [""],
"disk_template": "drbd",
"ip": null,
"admin_state": true,
"os": "debian-etch",
"vcpus": 2,
"oper_state": true
op = ganeti.opcodes.OpQueryInstances(output_fields=["name"], names=[])
instanceslist = baserlib.ExtractField(ganeti.cli.SubmitOpCode(op), 0)
if 'bulk' in self.queryargs:
op = ganeti.opcodes.OpQueryInstances(output_fields=I_FIELDS,
result = ganeti.cli.SubmitOpCode(op)
return baserlib.MapBulkFields(result, I_FIELDS)
return baserlib.BuildUriList(instanceslist, "/2/instances/%s",
uri_fields=("id", "uri"))
class R_2_instances_name_tags(baserlib.R_Generic):
"""/2/instances/[instance_name]/tags resource.
Manages per-instance tags.
DOC_URI = "/2/instances/[instance_name]/tags"
def GET(self):
"""Returns a list of instance tags.
Example: ["tag1", "tag2", "tag3"]
return baserlib._Tags_GET(constants.TAG_INSTANCE, name=self.items[0])
def POST(self):
"""Add a set of tags to the instance.
The reqest as a list of strings should be POST to this URI. And you'll have
back a job id.
return baserlib._Tags_POST(constants.TAG_INSTANCE,
self.post_data, name=self.items[0])
......@@ -28,8 +28,8 @@ import tempfile
from ganeti import errors
from ganeti import http
from ganeti.rapi import connector
from ganeti.rapi import rlib1
from ganeti.rapi import connector
from ganeti.rapi import rlib1
class MapperTests(unittest.TestCase):
......@@ -70,7 +70,7 @@ class R_RootTests(unittest.TestCase):
"""Testing for R_root class."""
def setUp(self):
self.root = connector.R_root(None, None, None)
self.root = connector.R_root(None, 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