Revision f2f88abf

b/lib/rapi/client.py
98 98
  USER_AGENT = "Ganeti RAPI Client"
99 99

  
100 100
  def __init__(self, master_hostname, port=5080, username=None, password=None,
101
               ssl_cert=None):
101
               ssl_cert_file=None):
102 102
    """Constructor.
103 103

  
104 104
    @type master_hostname: str
......
109 109
    @param username: the username to connect with
110 110
    @type password: str
111 111
    @param password: the password to connect with
112
    @type ssl_cert: str or None
113
    @param ssl_cert: the expected SSL certificate. if None, SSL certificate
114
        will not be verified
112
    @type ssl_cert_file: str or None
113
    @param ssl_cert_file: path to the expected SSL certificate. if None, SSL
114
        certificate will not be verified
115 115

  
116 116
    """
117 117
    self._master_hostname = master_hostname
118 118
    self._port = port
119
    if ssl_cert:
120
      _VerifyCertificate(self._master_hostname, self._port, ssl_cert)
121 119

  
120
    self._version = None
122 121
    self._http = httplib2.Http()
122

  
123
    # Older versions of httplib2 don't support the connection_type argument
124
    # to request(), so we have to manually specify the connection object in the
125
    # internal dict.
126
    base_url = self._MakeUrl("/", prepend_version=False)
127
    scheme, authority, _, _, _ = httplib2.parse_uri(base_url)
128
    conn_key = "%s:%s" % (scheme, authority)
129
    self._http.connections[conn_key] = \
130
      HTTPSConnectionOpenSSL(master_hostname, port, cert_file=ssl_cert_file)
131

  
123 132
    self._headers = {
124 133
        "Accept": "text/plain",
125 134
        "Content-type": "application/x-www-form-urlencoded",
126 135
        "User-Agent": self.USER_AGENT}
127
    self._version = None
128
    if username and password:
136

  
137
    if username is not None and password is not None:
129 138
      self._http.add_credentials(username, password)
130 139

  
131 140
  def _MakeUrl(self, path, query=None, prepend_version=True):
......
144 153

  
145 154
    """
146 155
    if prepend_version:
147
      if not self._version:
148
        self._GetVersionInternal()
149
      path = "/%d%s" % (self._version, path)
156
      path = "/%d%s" % (self.GetVersion(), path)
150 157

  
151 158
    return "https://%(host)s:%(port)d%(path)s?%(query)s" % {
152 159
        "host": self._master_hostname,
......
176 183
    @rtype: str
177 184
    @return: JSON-Decoded response
178 185

  
186
    @raises CertificateError: If an invalid SSL certificate is found
179 187
    @raises GanetiApiError: If an invalid response is returned
180 188

  
181 189
    """
182 190
    if content:
183
      simplejson.JSONEncoder(sort_keys=True).encode(content)
191
      content = simplejson.JSONEncoder(sort_keys=True).encode(content)
184 192

  
185 193
    url = self._MakeUrl(path, query, prepend_version)
186
    resp_headers, resp_content = self._http.request(
187
        url, method, body=content, headers=self._headers)
194
    try:
195
      resp_headers, resp_content = self._http.request(url, method,
196
          body=content, headers=self._headers)
197
    except (crypto.Error, SSL.Error):
198
      raise CertificateError("Invalid SSL certificate.")
188 199

  
189 200
    if resp_content:
190 201
      resp_content = simplejson.loads(resp_content)
......
201 212

  
202 213
    return resp_content
203 214

  
204
  def _GetVersionInternal(self):
205
    """Gets the Remote API version running on the cluster.
206

  
207
    @rtype: int
208
    @return: Ganeti version
209

  
210
    """
211
    self._version = self._SendRequest(HTTP_GET, "/version",
212
                                      prepend_version=False)
213
    return self._version
214

  
215 215
  def GetVersion(self):
216 216
    """Gets the Remote API version running on the cluster.
217 217

  
218 218
    @rtype: int
219
    @return: Ganeti version
219
    @return: Ganeti Remote API version
220 220

  
221 221
    """
222
    if not self._version:
223
      self._GetVersionInternal()
222
    if self._version is None:
223
      self._version = self._SendRequest(HTTP_GET, "/version",
224
                                        prepend_version=False)
224 225
    return self._version
225 226

  
226 227
  def GetOperatingSystems(self):
......
841 842
    ssl = SSL.Connection(ctx, sock)
842 843
    ssl.connect((self.host, self.port))
843 844
    self.sock = httplib.FakeSocket(sock, ssl)
844

  
845

  
846
def _VerifyCertificate(hostname, port, cert_file):
847
  """Verifies the SSL certificate for the given host/port.
848

  
849
  @type hostname: str
850
  @param hostname: the ganeti cluster master whose certificate to verify
851
  @type port: int
852
  @param port: the port on which the RAPI is running
853
  @type cert_file: str
854
  @param cert_file: filename of the expected SSL certificate
855

  
856
  @raises CertificateError: If an invalid SSL certificate is found
857

  
858
  """
859
  https = HTTPSConnectionOpenSSL(hostname, port, cert_file=cert_file)
860
  try:
861
    try:
862
      https.request(HTTP_GET, "/version")
863
    except (crypto.Error, SSL.Error):
864
      raise CertificateError("Invalid SSL certificate.")
865
  finally:
866
    https.close()
b/test/ganeti.rapi.client_unittest.py
145 145
  """
146 146

  
147 147
  def setUp(self):
148
    # Monkey-patch a fake VerifyCertificate function
149
    self._verify_certificate = client._VerifyCertificate
150
    client._VerifyCertificate = lambda x, y, z: True
151

  
152 148
    self.rapi = RapiMock()
153 149
    self.http = HttpMock(self.rapi)
154 150
    self.client = client.GanetiRapiClient('master.foo.com')
......
156 152
    # Hard-code the version for easier testing.
157 153
    self.client._version = 2
158 154

  
159
  def tearDown(self):
160
    # Un-do the monkey-patch
161
    client._VerifyCertificate = self._verify_certificate
162

  
163 155
  def assertHandler(self, handler_cls):
164 156
    self.failUnless(isinstance(self.rapi.GetLastHandler(), handler_cls))
165 157

  
......
392 384
    self.assertHandler(rlib2.R_2_nodes_name_role)
393 385
    self.assertItems(["node-foo"])
394 386
    self.assertQuery("force", ["True"])
395
    self.assertEqual("master-candidate", self.http.last_request_body)
387
    self.assertEqual("\"master-candidate\"", self.http.last_request_body)
396 388

  
397 389
    self.assertRaises(client.InvalidNodeRole,
398 390
                      self.client.SetNodeRole, "node-bar", "fake-role")

Also available in: Unified diff