Merge branch 'devel-2.1'
[ganeti-local] / qa / qa_rapi.py
1 #
2
3 # Copyright (C) 2007, 2008 Google Inc.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19
20
21 """Remote API QA tests.
22
23 """
24
25 import urllib2
26
27 from ganeti import utils
28 from ganeti import constants
29 from ganeti import errors
30 from ganeti import serializer
31
32 import qa_config
33 import qa_utils
34 import qa_error
35
36 from qa_utils import (AssertEqual, AssertNotEqual, AssertIn, AssertMatch,
37                       StartSSH)
38
39
40 class OpenerFactory:
41   """A factory singleton to construct urllib opener chain.
42
43   This is needed because qa_config is not initialized yet at module load time
44
45   """
46   _opener = None
47   _rapi_user = None
48   _rapi_secret = None
49
50   @classmethod
51   def SetCredentials(cls, rapi_user, rapi_secret):
52     """Set the credentials for authorized access.
53
54     """
55     cls._rapi_user = rapi_user
56     cls._rapi_secret = rapi_secret
57
58   @classmethod
59   def Opener(cls):
60     """Construct the opener if not yet done.
61
62     """
63     if not cls._opener:
64       if not cls._rapi_user or not cls._rapi_secret:
65         raise errors.ProgrammerError("SetCredentials was never called.")
66
67       # Create opener which doesn't try to look for proxies and does auth
68       master = qa_config.GetMasterNode()
69       host = master["primary"]
70       port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
71       passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
72       passman.add_password(None, 'https://%s:%s' % (host, port),
73                            cls._rapi_user,
74                            cls._rapi_secret)
75       authhandler = urllib2.HTTPBasicAuthHandler(passman)
76       cls._opener = urllib2.build_opener(urllib2.ProxyHandler({}), authhandler)
77
78     return cls._opener
79
80
81 class RapiRequest(urllib2.Request):
82   """This class supports other methods beside GET/POST.
83
84   """
85
86   def __init__(self, url, data=None, headers={}, origin_req_host=None,
87                unverifiable=False, method="GET"):
88     urllib2.Request.__init__(self, url, data, headers, origin_req_host,
89                              unverifiable)
90     self._method = method
91
92   def get_method(self):
93     return self._method
94
95
96 INSTANCE_FIELDS = ("name", "os", "pnode", "snodes",
97                    "admin_state",
98                    "disk_template", "disk.sizes",
99                    "nic.ips", "nic.macs", "nic.modes", "nic.links",
100                    "beparams", "hvparams",
101                    "oper_state", "oper_ram", "status", "tags")
102
103 NODE_FIELDS = ("name", "dtotal", "dfree",
104                "mtotal", "mnode", "mfree",
105                "pinst_cnt", "sinst_cnt", "tags")
106
107 LIST_FIELDS = ("id", "uri")
108
109
110 def Enabled():
111   """Return whether remote API tests should be run.
112
113   """
114   return qa_config.TestEnabled('rapi')
115
116
117 def _DoTests(uris):
118   master = qa_config.GetMasterNode()
119   host = master["primary"]
120   port = qa_config.get("rapi-port", default=constants.DEFAULT_RAPI_PORT)
121
122   for uri, verify, method in uris:
123     assert uri.startswith("/")
124
125     url = "https://%s:%s%s" % (host, port, uri)
126
127     print "Testing %s ..." % url
128
129     req = RapiRequest(url, method=method)
130     response = OpenerFactory.Opener().open(req)
131
132     AssertEqual(response.info()["Content-type"], "application/json")
133
134     data = serializer.LoadJson(response.read())
135
136     if verify is not None:
137       if callable(verify):
138         verify(data)
139       else:
140         AssertEqual(data, verify)
141
142
143 def TestVersion():
144   """Testing remote API version.
145
146   """
147   _DoTests([
148     ("/version", constants.RAPI_VERSION, 'GET'),
149     ])
150
151
152 def TestEmptyCluster():
153   """Testing remote API on an empty cluster.
154
155   """
156   master_name = qa_config.GetMasterNode()["primary"]
157
158   def _VerifyInfo(data):
159     AssertIn("name", data)
160     AssertIn("master", data)
161     AssertEqual(data["master"], master_name)
162
163   def _VerifyNodes(data):
164     master_entry = {
165       "id": master_name,
166       "uri": "/2/nodes/%s" % master_name,
167       }
168     AssertIn(master_entry, data)
169
170   def _VerifyNodesBulk(data):
171     for node in data:
172       for entry in NODE_FIELDS:
173         AssertIn(entry, node)
174
175   _DoTests([
176     ("/", None, 'GET'),
177     ("/2/info", _VerifyInfo, 'GET'),
178     ("/2/tags", None, 'GET'),
179     ("/2/nodes", _VerifyNodes, 'GET'),
180     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET'),
181     ("/2/instances", [], 'GET'),
182     ("/2/instances?bulk=1", [], 'GET'),
183     ("/2/os", None, 'GET'),
184     ])
185
186
187 def TestInstance(instance):
188   """Testing getting instance(s) info via remote API.
189
190   """
191   def _VerifyInstance(data):
192     for entry in INSTANCE_FIELDS:
193       AssertIn(entry, data)
194
195   def _VerifyInstancesList(data):
196     for instance in data:
197       for entry in LIST_FIELDS:
198         AssertIn(entry, instance)
199
200   def _VerifyInstancesBulk(data):
201     for instance_data in data:
202       _VerifyInstance(instance_data)
203
204   def _VerifyReturnsJob(data):
205     AssertMatch(data, r'^\d+$')
206
207   _DoTests([
208     ("/2/instances/%s" % instance["name"], _VerifyInstance, 'GET'),
209     ("/2/instances", _VerifyInstancesList, 'GET'),
210     ("/2/instances?bulk=1", _VerifyInstancesBulk, 'GET'),
211     ("/2/instances/%s/activate-disks" % instance["name"], _VerifyReturnsJob, 'PUT'),
212     ("/2/instances/%s/deactivate-disks" % instance["name"], _VerifyReturnsJob, 'PUT'),
213     ])
214
215
216 def TestNode(node):
217   """Testing getting node(s) info via remote API.
218
219   """
220   def _VerifyNode(data):
221     for entry in NODE_FIELDS:
222       AssertIn(entry, data)
223
224   def _VerifyNodesList(data):
225     for node in data:
226       for entry in LIST_FIELDS:
227         AssertIn(entry, node)
228
229   def _VerifyNodesBulk(data):
230     for node_data in data:
231       _VerifyNode(node_data)
232
233   _DoTests([
234     ("/2/nodes/%s" % node["primary"], _VerifyNode, 'GET'),
235     ("/2/nodes", _VerifyNodesList, 'GET'),
236     ("/2/nodes?bulk=1", _VerifyNodesBulk, 'GET'),
237     ])
238
239
240 def TestTags(kind, name, tags):
241   """Tests .../tags resources.
242
243   """
244   if kind == constants.TAG_CLUSTER:
245     uri = "/2/tags"
246   elif kind == constants.TAG_NODE:
247     uri = "/2/nodes/%s/tags" % name
248   elif kind == constants.TAG_INSTANCE:
249     uri = "/2/instances/%s/tags" % name
250   else:
251     raise errors.ProgrammerError("Unknown tag kind")
252
253   def _VerifyTags(data):
254     # Create copies to modify
255     should = tags[:]
256     should.sort()
257
258     returned = data[:]
259     returned.sort()
260     AssertEqual(should, returned)
261
262   _DoTests([
263     (uri, _VerifyTags, 'GET'),
264     ])