Statistics
| Branch: | Tag: | Revision:

root / test / ganeti.server.rapi_unittest.py @ 1a2eb2dc

History | View | Annotate | Download (8.8 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 105f0d47 Michael Hanselmann
            user_fn=NotImplemented, luxi_client=NotImplemented):
55 105f0d47 Michael Hanselmann
    rm = rapi.testutils._RapiMock(user_fn, luxi_client)
56 105f0d47 Michael Hanselmann
57 105f0d47 Michael Hanselmann
    (resp_code, resp_headers, resp_body) = \
58 105f0d47 Michael Hanselmann
      rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)),
59 105f0d47 Michael Hanselmann
                       reqbody)
60 105f0d47 Michael Hanselmann
61 105f0d47 Michael Hanselmann
    self.assertTrue(resp_headers[http.HTTP_DATE])
62 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_CONNECTION], "close")
63 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_CONTENT_TYPE], http.HTTP_APP_JSON)
64 105f0d47 Michael Hanselmann
    self.assertEqual(resp_headers[http.HTTP_SERVER], http.HTTP_GANETI_VERSION)
65 105f0d47 Michael Hanselmann
66 105f0d47 Michael Hanselmann
    return (resp_code, resp_headers, serializer.LoadJson(resp_body))
67 105f0d47 Michael Hanselmann
68 105f0d47 Michael Hanselmann
  def testRoot(self):
69 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/", "", None)
70 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
71 105f0d47 Michael Hanselmann
    self.assertTrue(data is None)
72 105f0d47 Michael Hanselmann
73 105f0d47 Michael Hanselmann
  def testVersion(self):
74 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/version", "", None)
75 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
76 105f0d47 Michael Hanselmann
    self.assertEqual(data, constants.RAPI_VERSION)
77 105f0d47 Michael Hanselmann
78 105f0d47 Michael Hanselmann
  def testSlashTwo(self):
79 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/2", "", None)
80 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
81 105f0d47 Michael Hanselmann
    self.assertTrue(data is None)
82 105f0d47 Michael Hanselmann
83 105f0d47 Michael Hanselmann
  def testFeatures(self):
84 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/2/features", "", None)
85 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HTTP_OK)
86 105f0d47 Michael Hanselmann
    self.assertEqual(set(data), set(rapi.rlib2.ALL_FEATURES))
87 105f0d47 Michael Hanselmann
88 105f0d47 Michael Hanselmann
  def testPutInstances(self):
89 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_PUT, "/2/instances", "", None)
90 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpNotImplemented.code)
91 105f0d47 Michael Hanselmann
    self.assertTrue(data["message"].startswith("Method PUT is unsupported"))
92 105f0d47 Michael Hanselmann
93 105f0d47 Michael Hanselmann
  def testPostInstancesNoAuth(self):
94 105f0d47 Michael Hanselmann
    (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", "", None)
95 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpUnauthorized.code)
96 105f0d47 Michael Hanselmann
97 105f0d47 Michael Hanselmann
  def testRequestWithUnsupportedMediaType(self):
98 105f0d47 Michael Hanselmann
    for fn in [lambda s: s, lambda s: s.upper(), lambda s: s.title()]:
99 105f0d47 Michael Hanselmann
      headers = rapi.testutils._FormatHeaders([
100 105f0d47 Michael Hanselmann
        "%s: %s" % (http.HTTP_CONTENT_TYPE, fn("un/supported/media/type")),
101 105f0d47 Michael Hanselmann
        ])
102 105f0d47 Michael Hanselmann
      (code, _, data) = self._Test(http.HTTP_GET, "/", headers, "body")
103 105f0d47 Michael Hanselmann
      self.assertEqual(code, http.HttpUnsupportedMediaType.code)
104 105f0d47 Michael Hanselmann
      self.assertEqual(data["message"], "Unsupported Media Type")
105 105f0d47 Michael Hanselmann
106 105f0d47 Michael Hanselmann
  def testRequestWithInvalidJsonData(self):
107 105f0d47 Michael Hanselmann
    body = "_this/is/no'valid.json"
108 105f0d47 Michael Hanselmann
    self.assertRaises(Exception, serializer.LoadJson, body)
109 105f0d47 Michael Hanselmann
110 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
111 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
112 105f0d47 Michael Hanselmann
      ])
113 105f0d47 Michael Hanselmann
114 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_GET, "/", headers, body)
115 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpBadRequest.code)
116 105f0d47 Michael Hanselmann
    self.assertEqual(data["message"], "Unable to parse JSON data")
117 105f0d47 Michael Hanselmann
118 105f0d47 Michael Hanselmann
  def testUnsupportedAuthScheme(self):
119 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
120 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_AUTHORIZATION, "Unsupported scheme"),
121 105f0d47 Michael Hanselmann
      ])
122 105f0d47 Michael Hanselmann
123 105f0d47 Michael Hanselmann
    (code, _, _) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
124 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpUnauthorized.code)
125 105f0d47 Michael Hanselmann
126 105f0d47 Michael Hanselmann
  def testIncompleteBasicAuth(self):
127 105f0d47 Michael Hanselmann
    headers = rapi.testutils._FormatHeaders([
128 105f0d47 Michael Hanselmann
      "%s: Basic" % http.HTTP_AUTHORIZATION,
129 105f0d47 Michael Hanselmann
      ])
130 105f0d47 Michael Hanselmann
131 105f0d47 Michael Hanselmann
    (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
132 105f0d47 Michael Hanselmann
    self.assertEqual(code, http.HttpBadRequest.code)
133 105f0d47 Michael Hanselmann
    self.assertEqual(data["message"],
134 105f0d47 Michael Hanselmann
                     "Basic authentication requires credentials")
135 105f0d47 Michael Hanselmann
136 105f0d47 Michael Hanselmann
  def testInvalidBasicAuth(self):
137 105f0d47 Michael Hanselmann
    for auth in ["!invalid=base!64.", base64.b64encode(" "),
138 105f0d47 Michael Hanselmann
                 base64.b64encode("missingcolonchar")]:
139 105f0d47 Michael Hanselmann
      headers = rapi.testutils._FormatHeaders([
140 105f0d47 Michael Hanselmann
        "%s: Basic %s" % (http.HTTP_AUTHORIZATION, auth),
141 105f0d47 Michael Hanselmann
        ])
142 105f0d47 Michael Hanselmann
143 105f0d47 Michael Hanselmann
      (code, _, data) = self._Test(http.HTTP_POST, "/2/instances", headers, "")
144 105f0d47 Michael Hanselmann
      self.assertEqual(code, http.HttpUnauthorized.code)
145 105f0d47 Michael Hanselmann
146 105f0d47 Michael Hanselmann
  @staticmethod
147 105f0d47 Michael Hanselmann
  def _MakeAuthHeaders(username, password, correct_password):
148 105f0d47 Michael Hanselmann
    if correct_password:
149 105f0d47 Michael Hanselmann
      pw = password
150 105f0d47 Michael Hanselmann
    else:
151 105f0d47 Michael Hanselmann
      pw = "wrongpass"
152 105f0d47 Michael Hanselmann
153 105f0d47 Michael Hanselmann
    return rapi.testutils._FormatHeaders([
154 105f0d47 Michael Hanselmann
      "%s: Basic %s" % (http.HTTP_AUTHORIZATION,
155 105f0d47 Michael Hanselmann
                        base64.b64encode("%s:%s" % (username, pw))),
156 105f0d47 Michael Hanselmann
      "%s: %s" % (http.HTTP_CONTENT_TYPE, http.HTTP_APP_JSON),
157 105f0d47 Michael Hanselmann
      ])
158 105f0d47 Michael Hanselmann
159 105f0d47 Michael Hanselmann
  def testQueryAuth(self):
160 105f0d47 Michael Hanselmann
    username = "admin"
161 105f0d47 Michael Hanselmann
    password = "2046920054"
162 105f0d47 Michael Hanselmann
163 105f0d47 Michael Hanselmann
    header_fn = compat.partial(self._MakeAuthHeaders, username, password)
164 105f0d47 Michael Hanselmann
165 105f0d47 Michael Hanselmann
    def _LookupUserNoWrite(name):
166 105f0d47 Michael Hanselmann
      if name == username:
167 105f0d47 Michael Hanselmann
        return http.auth.PasswordFileUser(name, password, [])
168 105f0d47 Michael Hanselmann
      else:
169 105f0d47 Michael Hanselmann
        return None
170 105f0d47 Michael Hanselmann
171 5e12acfe Michael Hanselmann
    for access in [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]:
172 5e12acfe Michael Hanselmann
      def _LookupUserWithWrite(name):
173 5e12acfe Michael Hanselmann
        if name == username:
174 5e12acfe Michael Hanselmann
          return http.auth.PasswordFileUser(name, password, [
175 5e12acfe Michael Hanselmann
            access,
176 5e12acfe Michael Hanselmann
            ])
177 105f0d47 Michael Hanselmann
        else:
178 5e12acfe Michael Hanselmann
          return None
179 5e12acfe Michael Hanselmann
180 5e12acfe Michael Hanselmann
      for qr in constants.QR_VIA_RAPI:
181 5e12acfe Michael Hanselmann
        # The /2/query resource has somewhat special rules for authentication as
182 5e12acfe Michael Hanselmann
        # it can be used to retrieve critical information
183 5e12acfe Michael Hanselmann
        path = "/2/query/%s" % qr
184 5e12acfe Michael Hanselmann
185 5e12acfe Michael Hanselmann
        for method in rapi.baserlib._SUPPORTED_METHODS:
186 5e12acfe Michael Hanselmann
          # No authorization
187 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, "", "")
188 5e12acfe Michael Hanselmann
189 5e12acfe Michael Hanselmann
          if method in (http.HTTP_DELETE, http.HTTP_POST):
190 5e12acfe Michael Hanselmann
            self.assertEqual(code, http.HttpNotImplemented.code)
191 5e12acfe Michael Hanselmann
            continue
192 5e12acfe Michael Hanselmann
193 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
194 5e12acfe Michael Hanselmann
195 5e12acfe Michael Hanselmann
          # Incorrect user
196 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(True), "",
197 5e12acfe Michael Hanselmann
                                    user_fn=self._LookupWrongUser)
198 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
199 5e12acfe Michael Hanselmann
200 5e12acfe Michael Hanselmann
          # User has no write access, but the password is correct
201 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(True), "",
202 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserNoWrite)
203 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpForbidden.code)
204 5e12acfe Michael Hanselmann
205 5e12acfe Michael Hanselmann
          # Wrong password and no write access
206 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(False), "",
207 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserNoWrite)
208 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
209 5e12acfe Michael Hanselmann
210 5e12acfe Michael Hanselmann
          # Wrong password with write access
211 5e12acfe Michael Hanselmann
          (code, _, _) = self._Test(method, path, header_fn(False), "",
212 5e12acfe Michael Hanselmann
                                    user_fn=_LookupUserWithWrite)
213 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HttpUnauthorized.code)
214 5e12acfe Michael Hanselmann
215 5e12acfe Michael Hanselmann
          # Prepare request information
216 5e12acfe Michael Hanselmann
          if method == http.HTTP_PUT:
217 5e12acfe Michael Hanselmann
            reqpath = path
218 5e12acfe Michael Hanselmann
            body = serializer.DumpJson({
219 5e12acfe Michael Hanselmann
              "fields": ["name"],
220 5e12acfe Michael Hanselmann
              })
221 5e12acfe Michael Hanselmann
          elif method == http.HTTP_GET:
222 5e12acfe Michael Hanselmann
            reqpath = "%s?fields=name" % path
223 5e12acfe Michael Hanselmann
            body = ""
224 5e12acfe Michael Hanselmann
          else:
225 5e12acfe Michael Hanselmann
            self.fail("Unknown method '%s'" % method)
226 5e12acfe Michael Hanselmann
227 5e12acfe Michael Hanselmann
          # User has write access, password is correct
228 5e12acfe Michael Hanselmann
          (code, _, data) = self._Test(method, reqpath, header_fn(True), body,
229 5e12acfe Michael Hanselmann
                                       user_fn=_LookupUserWithWrite,
230 5e12acfe Michael Hanselmann
                                       luxi_client=_FakeLuxiClientForQuery)
231 5e12acfe Michael Hanselmann
          self.assertEqual(code, http.HTTP_OK)
232 5e12acfe Michael Hanselmann
          self.assertTrue(objects.QueryResponse.FromDict(data))
233 5e12acfe Michael Hanselmann
234 5e12acfe Michael Hanselmann
  def testConsole(self):
235 5e12acfe Michael Hanselmann
    path = "/2/instances/inst1.example.com/console"
236 5e12acfe Michael Hanselmann
237 5e12acfe Michael Hanselmann
    for method in rapi.baserlib._SUPPORTED_METHODS:
238 5e12acfe Michael Hanselmann
      # No authorization
239 5e12acfe Michael Hanselmann
      (code, _, _) = self._Test(method, path, "", "")
240 5e12acfe Michael Hanselmann
241 5e12acfe Michael Hanselmann
      if method == http.HTTP_GET:
242 5e12acfe Michael Hanselmann
        self.assertEqual(code, http.HttpUnauthorized.code)
243 5e12acfe Michael Hanselmann
      else:
244 5e12acfe Michael Hanselmann
        self.assertEqual(code, http.HttpNotImplemented.code)
245 105f0d47 Michael Hanselmann
246 105f0d47 Michael Hanselmann
247 105f0d47 Michael Hanselmann
class _FakeLuxiClientForQuery:
248 105f0d47 Michael Hanselmann
  def __init__(self, *args, **kwargs):
249 105f0d47 Michael Hanselmann
    pass
250 105f0d47 Michael Hanselmann
251 105f0d47 Michael Hanselmann
  def Query(self, *args):
252 105f0d47 Michael Hanselmann
    return objects.QueryResponse(fields=[])
253 105f0d47 Michael Hanselmann
254 105f0d47 Michael Hanselmann
255 105f0d47 Michael Hanselmann
if __name__ == "__main__":
256 105f0d47 Michael Hanselmann
  testutils.GanetiTestProgram()