Statistics
| Branch: | Tag: | Revision:

root / test / py / ganeti.server.rapi_unittest.py @ 560ef132

History | View | Annotate | Download (9.1 kB)

1 105f0d47 Michael Hanselmann
#!/usr/bin/python
2 105f0d47 Michael Hanselmann
#
3 105f0d47 Michael Hanselmann
4 105f0d47 Michael Hanselmann
# Copyright (C) 2012 Google Inc.
5 105f0d47 Michael Hanselmann
#
6 105f0d47 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 105f0d47 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 105f0d47 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 105f0d47 Michael Hanselmann
# (at your option) any later version.
10 105f0d47 Michael Hanselmann
#
11 105f0d47 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 105f0d47 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 105f0d47 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 105f0d47 Michael Hanselmann
# General Public License for more details.
15 105f0d47 Michael Hanselmann
#
16 105f0d47 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 105f0d47 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 105f0d47 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 105f0d47 Michael Hanselmann
# 02110-1301, USA.
20 105f0d47 Michael Hanselmann
21 105f0d47 Michael Hanselmann
22 105f0d47 Michael Hanselmann
"""Script for testing ganeti.server.rapi"""
23 105f0d47 Michael Hanselmann
24 105f0d47 Michael Hanselmann
import re
25 105f0d47 Michael Hanselmann
import unittest
26 105f0d47 Michael Hanselmann
import random
27 105f0d47 Michael Hanselmann
import mimetools
28 105f0d47 Michael Hanselmann
import base64
29 105f0d47 Michael Hanselmann
from cStringIO import StringIO
30 105f0d47 Michael Hanselmann
31 105f0d47 Michael Hanselmann
from ganeti import constants
32 105f0d47 Michael Hanselmann
from ganeti import utils
33 105f0d47 Michael Hanselmann
from ganeti import compat
34 105f0d47 Michael Hanselmann
from ganeti import errors
35 105f0d47 Michael Hanselmann
from ganeti import serializer
36 105f0d47 Michael Hanselmann
from ganeti import rapi
37 105f0d47 Michael Hanselmann
from ganeti import http
38 105f0d47 Michael Hanselmann
from ganeti import objects
39 105f0d47 Michael Hanselmann
40 105f0d47 Michael Hanselmann
import ganeti.rapi.baserlib
41 105f0d47 Michael Hanselmann
import ganeti.rapi.testutils
42 105f0d47 Michael Hanselmann
import ganeti.rapi.rlib2
43 105f0d47 Michael Hanselmann
import ganeti.http.auth
44 105f0d47 Michael Hanselmann
45 105f0d47 Michael Hanselmann
import testutils
46 105f0d47 Michael Hanselmann
47 105f0d47 Michael Hanselmann
48 105f0d47 Michael Hanselmann
class TestRemoteApiHandler(unittest.TestCase):
49 105f0d47 Michael Hanselmann
  @staticmethod
50 105f0d47 Michael Hanselmann
  def _LookupWrongUser(_):
51 105f0d47 Michael Hanselmann
    return None
52 105f0d47 Michael Hanselmann
53 105f0d47 Michael Hanselmann
  def _Test(self, method, path, headers, reqbody,
54 27a8a190 Michael Hanselmann
            user_fn=NotImplemented, luxi_client=NotImplemented,
55 27a8a190 Michael Hanselmann
            reqauth=False):
56 27a8a190 Michael Hanselmann
    rm = rapi.testutils._RapiMock(user_fn, luxi_client, reqauth=reqauth)
57 105f0d47 Michael Hanselmann
58 105f0d47 Michael Hanselmann
    (resp_code, resp_headers, resp_body) = \
59 105f0d47 Michael Hanselmann
      rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)),
60 105f0d47 Michael Hanselmann
                       reqbody)
61 105f0d47 Michael Hanselmann
62 105f0d47 Michael Hanselmann
    self.assertTrue(resp_headers[http.HTTP_DATE])
63 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_CONNECTION], "close")
64 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_CONTENT_TYPE], http.HTTP_APP_JSON)
65 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_SERVER], http.HTTP_GANETI_VERSION)
66 105f0d47 Michael Hanselmann
67 105f0d47 Michael Hanselmann
    return (resp_code, resp_headers, serializer.LoadJson(resp_body))
68 105f0d47 Michael Hanselmann
69 105f0d47 Michael Hanselmann
  def testRoot(self):
70 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/", "", None)
71 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
72 105f0d47 Michael Hanselmann
    self.assertTrue(data is None)
73 105f0d47 Michael Hanselmann
74 27a8a190 Michael Hanselmann
  def testRootReqAuth(self):
75 27a8a190 Michael Hanselmann
    (code, _, _) = self._Test(http.HTTP_GET, "/", "", None, reqauth=True)
76 27a8a190 Michael Hanselmann
    self.assertEqual(code, http.HttpUnauthorized.code)
77 27a8a190 Michael Hanselmann
78 105f0d47 Michael Hanselmann
  def testVersion(self):
79 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/version", "", None)
80 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
81 105f0d47 Michael Hanselmann
    self.assertEqual(data, constants.RAPI_VERSION)
82 105f0d47 Michael Hanselmann
83 105f0d47 Michael Hanselmann
  def testSlashTwo(self):
84 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/2", "", None)
85 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
86 105f0d47 Michael Hanselmann
    self.assertTrue(data is None)
87 105f0d47 Michael Hanselmann
88 105f0d47 Michael Hanselmann
  def testFeatures(self):
89 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/2/features", "", None)
90 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
91 105f0d47 Michael Hanselmann
    self.assertEqual(set(data), set(rapi.rlib2.ALL_FEATURES))
92 105f0d47 Michael Hanselmann
93 105f0d47 Michael Hanselmann
  def testPutInstances(self):
94 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_PUT, "/2/instances", "", None)
95 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpNotImplemented.code)
96 105f0d47 Michael Hanselmann
    self.assertTrue(data["message"].startswith("Method PUT is unsupported"))
97 105f0d47 Michael Hanselmann
98 105f0d47 Michael Hanselmann
  def testPostInstancesNoAuth(self):
99 105f0d47 Michael Hanselmann
    (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", "", None)
100 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpUnauthorized.code)
101 105f0d47 Michael Hanselmann
102 105f0d47 Michael Hanselmann
  def testRequestWithUnsupportedMediaType(self):
103 105f0d47 Michael Hanselmann
    for fn in [lambda s: s, lambda s: s.upper(), lambda s: s.title()]:
104 105f0d47 Michael Hanselmann
      headers = rapi.testutils._FormatHeaders([
105 105f0d47 Michael Hanselmann
        "%s: %s" % (http.HTTP_CONTENT_TYPE, fn("un/supported/media/type")),
106 105f0d47 Michael Hanselmann
        ])
107 105f0d47 Michael Hanselmann
      (code, _, data) = self._Test(http.HTTP_GET, "/", headers, "body")
108 105f0d47 Michael Hanselmann
      self.assertEqual(code, http.HttpUnsupportedMediaType.code)
109 105f0d47 Michael Hanselmann
      self.assertEqual(data["message"], "Unsupported Media Type")
110 105f0d47 Michael Hanselmann
111 105f0d47 Michael Hanselmann
  def testRequestWithInvalidJsonData(self):
112 105f0d47 Michael Hanselmann
    body = "_this/is/no'valid.json"
113 105f0d47 Michael Hanselmann
    self.assertRaises(Exception, serializer.LoadJson, body)
114 105f0d47 Michael Hanselmann
115 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
116 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
117 105f0d47 Michael Hanselmann
      ])
118 105f0d47 Michael Hanselmann
119 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/", headers, body)
120 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpBadRequest.code)
121 105f0d47 Michael Hanselmann
    self.assertEqual(data["message"], "Unable to parse JSON data")
122 105f0d47 Michael Hanselmann
123 105f0d47 Michael Hanselmann
  def testUnsupportedAuthScheme(self):
124 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
125 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_AUTHORIZATION, "Unsupported scheme"),
126 105f0d47 Michael Hanselmann
      ])
127 105f0d47 Michael Hanselmann
128 105f0d47 Michael Hanselmann
    (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
129 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpUnauthorized.code)
130 105f0d47 Michael Hanselmann
131 105f0d47 Michael Hanselmann
  def testIncompleteBasicAuth(self):
132 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
133 105f0d47 Michael Hanselmann
      "%s: Basic" % http.HTTP_AUTHORIZATION,
134 105f0d47 Michael Hanselmann
      ])
135 105f0d47 Michael Hanselmann
136 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
137 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpBadRequest.code)
138 105f0d47 Michael Hanselmann
    self.assertEqual(data["message"],
139 105f0d47 Michael Hanselmann
                     "Basic authentication requires credentials")
140 105f0d47 Michael Hanselmann
141 105f0d47 Michael Hanselmann
  def testInvalidBasicAuth(self):
142 105f0d47 Michael Hanselmann
    for auth in ["!invalid=base!64.", base64.b64encode(" "),
143 105f0d47 Michael Hanselmann
                 base64.b64encode("missingcolonchar")]:
144 105f0d47 Michael Hanselmann
      headers = rapi.testutils._FormatHeaders([
145 105f0d47 Michael Hanselmann
        "%s: Basic %s" % (http.HTTP_AUTHORIZATION, auth),
146 105f0d47 Michael Hanselmann
        ])
147 105f0d47 Michael Hanselmann
148 105f0d47 Michael Hanselmann
      (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
149 105f0d47 Michael Hanselmann
      self.assertEqual(code, http.HttpUnauthorized.code)
150 105f0d47 Michael Hanselmann
151 105f0d47 Michael Hanselmann
  @staticmethod
152 105f0d47 Michael Hanselmann
  def _MakeAuthHeaders(username, password, correct_password):
153 105f0d47 Michael Hanselmann
    if correct_password:
154 105f0d47 Michael Hanselmann
      pw = password
155 105f0d47 Michael Hanselmann
    else:
156 105f0d47 Michael Hanselmann
      pw = "wrongpass"
157 105f0d47 Michael Hanselmann
158 105f0d47 Michael Hanselmann
    return rapi.testutils._FormatHeaders([
159 105f0d47 Michael Hanselmann
      "%s: Basic %s" % (http.HTTP_AUTHORIZATION,
160 105f0d47 Michael Hanselmann
                        base64.b64encode("%s:%s" % (username, pw))),
161 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
162 105f0d47 Michael Hanselmann
      ])
163 105f0d47 Michael Hanselmann
164 105f0d47 Michael Hanselmann
  def testQueryAuth(self):
165 105f0d47 Michael Hanselmann
    username = "admin"
166 105f0d47 Michael Hanselmann
    password = "2046920054"
167 105f0d47 Michael Hanselmann
168 105f0d47 Michael Hanselmann
    header_fn = compat.partial(self._MakeAuthHeaders, username, password)
169 105f0d47 Michael Hanselmann
170 105f0d47 Michael Hanselmann
    def _LookupUserNoWrite(name):
171 105f0d47 Michael Hanselmann
      if name == username:
172 105f0d47 Michael Hanselmann
        return http.auth.PasswordFileUser(name, password, [])
173 105f0d47 Michael Hanselmann
      else:
174 105f0d47 Michael Hanselmann
        return None
175 105f0d47 Michael Hanselmann
176 5e12acfe Michael Hanselmann
    for access in [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]:
177 5e12acfe Michael Hanselmann
      def _LookupUserWithWrite(name):
178 5e12acfe Michael Hanselmann
        if name == username:
179 5e12acfe Michael Hanselmann
          return http.auth.PasswordFileUser(name, password, [
180 5e12acfe Michael Hanselmann
            access,
181 5e12acfe Michael Hanselmann
            ])
182 105f0d47 Michael Hanselmann
        else:
183 5e12acfe Michael Hanselmann
          return None
184 5e12acfe Michael Hanselmann
185 5e12acfe Michael Hanselmann
      for qr in constants.QR_VIA_RAPI:
186 5e12acfe Michael Hanselmann
        # The /2/query resource has somewhat special rules for authentication as
187 5e12acfe Michael Hanselmann
        # it can be used to retrieve critical information
188 5e12acfe Michael Hanselmann
        path = "/2/query/%s" % qr
189 5e12acfe Michael Hanselmann
190 5e12acfe Michael Hanselmann
        for method in rapi.baserlib._SUPPORTED_METHODS:
191 5e12acfe Michael Hanselmann
          # No authorization
192 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, "", "")
193 5e12acfe Michael Hanselmann
194 5e12acfe Michael Hanselmann
          if method in (http.HTTP_DELETE, http.HTTP_POST):
195 5e12acfe Michael Hanselmann
            self.assertEqual(code, http.HttpNotImplemented.code)
196 5e12acfe Michael Hanselmann
            continue
197 5e12acfe Michael Hanselmann
198 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
199 5e12acfe Michael Hanselmann
200 5e12acfe Michael Hanselmann
          # Incorrect user
201 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(True), "",
202 5e12acfe Michael Hanselmann
                                    user_fn=self._LookupWrongUser)
203 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
204 5e12acfe Michael Hanselmann
205 5e12acfe Michael Hanselmann
          # User has no write access, but the password is correct
206 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(True), "",
207 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserNoWrite)
208 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpForbidden.code)
209 5e12acfe Michael Hanselmann
210 5e12acfe Michael Hanselmann
          # Wrong password and no write access
211 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(False), "",
212 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserNoWrite)
213 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
214 5e12acfe Michael Hanselmann
215 5e12acfe Michael Hanselmann
          # Wrong password with write access
216 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(False), "",
217 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserWithWrite)
218 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
219 5e12acfe Michael Hanselmann
220 5e12acfe Michael Hanselmann
          # Prepare request information
221 5e12acfe Michael Hanselmann
          if method == http.HTTP_PUT:
222 5e12acfe Michael Hanselmann
            reqpath = path
223 5e12acfe Michael Hanselmann
            body = serializer.DumpJson({
224 5e12acfe Michael Hanselmann
              "fields": ["name"],
225 5e12acfe Michael Hanselmann
              })
226 5e12acfe Michael Hanselmann
          elif method == http.HTTP_GET:
227 5e12acfe Michael Hanselmann
            reqpath = "%s?fields=name" % path
228 5e12acfe Michael Hanselmann
            body = ""
229 5e12acfe Michael Hanselmann
          else:
230 5e12acfe Michael Hanselmann
            self.fail("Unknown method '%s'" % method)
231 5e12acfe Michael Hanselmann
232 5e12acfe Michael Hanselmann
          # User has write access, password is correct
233 5e12acfe Michael Hanselmann
          (code, _, data) = self._Test(method, reqpath, header_fn(True), body,
234 5e12acfe Michael Hanselmann
                                       user_fn=_LookupUserWithWrite,
235 5e12acfe Michael Hanselmann
                                       luxi_client=_FakeLuxiClientForQuery)
236 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HTTP_OK)
237 5e12acfe Michael Hanselmann
          self.assertTrue(objects.QueryResponse.FromDict(data))
238 5e12acfe Michael Hanselmann
239 5e12acfe Michael Hanselmann
  def testConsole(self):
240 5e12acfe Michael Hanselmann
    path = "/2/instances/inst1.example.com/console"
241 5e12acfe Michael Hanselmann
242 5e12acfe Michael Hanselmann
    for method in rapi.baserlib._SUPPORTED_METHODS:
243 27a8a190 Michael Hanselmann
      for reqauth in [False, True]:
244 27a8a190 Michael Hanselmann
        # No authorization
245 27a8a190 Michael Hanselmann
        (code, _, _) = self._Test(method, path, "", "", reqauth=reqauth)
246 5e12acfe Michael Hanselmann
247 27a8a190 Michael Hanselmann
        if method == http.HTTP_GET or reqauth:
248 27a8a190 Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
249 27a8a190 Michael Hanselmann
        else:
250 27a8a190 Michael Hanselmann
          self.assertEqual(code, http.HttpNotImplemented.code)
251 105f0d47 Michael Hanselmann
252 105f0d47 Michael Hanselmann
253 105f0d47 Michael Hanselmann
class _FakeLuxiClientForQuery:
254 105f0d47 Michael Hanselmann
  def __init__(self, *args, **kwargs):
255 105f0d47 Michael Hanselmann
    pass
256 105f0d47 Michael Hanselmann
257 105f0d47 Michael Hanselmann
  def Query(self, *args):
258 105f0d47 Michael Hanselmann
    return objects.QueryResponse(fields=[])
259 105f0d47 Michael Hanselmann
260 105f0d47 Michael Hanselmann
261 105f0d47 Michael Hanselmann
if __name__ == "__main__":
262 105f0d47 Michael Hanselmann
  testutils.GanetiTestProgram()