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
,
183 """ Stop the proxy server """
184 if self
._proxy
_server
is None:
185 self
._log
.warning("%s already stopped")
188 self
._log
.debug("Stopping %s", self
)
189 self
._proxy
_server
.stop()
190 self
._proxy
_server
= None
193 class GlanceHTTPProxyServer(object):
194 """ This class implements a HTTP Proxy server
196 Proxying requests to glance has the following high-level advantages:
197 - Allows us to intercept HTTP requests and responses to hook in functionality
198 - Allows us to configure the glance catalog server and keep the endpoint the same
201 DEFAULT_GLANCE_CONFIG
= GlanceConfig()
203 def __init__(self
, log
, loop
,
205 glance_config
=DEFAULT_GLANCE_CONFIG
,
206 on_create_image_request
=None,
207 on_create_image_response
=None,
212 self
._http
_proxy
_server
= http_proxy_server
213 self
._glance
_config
= glance_config
215 self
._on
_create
_image
_request
= on_create_image_request
216 self
._on
_create
_image
_response
= on_create_image_response
218 def _handle_create_image_request(self
, request
):
219 image_request
= GlanceImageCreateRequest
.from_header_dict(request
.headers
)
220 self
._log
.debug("Parsed image request: %s", image_request
)
221 if self
._on
_create
_image
_request
is not None:
222 self
._on
_create
_image
_request
(image_request
)
224 # Store the GlanceImageCreateRequest in the request context so it
225 # is available in the response
226 request
.context
["image_request"] = image_request
230 def _handle_create_image_response(self
, response
):
231 image_request
= response
.context
["image_request"]
233 self
._log
.debug("Got response body: %s", response
.body
)
234 image_response
= GlanceImageCreateResponse
.from_response_body(response
.body
)
235 self
._log
.debug("Parsed image response: %s", image_response
)
236 if self
._on
_create
_image
_response
is not None:
237 response
= self
._on
_create
_image
_response
(image_response
, image_request
)
242 """ Start the glance proxy server """
243 def request_callback(request
):
244 # Redirect the request to the actual glance server
245 self
._log
.debug("Proxying request to glance (path: %s, method: %s)",
246 request
.path
, request
.method
)
248 # Save the path and method to detect whether the response for
249 # for a create_image request
250 request
.context
["path"] = request
.path
251 request
.context
["method"] = request
.method
253 if request
.path
.endswith("images") and request
.method
== "POST":
254 request
= self
._handle
_create
_image
_request
(request
)
256 # Redirect the request to the actual glance server
257 request
.host
= self
._glance
_config
.host
258 request
.port
= self
._glance
_config
.port
262 def response_callback(response
):
263 self
._log
.debug("Got glance request response: %s", response
)
265 if response
.context
["path"].endswith("images") and response
.context
["method"] == "POST":
266 response
= self
._handle
_create
_image
_response
(response
)
270 self
._http
_proxy
_server
.start(
271 on_http_request
=request_callback
,
272 on_http_response
=response_callback
276 """ Stop the glance proxy server """
277 self
._http
_proxy
_server
.stop()