3 # Copyright 2016 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
20 from .lib
import quickproxy
21 import rift
.tasklets
.tornado
24 class GlanceConfig(object):
25 DEFAULT_HOST
= "127.0.0.1"
27 DEFAULT_TOKEN
= "test"
29 def __init__(self
, host
=DEFAULT_HOST
, port
=DEFAULT_PORT
, token
=DEFAULT_TOKEN
):
35 class GlanceImageCreateRequest(object):
36 def __init__(self
, name
, size
, checksum
, disk_format
, container_format
):
39 self
.checksum
= checksum
40 self
.disk_format
= disk_format
41 self
.container_format
= container_format
44 return "{}({})".format(
45 self
.__class
__.__name
__,
49 checksum
=self
.checksum
,
54 def from_header_dict(cls
, header_dict
):
56 curl -i -X POST -H 'x-image-meta-container_format: bare' -H
57 'Transfer-Encoding: chunked' -H 'User-Agent: python-glanceclient' -H
58 'x-image-meta-size: 13167616' -H 'x-image-meta-is_public: False' -H
59 'X-Auth-Token: test' -H 'Content-Type: application/octet-stream' -H
60 'x-image-meta-checksum: 64d7c1cd2b6f60c92c14662941cb7913' -H
61 'x-image-meta-disk_format: raw' -H 'x-image-meta-name:
62 cirros-0.3.2-x86_64-disk.img'
65 name
= header_dict
["x-image-meta-name"]
67 size
= int(header_dict
["x-image-meta-size"])
72 checksum
= header_dict
["x-image-meta-checksum"]
76 disk_format
= header_dict
["x-image-meta-disk_format"]
77 container_format
= header_dict
["x-image-meta-container_format"]
79 return cls(name
=name
, size
=size
, checksum
=checksum
,
80 disk_format
=disk_format
, container_format
=container_format
)
83 class GlanceImageCreateResponse(object):
84 def __init__(self
, id, name
, status
, size
, checksum
):
89 self
.checksum
= checksum
92 return "{}({})".format(
93 self
.__class
__.__name
__,
98 checksum
=self
.checksum
,
103 def from_response_body(cls
, response_body
):
105 {"image": {"status": "active", "deleted": false, "container_format":
106 "bare", "min_ram": 0, "updated_at": "2016-06-24T14:41:38.598199",
107 "owner": null, "min_disk": 0, "is_public": false, "deleted_at": null,
108 "id": "5903cb2d-53db-4343-b055-586475a077f5", "size": 13167616, "name":
109 "cirros-0.3.2-x86_64-disk.img", "checksum":
110 "64d7c1cd2b6f60c92c14662941cb7913", "created_at":
111 "2016-06-24T14:41:38.207356", "disk_format": "raw",
112 "properties": {}, "protected": false}}
115 response_dict
= json
.loads(response_body
.decode())
116 image
= response_dict
["image"]
120 status
= image
["status"]
122 checksum
= image
["checksum"]
125 id=id, name
=name
, status
=status
,
126 size
=size
, checksum
=checksum
130 class GlanceHTTPMockProxy(object):
131 def __init__(self
, log
, loop
, on_http_request
, on_http_response
):
134 self
._on
_http
_request
= on_http_request
135 self
._on
_http
_response
= on_http_response
144 class QuickProxyServer(object):
145 """ This class implements a HTTP Proxy server
147 DEFAULT_PROXY_PORT
= 9999
150 def __init__(self
, log
, loop
, proxy_port
=DEFAULT_PROXY_PORT
):
153 self
._proxy
_port
= proxy_port
155 self
._proxy
_server
= None
158 return "{}(port={})".format(self
.__class
__.__name
__, self
._proxy
_port
)
160 def start(self
, on_http_request
, on_http_response
):
161 """ Start the proxy server
164 on_http_request - A callback when a http request is initiated
165 on_http_response - A callback when a http response is initiated
168 self
._log
.debug("Starting %s", self
)
169 io_loop
= rift
.tasklets
.tornado
.TaskletAsyncIOLoop(
170 asyncio_loop
=self
._loop
173 self
._proxy
_server
= quickproxy
.run_proxy(
174 port
=self
._proxy
_port
,
175 req_callback
=on_http_request
,
176 resp_callback
=on_http_response
,
178 debug_level
=QuickProxyServer
.DEBUG_LEVEL
182 """ Stop the proxy server """
183 if self
._proxy
_server
is None:
184 self
._log
.warning("%s already stopped")
187 self
._log
.debug("Stopping %s", self
)
188 self
._proxy
_server
.stop()
189 self
._proxy
_server
= None
192 class GlanceHTTPProxyServer(object):
193 """ This class implements a HTTP Proxy server
195 Proxying requests to glance has the following high-level advantages:
196 - Allows us to intercept HTTP requests and responses to hook in functionality
197 - Allows us to configure the glance catalog server and keep the endpoint the same
200 DEFAULT_GLANCE_CONFIG
= GlanceConfig()
202 def __init__(self
, log
, loop
,
204 glance_config
=DEFAULT_GLANCE_CONFIG
,
205 on_create_image_request
=None,
206 on_create_image_response
=None,
211 self
._http
_proxy
_server
= http_proxy_server
212 self
._glance
_config
= glance_config
214 self
._on
_create
_image
_request
= on_create_image_request
215 self
._on
_create
_image
_response
= on_create_image_response
217 def _handle_create_image_request(self
, request
):
218 image_request
= GlanceImageCreateRequest
.from_header_dict(request
.headers
)
219 self
._log
.debug("Parsed image request: %s", image_request
)
220 if self
._on
_create
_image
_request
is not None:
221 self
._on
_create
_image
_request
(image_request
)
223 # Store the GlanceImageCreateRequest in the request context so it
224 # is available in the response
225 request
.context
["image_request"] = image_request
229 def _handle_create_image_response(self
, response
):
230 image_request
= response
.context
["image_request"]
232 self
._log
.debug("Got response body: %s", response
.body
)
233 image_response
= GlanceImageCreateResponse
.from_response_body(response
.body
)
234 self
._log
.debug("Parsed image response: %s", image_response
)
235 if self
._on
_create
_image
_response
is not None:
236 response
= self
._on
_create
_image
_response
(image_response
, image_request
)
241 """ Start the glance proxy server """
242 def request_callback(request
):
243 # Redirect the request to the actual glance server
244 self
._log
.debug("Proxying request to glance (path: %s, method: %s)",
245 request
.path
, request
.method
)
247 # Save the path and method to detect whether the response for
248 # for a create_image request
249 request
.context
["path"] = request
.path
250 request
.context
["method"] = request
.method
252 if request
.path
.endswith("images") and request
.method
== "POST":
253 request
= self
._handle
_create
_image
_request
(request
)
255 # Redirect the request to the actual glance server
256 request
.host
= self
._glance
_config
.host
257 request
.port
= self
._glance
_config
.port
261 def response_callback(response
):
262 self
._log
.debug("Got glance request response: %s", response
)
264 if response
.context
["path"].endswith("images") and response
.context
["method"] == "POST":
265 response
= self
._handle
_create
_image
_response
(response
)
269 self
._http
_proxy
_server
.start(
270 on_http_request
=request_callback
,
271 on_http_response
=response_callback
275 """ Stop the glance proxy server """
276 self
._http
_proxy
_server
.stop()