Skip to content
Snippets Groups Projects
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
No related branches found
No related tags found
No related merge requests found
...@@ -22,15 +22,16 @@ ...@@ -22,15 +22,16 @@
""" """
import glob import glob
import logging
import optparse import optparse
import sys import sys
import os import os
import signal import signal
from ganeti import logger
from ganeti import constants from ganeti import constants
from ganeti import errors from ganeti import errors
from ganeti import http from ganeti import http
from ganeti import rpc
from ganeti import ssconf from ganeti import ssconf
from ganeti import utils from ganeti import utils
from ganeti.rapi import connector from ganeti.rapi import connector
...@@ -49,7 +50,7 @@ class RESTRequestHandler(http.HTTPRequestHandler): ...@@ -49,7 +50,7 @@ class RESTRequestHandler(http.HTTPRequestHandler):
""" """
(HandlerClass, items, args) = self._resmap.getController(self.path) (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() command = self.command.upper()
try: try:
...@@ -58,7 +59,11 @@ class RESTRequestHandler(http.HTTPRequestHandler): ...@@ -58,7 +59,11 @@ class RESTRequestHandler(http.HTTPRequestHandler):
raise http.HTTPBadRequest() raise http.HTTPBadRequest()
try: try:
result = fn() try:
result = fn()
except:
logging.exception("Error while handling the %s request", command)
raise
except errors.OpPrereqError, err: except errors.OpPrereqError, err:
# TODO: "Not found" is not always the correct error. Ganeti's core must # TODO: "Not found" is not always the correct error. Ganeti's core must
...@@ -135,6 +140,9 @@ def main(): ...@@ -135,6 +140,9 @@ def main():
if options.fork: if options.fork:
utils.Daemonize(logfile=constants.LOG_RAPISERVER) utils.Daemonize(logfile=constants.LOG_RAPISERVER)
logger.SetupLogging(constants.LOG_RAPISERVER, debug=options.debug,
stderr_logging=not options.fork)
utils.WritePidFile(constants.RAPI_PID) utils.WritePidFile(constants.RAPI_PID)
log_fd = open(constants.LOG_RAPIACCESS, 'a') log_fd = open(constants.LOG_RAPIACCESS, 'a')
...@@ -155,4 +163,5 @@ def main(): ...@@ -155,4 +163,5 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
import ganeti.cli import ganeti.cli
import ganeti.opcodes import ganeti.opcodes
from ganeti import luxi
def BuildUriList(ids, uri_format, uri_fields=("name", "uri")): def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
"""Builds a URI list as used by index resources. """Builds a URI list as used by index resources.
...@@ -89,6 +91,19 @@ def _Tags_GET(kind, name=None): ...@@ -89,6 +91,19 @@ def _Tags_GET(kind, name=None):
return list(tags) 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,
tags=tags)])
def MapBulkFields(itemslist, fields): def MapBulkFields(itemslist, fields):
"""Map value to field name in to one dictionary. """Map value to field name in to one dictionary.
...@@ -110,7 +125,7 @@ class R_Generic(object): ...@@ -110,7 +125,7 @@ class R_Generic(object):
"""Generic class for resources. """Generic class for resources.
""" """
def __init__(self, request, items, queryargs): def __init__(self, request, items, queryargs, post_data):
"""Generic resource constructor. """Generic resource constructor.
Args: Args:
...@@ -122,3 +137,4 @@ class R_Generic(object): ...@@ -122,3 +137,4 @@ class R_Generic(object):
self.request = request self.request = request
self.items = items self.items = items
self.queryargs = queryargs self.queryargs = queryargs
self.post_data = post_data
...@@ -137,5 +137,8 @@ CONNECTOR.update({ ...@@ -137,5 +137,8 @@ CONNECTOR.update({
"/2/jobs": rlib2.R_2_jobs, "/2/jobs": rlib2.R_2_jobs,
"/2/nodes": rlib2.R_2_nodes, "/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, 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): ...@@ -129,5 +129,97 @@ class R_2_nodes(baserlib.R_Generic):
result = ganeti.cli.SubmitOpCode(op) result = ganeti.cli.SubmitOpCode(op)
return baserlib.MapBulkFields(result, N_FIELDS) return baserlib.MapBulkFields(result, N_FIELDS)
return baserlib.BuildUriList(nodeslist, "/nodes/%s", return baserlib.BuildUriList(nodeslist, "/2/nodes/%s",
uri_fields=("id", "uri")) 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.
Returns:
A dictionary with 'name' and 'uri' keys for each of them.
Example: [
{
"name": "web.example.com",
"uri": "\/instances\/web.example.com"
},
{
"name": "mail.example.com",
"uri": "\/instances\/mail.example.com"
}]
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": "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
},
...
]
"""
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,
names=instanceslist)
result = ganeti.cli.SubmitOpCode(op)
return baserlib.MapBulkFields(result, I_FIELDS)
else:
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 ...@@ -28,8 +28,8 @@ import tempfile
from ganeti import errors from ganeti import errors
from ganeti import http from ganeti import http
from ganeti.rapi import connector from ganeti.rapi import connector
from ganeti.rapi import rlib1 from ganeti.rapi import rlib1
class MapperTests(unittest.TestCase): class MapperTests(unittest.TestCase):
...@@ -70,7 +70,7 @@ class R_RootTests(unittest.TestCase): ...@@ -70,7 +70,7 @@ class R_RootTests(unittest.TestCase):
"""Testing for R_root class.""" """Testing for R_root class."""
def setUp(self): def setUp(self):
self.root = connector.R_root(None, None, None) self.root = connector.R_root(None, None, None, None)
def testGet(self): def testGet(self):
expected = [ expected = [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment