RAPI: Add support for querying resources
[ganeti-local] / 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 # pylint: disable-msg=C0103
26
27 # C0103: Invalid name, since the R_* names are not conforming
28
29 import cgi
30 import re
31
32 from ganeti import constants
33 from ganeti import http
34 from ganeti import utils
35
36 from ganeti.rapi import baserlib
37 from ganeti.rapi import rlib2
38
39
40 _NAME_PATTERN = r"[\w\._-]+"
41 _DISK_PATTERN = r"\d+"
42
43 # the connection map is created at the end of this file
44 CONNECTOR = {}
45
46
47 class Mapper:
48   """Map resource to method.
49
50   """
51   def __init__(self, connector=None):
52     """Resource mapper constructor.
53
54     @param connector: a dictionary, mapping method name with URL path regexp
55
56     """
57     if connector is None:
58       connector = CONNECTOR
59     self._connector = connector
60
61   def getController(self, uri):
62     """Find method for a given URI.
63
64     @param uri: string with URI
65
66     @return: None if no method is found or a tuple containing
67         the following fields:
68             - method: name of method mapped to URI
69             - items: a list of variable intems in the path
70             - args: a dictionary with additional parameters from URL
71
72     """
73     if '?' in uri:
74       (path, query) = uri.split('?', 1)
75       args = cgi.parse_qs(query)
76     else:
77       path = uri
78       query = None
79       args = {}
80
81     # Try to find handler for request path
82     result = utils.FindMatch(self._connector, path)
83
84     if result is None:
85       raise http.HttpNotFound()
86
87     (handler, groups) = result
88
89     return (handler, groups, args)
90
91
92 class R_root(baserlib.R_Generic):
93   """/ resource.
94
95   """
96   _ROOT_PATTERN = re.compile("^R_([a-zA-Z0-9]+)$")
97
98   @classmethod
99   def GET(cls):
100     """Show the list of mapped resources.
101
102     @return: a dictionary with 'name' and 'uri' keys for each of them.
103
104     """
105     rootlist = []
106     for handler in CONNECTOR.values():
107       m = cls._ROOT_PATTERN.match(handler.__name__)
108       if m:
109         name = m.group(1)
110         if name != 'root':
111           rootlist.append(name)
112
113     return baserlib.BuildUriList(rootlist, "/%s")
114
115
116 def _getResources(id_):
117   """Return a list of resources underneath given id.
118
119   This is to generalize querying of version resources lists.
120
121   @return: a list of resources names.
122
123   """
124   r_pattern = re.compile('^R_%s_([a-zA-Z0-9]+)$' % id_)
125
126   rlist = []
127   for handler in CONNECTOR.values():
128     m = r_pattern.match(handler.__name__)
129     if m:
130       name = m.group(1)
131       rlist.append(name)
132
133   return rlist
134
135
136 class R_2(baserlib.R_Generic):
137   """/2 resource.
138
139   This is the root of the version 2 API.
140
141   """
142   @staticmethod
143   def GET():
144     """Show the list of mapped resources.
145
146     @return: a dictionary with 'name' and 'uri' keys for each of them.
147
148     """
149     return baserlib.BuildUriList(_getResources("2"), "/2/%s")
150
151
152 def GetHandlers(node_name_pattern, instance_name_pattern,
153                 group_name_pattern, job_id_pattern, disk_pattern,
154                 query_res_pattern):
155   """Returns all supported resources and their handlers.
156
157   """
158   # Important note: New resources should always be added under /2. During a
159   # discussion in July 2010 it was decided that having per-resource versions
160   # is more flexible and future-compatible than versioning the whole remote
161   # API.
162   return {
163     "/": R_root,
164
165     "/version": rlib2.R_version,
166
167     "/2": R_2,
168
169     "/2/nodes": rlib2.R_2_nodes,
170     re.compile(r'^/2/nodes/(%s)$' % node_name_pattern):
171       rlib2.R_2_nodes_name,
172     re.compile(r'^/2/nodes/(%s)/tags$' % node_name_pattern):
173       rlib2.R_2_nodes_name_tags,
174     re.compile(r'^/2/nodes/(%s)/role$' % node_name_pattern):
175       rlib2.R_2_nodes_name_role,
176     re.compile(r'^/2/nodes/(%s)/evacuate$' % node_name_pattern):
177       rlib2.R_2_nodes_name_evacuate,
178     re.compile(r'^/2/nodes/(%s)/migrate$' % node_name_pattern):
179       rlib2.R_2_nodes_name_migrate,
180     re.compile(r'^/2/nodes/(%s)/storage$' % node_name_pattern):
181       rlib2.R_2_nodes_name_storage,
182     re.compile(r'^/2/nodes/(%s)/storage/modify$' % node_name_pattern):
183       rlib2.R_2_nodes_name_storage_modify,
184     re.compile(r'^/2/nodes/(%s)/storage/repair$' % node_name_pattern):
185       rlib2.R_2_nodes_name_storage_repair,
186
187     "/2/instances": rlib2.R_2_instances,
188     re.compile(r'^/2/instances/(%s)$' % instance_name_pattern):
189       rlib2.R_2_instances_name,
190     re.compile(r'^/2/instances/(%s)/info$' % instance_name_pattern):
191       rlib2.R_2_instances_name_info,
192     re.compile(r'^/2/instances/(%s)/tags$' % instance_name_pattern):
193       rlib2.R_2_instances_name_tags,
194     re.compile(r'^/2/instances/(%s)/reboot$' % instance_name_pattern):
195       rlib2.R_2_instances_name_reboot,
196     re.compile(r'^/2/instances/(%s)/reinstall$' % instance_name_pattern):
197       rlib2.R_2_instances_name_reinstall,
198     re.compile(r'^/2/instances/(%s)/replace-disks$' % instance_name_pattern):
199       rlib2.R_2_instances_name_replace_disks,
200     re.compile(r'^/2/instances/(%s)/shutdown$' % instance_name_pattern):
201       rlib2.R_2_instances_name_shutdown,
202     re.compile(r'^/2/instances/(%s)/startup$' % instance_name_pattern):
203       rlib2.R_2_instances_name_startup,
204     re.compile(r'^/2/instances/(%s)/activate-disks$' % instance_name_pattern):
205       rlib2.R_2_instances_name_activate_disks,
206     re.compile(r'^/2/instances/(%s)/deactivate-disks$' % instance_name_pattern):
207       rlib2.R_2_instances_name_deactivate_disks,
208     re.compile(r'^/2/instances/(%s)/prepare-export$' % instance_name_pattern):
209       rlib2.R_2_instances_name_prepare_export,
210     re.compile(r'^/2/instances/(%s)/export$' % instance_name_pattern):
211       rlib2.R_2_instances_name_export,
212     re.compile(r'^/2/instances/(%s)/migrate$' % instance_name_pattern):
213       rlib2.R_2_instances_name_migrate,
214     re.compile(r'^/2/instances/(%s)/rename$' % instance_name_pattern):
215       rlib2.R_2_instances_name_rename,
216     re.compile(r'^/2/instances/(%s)/modify$' % instance_name_pattern):
217       rlib2.R_2_instances_name_modify,
218     re.compile(r"^/2/instances/(%s)/disk/(%s)/grow$" %
219                (instance_name_pattern, disk_pattern)):
220       rlib2.R_2_instances_name_disk_grow,
221     re.compile(r'^/2/instances/(%s)/console$' % instance_name_pattern):
222       rlib2.R_2_instances_name_console,
223
224     "/2/groups": rlib2.R_2_groups,
225     re.compile(r'^/2/groups/(%s)$' % group_name_pattern):
226       rlib2.R_2_groups_name,
227     re.compile(r'^/2/groups/(%s)/modify$' % group_name_pattern):
228       rlib2.R_2_groups_name_modify,
229     re.compile(r'^/2/groups/(%s)/rename$' % group_name_pattern):
230       rlib2.R_2_groups_name_rename,
231     re.compile(r'^/2/groups/(%s)/assign-nodes$' % group_name_pattern):
232       rlib2.R_2_groups_name_assign_nodes,
233
234     "/2/jobs": rlib2.R_2_jobs,
235     re.compile(r"^/2/jobs/(%s)$" % job_id_pattern):
236       rlib2.R_2_jobs_id,
237     re.compile(r"^/2/jobs/(%s)/wait$" % job_id_pattern):
238       rlib2.R_2_jobs_id_wait,
239
240     "/2/tags": rlib2.R_2_tags,
241     "/2/info": rlib2.R_2_info,
242     "/2/os": rlib2.R_2_os,
243     "/2/redistribute-config": rlib2.R_2_redist_config,
244     "/2/features": rlib2.R_2_features,
245     "/2/modify": rlib2.R_2_cluster_modify,
246     re.compile(r"^/2/query/(%s)$" % query_res_pattern): rlib2.R_2_query,
247     re.compile(r"^/2/query/(%s)/fields$" % query_res_pattern):
248       rlib2.R_2_query_fields,
249     }
250
251
252 CONNECTOR.update(GetHandlers(_NAME_PATTERN, _NAME_PATTERN, _NAME_PATTERN,
253                              constants.JOB_ID_TEMPLATE, _DISK_PATTERN,
254                              _NAME_PATTERN))