9b3972e54fac7a81b43e16545d56a7bb07e80b3c
[osm/SO.git] / rwlaunchpad / plugins / rwimagemgr / rift / tasklets / rwimagemgr / glance_proxy_server.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16 #
17
18 import json
19
20 from .lib import quickproxy
21 import rift.tasklets.tornado
22
23
24 class GlanceConfig(object):
25 DEFAULT_HOST = "127.0.0.1"
26 DEFAULT_PORT = 9292
27 DEFAULT_TOKEN = "test"
28
29 def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, token=DEFAULT_TOKEN):
30 self.host = host
31 self.port = port
32 self.token = token
33
34
35 class GlanceImageCreateRequest(object):
36 def __init__(self, name, size, checksum, disk_format, container_format):
37 self.name = name
38 self.size = size
39 self.checksum = checksum
40 self.disk_format = disk_format
41 self.container_format = container_format
42
43 def __repr__(self):
44 return "{}({})".format(
45 self.__class__.__name__,
46 dict(
47 name=self.name,
48 size=self.size,
49 checksum=self.checksum,
50 )
51 )
52
53 @classmethod
54 def from_header_dict(cls, header_dict):
55 """
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'
63 """
64
65 name = header_dict["x-image-meta-name"]
66 try:
67 size = int(header_dict["x-image-meta-size"])
68 except KeyError:
69 size = None
70
71 try:
72 checksum = header_dict["x-image-meta-checksum"]
73 except KeyError:
74 checksum = None
75
76 disk_format = header_dict["x-image-meta-disk_format"]
77 container_format = header_dict["x-image-meta-container_format"]
78
79 return cls(name=name, size=size, checksum=checksum,
80 disk_format=disk_format, container_format=container_format)
81
82
83 class GlanceImageCreateResponse(object):
84 def __init__(self, id, name, status, size, checksum):
85 self.id = id
86 self.name = name
87 self.status = status
88 self.size = size
89 self.checksum = checksum
90
91 def __repr__(self):
92 return "{}({})".format(
93 self.__class__.__name__,
94 dict(
95 id=self.id,
96 name=self.name,
97 status=self.status,
98 checksum=self.checksum,
99 )
100 )
101
102 @classmethod
103 def from_response_body(cls, response_body):
104 """
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}}
113 """
114
115 response_dict = json.loads(response_body.decode())
116 image = response_dict["image"]
117
118 id = image["id"]
119 name = image["name"]
120 status = image["status"]
121 size = image["size"]
122 checksum = image["checksum"]
123
124 return cls(
125 id=id, name=name, status=status,
126 size=size, checksum=checksum
127 )
128
129
130 class GlanceHTTPMockProxy(object):
131 def __init__(self, log, loop, on_http_request, on_http_response):
132 self._log = log
133 self._loop = loop
134 self._on_http_request = on_http_request
135 self._on_http_response = on_http_response
136
137 def start(self):
138 pass
139
140 def stop(self):
141 pass
142
143
144 class QuickProxyServer(object):
145 """ This class implements a HTTP Proxy server
146 """
147 DEFAULT_PROXY_PORT = 9999
148 DEBUG_LEVEL = 0
149
150 def __init__(self, log, loop, proxy_port=DEFAULT_PROXY_PORT):
151 self._log = log
152 self._loop = loop
153 self._proxy_port = proxy_port
154
155 self._proxy_server = None
156
157 def __repr__(self):
158 return "{}(port={})".format(self.__class__.__name__, self._proxy_port)
159
160 def start(self, on_http_request, on_http_response):
161 """ Start the proxy server
162
163 Arguments:
164 on_http_request - A callback when a http request is initiated
165 on_http_response - A callback when a http response is initiated
166
167 """
168 self._log.debug("Starting %s", self)
169 io_loop = rift.tasklets.tornado.TaskletAsyncIOLoop(
170 asyncio_loop=self._loop
171 )
172
173 self._proxy_server = quickproxy.run_proxy(
174 port=self._proxy_port,
175 req_callback=on_http_request,
176 resp_callback=on_http_response,
177 io_loop=io_loop,
178 debug_level=QuickProxyServer.DEBUG_LEVEL
179 )
180
181 def stop(self):
182 """ Stop the proxy server """
183 if self._proxy_server is None:
184 self._log.warning("%s already stopped")
185 return
186
187 self._log.debug("Stopping %s", self)
188 self._proxy_server.stop()
189 self._proxy_server = None
190
191
192 class GlanceHTTPProxyServer(object):
193 """ This class implements a HTTP Proxy server
194
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
198 """
199
200 DEFAULT_GLANCE_CONFIG = GlanceConfig()
201
202 def __init__(self, log, loop,
203 http_proxy_server,
204 glance_config=DEFAULT_GLANCE_CONFIG,
205 on_create_image_request=None,
206 on_create_image_response=None,
207 ):
208
209 self._log = log
210 self._loop = loop
211 self._http_proxy_server = http_proxy_server
212 self._glance_config = glance_config
213
214 self._on_create_image_request = on_create_image_request
215 self._on_create_image_response = on_create_image_response
216
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)
222
223 # Store the GlanceImageCreateRequest in the request context so it
224 # is available in the response
225 request.context["image_request"] = image_request
226
227 return request
228
229 def _handle_create_image_response(self, response):
230 image_request = response.context["image_request"]
231
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)
237
238 return response
239
240 def start(self):
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)
246
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
251
252 if request.path.endswith("images") and request.method == "POST":
253 request = self._handle_create_image_request(request)
254
255 # Redirect the request to the actual glance server
256 request.host = self._glance_config.host
257 request.port = self._glance_config.port
258
259 return request
260
261 def response_callback(response):
262 self._log.debug("Got glance request response: %s", response)
263
264 if response.context["path"].endswith("images") and response.context["method"] == "POST":
265 response = self._handle_create_image_response(response)
266
267 return response
268
269 self._http_proxy_server.start(
270 on_http_request=request_callback,
271 on_http_response=response_callback
272 )
273
274 def stop(self):
275 """ Stop the glance proxy server """
276 self._http_proxy_server.stop()