Fixes to server
[ta/remote-installer.git] / src / remoteinstaller / server / server.py
1 # Copyright 2019 Nokia
2
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import sys
16 import argparse
17 from configparser import ConfigParser
18 import logging
19 from logging.handlers import RotatingFileHandler
20 import os
21 from threading import Thread
22 import time
23 import json
24 import urllib
25 import urlparse
26 import uuid as uuid_module
27 import ssl
28
29 from wsgiref.simple_server import make_server
30 import routes
31
32 from remoteinstaller.installer.install import Installer
33 from remoteinstaller.installer.install import InstallException
34
35
36 class LoggingSSLSocket(ssl.SSLSocket):
37     def accept(self, *args, **kwargs):
38         try:
39             result = super(LoggingSSLSocket, self).accept(*args, **kwargs)
40         except Exception as ex:
41             logging.warning('SSLSocket.accept raised exception: %s', str(ex))
42             raise
43         return result
44
45
46 class InstallationWorker(Thread):
47     def __init__(self, server, uuid, admin_passwd, yaml, logdir, args=None):
48         super(InstallationWorker, self).__init__(name=uuid)
49         self._server = server
50         self._uuid = uuid
51         self._admin_passwd = admin_passwd
52         self._yaml = yaml
53         self._logdir = logdir
54         self._args = args
55
56     def run(self):
57         installer = Installer(self._server, self._uuid, self._yaml, self._logdir, self._args)
58         access_info = installer.get_access_info()
59
60         if self._args:
61             try:
62                 installer.install()
63                 logging.info('Installation triggered for %s', self._uuid)
64             except InstallException as ex:
65                 logging.warning('Installation triggering failed for %s: %s', self._uuid, str(ex))
66                 self._server.set_state(self._uuid, 'failed', str(ex))
67                 return
68
69         installation_finished = False
70         while not installation_finished:
71             state = self._server.get_state(self._uuid)
72             if not state['status'] == 'ongoing':
73                 installation_finished = True
74             else:
75                 logging.info('Installation of %s still ongoing (%s%%): %s',
76                              self._uuid,
77                              state['percentage'],
78                              state['description'])
79                 time.sleep(10)
80
81         logging.info('Installation finished for %s: %s', self._uuid, state)
82         logging.info('Login details for installation %s: %s', self._uuid, str(access_info))
83
84         logging.info('Getting logs for installation %s...', self._uuid)
85         installer.get_logs(self._admin_passwd)
86         logging.info('Logs retrieved for %s', self._uuid)
87
88 class Server(object):
89     DEFAULT_PATH = '/opt/remoteinstaller'
90     USER_CONFIG_PATH = 'user-configs'
91     ISO_PATH = 'images'
92     CERTIFICATE_PATH = 'certificates'
93     INSTALLATIONS_PATH = 'installations'
94     USER_CONFIG_NAME = 'user_config.yaml'
95     EXTRA_CONFIG_NAME = 'installation.ini'
96
97     def __init__(self,
98                  host,
99                  port,
100                  cert=None,
101                  key=None,
102                  client_cert=None,
103                  client_key=None,
104                  ca_cert=None,
105                  path=None,
106                  http_port=None):
107         self._host = host
108         self._port = port
109         self._http_port = http_port
110
111         self._path = path
112         if not self._path:
113             self._path = Server.DEFAULT_PATH
114
115         self._cert = '{}/{}/{}'.format(self._path, Server.CERTIFICATE_PATH, cert)
116         self._key = '{}/{}/{}'.format(self._path, Server.CERTIFICATE_PATH, key)
117         self._client_cert = '{}/{}/{}'.format(self._path, Server.CERTIFICATE_PATH, client_cert)
118         self._client_key = '{}/{}/{}'.format(self._path, Server.CERTIFICATE_PATH, client_key)
119         self._ca_cert = '{}/{}/{}'.format(self._path, Server.CERTIFICATE_PATH, ca_cert)
120
121         self._ongoing_installations = {}
122         self._load_states()
123
124     def get_server_keys(self):
125         return {'cert': self._cert, 'key': self._key, 'ca_cert': self._ca_cert}
126
127     def _read_admin_passwd(self, cloud_name):
128         with open('{}/{}/{}/admin_passwd'.format(self._path,
129                                                  Server.USER_CONFIG_PATH,
130                                                  cloud_name)) as pwf:
131             admin_passwd = pwf.readline().strip()
132
133         return admin_passwd
134
135     def _get_yaml_path_for_cloud(self, cloud_name):
136         yaml = '{}/{}/{}/{}'.format(self._path,
137                                     Server.USER_CONFIG_PATH,
138                                     cloud_name,
139                                     Server.USER_CONFIG_NAME)
140         if not os.path.isfile(yaml):
141             raise ServerError('YAML file {} not found'.format(yaml))
142
143         return yaml
144
145     def _load_states(self):
146         uuid_list = os.listdir('{}/{}'.format(self._path, Server.INSTALLATIONS_PATH))
147         for uuid in uuid_list:
148             state_file_name = '{}/{}/{}.state'.format(self._path, Server.INSTALLATIONS_PATH, uuid)
149             if os.path.exists(state_file_name):
150                 with open(state_file_name) as sf:
151                     state_json = sf.readline()
152                     self._ongoing_installations[uuid] = json.loads(state_json)
153
154                 if self._ongoing_installations[uuid]['status'] == 'ongoing':
155                     logdir = '{}/{}/{}'.format(self._path, Server.INSTALLATIONS_PATH, uuid)
156                     cloud_name = self._ongoing_installations[uuid]['cloud_name']
157                     admin_passwd = self._read_admin_passwd(cloud_name)
158                     yaml = self._get_yaml_path_for_cloud(cloud_name)
159                     worker = InstallationWorker(self, uuid, admin_passwd, yaml, logdir)
160                     worker.start()
161
162     def _set_state(self, uuid, status, description, percentage=None, cloud_name=None):
163         if not self._ongoing_installations.get(uuid, None):
164             self._ongoing_installations[uuid] = {}
165         self._ongoing_installations[uuid]['status'] = status
166         self._ongoing_installations[uuid]['description'] = description
167         if percentage is not None:
168             self._ongoing_installations[uuid]['percentage'] = percentage
169         if cloud_name:
170             self._ongoing_installations[uuid]['cloud_name'] = cloud_name
171
172         state_file = '{}/{}/{}.state'.format(self._path, Server.INSTALLATIONS_PATH, uuid)
173         with open(state_file, 'w') as sf:
174             sf.write(json.dumps(self._ongoing_installations[uuid]))
175
176     def set_state(self, uuid, status, description, percentage=None):
177         logging.debug('set_state called for %s: status=%s, description=%s, percentage=%s',
178                       uuid, status, description, percentage)
179
180         if not uuid in self._ongoing_installations:
181             raise ServerError('Installation id {} not found'.format(uuid))
182
183         if not status in ['ongoing', 'failed', 'completed']:
184             raise ServerError('Invalid state: {}'.format(status))
185
186         self._set_state(uuid, status, description, percentage)
187
188     def get_state(self, uuid):
189         logging.debug('get_state called for %s', uuid)
190
191         if not uuid in self._ongoing_installations:
192             raise ServerError('Installation id {} not found'.format(uuid))
193
194         return {'status': self._ongoing_installations[uuid]['status'],
195                 'description': self._ongoing_installations[uuid]['description'],
196                 'percentage': self._ongoing_installations[uuid]['percentage']}
197
198     def _read_extra_args(self, cloud_name):
199         extra = {}
200
201         extra_config_filename = '{}/{}/{}/{}'.format(self._path,
202                                     Server.USER_CONFIG_PATH,
203                                     cloud_name,
204                                     Server.EXTRA_CONFIG_NAME)
205
206         if os.path.isfile(extra_config_filename):
207             logging.debug('Read extra installation args from: %s', extra_config_filename)
208             extra_config = ConfigParser()
209             with open(extra_config_filename, 'r') as extra_config_file:
210                 extra_config.readfp(extra_config_file)
211
212             if extra_config.has_section('extra'):
213                 for key, value in extra_config.items('extra'):
214                     extra[key] = value
215
216         return extra
217
218     def start_installation(self, cloud_name, iso, boot_iso):
219         logging.debug('start_installation called with args: (%s, %s, %s)', cloud_name, iso, boot_iso)
220
221         uuid = str(uuid_module.uuid4())[:8]
222
223         args = argparse.Namespace()
224
225         extra_args = self._read_extra_args(cloud_name)
226         vars(args).update(extra_args)
227
228         args.yaml = self._get_yaml_path_for_cloud(cloud_name)
229
230         iso_path = '{}/{}/{}'.format(self._path, Server.ISO_PATH, iso)
231         if not os.path.isfile(iso_path):
232             raise ServerError('ISO file {} not found'.format(iso_path))
233
234         boot_iso_path = '{}/{}/{}'.format(self._path, Server.ISO_PATH, boot_iso)
235         if not os.path.isfile(boot_iso_path):
236             raise ServerError('Provisioning ISO file {} not found'.format(boot_iso_path))
237
238         http_port_part = ''
239         if self._http_port:
240             http_port_part = ':{}'.format(self._http_port)
241
242         args.iso = 'https://{}{}/{}/{}'.format(self._host, http_port_part, Server.ISO_PATH, iso)
243
244         args.logdir = '{}/{}/{}'.format(self._path, Server.INSTALLATIONS_PATH, uuid)
245
246         os.makedirs(args.logdir)
247
248         args.boot_iso = '{}/{}/{}'.format(self._path, Server.ISO_PATH, boot_iso)
249
250         args.tag = uuid
251         args.callback_url = 'https://{}:{}/v1/installations/{}/state'.format(self._host,
252                                                                              self._port,
253                                                                              uuid)
254
255         args.client_cert = self._client_cert
256         args.client_key = self._client_key
257         args.ca_cert = self._ca_cert
258         args.host_ip = self._host
259
260         self._set_state(uuid, 'ongoing', '', 0, cloud_name)
261
262         admin_passwd = self._read_admin_passwd(cloud_name)
263         worker = InstallationWorker(self, uuid, admin_passwd, args.yaml, args.logdir, args)
264         worker.start()
265
266         return uuid
267
268
269 class ServerError(Exception):
270     pass
271
272
273 class HTTPErrors(object):
274     # response for a successful GET, PUT, PATCH, DELETE,
275     # can also be used for POST that does not result in creation.
276     HTTP_OK = 200
277     # response to a POST which results in creation.
278     HTTP_CREATED = 201
279     # response to a successfull request that won't be returning any body like a DELETE request
280     HTTP_NO_CONTENT = 204
281     # used when http caching headers are in play
282     HTTP_NOT_MODIFIED = 304
283     # the request is malformed such as if the body does not parse
284     HTTP_BAD_REQUEST = 400
285     # when no or invalid authentication details are provided.
286     # also useful to trigger an auth popup API is used from a browser
287     HTTP_UNAUTHORIZED_OPERATION = 401
288     # when authentication succeeded but authenticated user doesn't have access to the resource
289     HTTP_FORBIDDEN = 403
290     # when a non-existent resource is requested
291     HTTP_NOT_FOUND = 404
292     # when an http method is being requested that isn't allowed for the authenticated user
293     HTTP_METHOD_NOT_ALLOWED = 405
294     # indicates the resource at this point is no longer available
295     HTTP_GONE = 410
296     # if incorrect content type was provided as part of the request
297     HTTP_UNSUPPORTED_MEDIA_TYPE = 415
298     # used for validation errors
299     HTTP_UNPROCESSABLE_ENTITY = 422
300     # when request is rejected due to rate limiting
301     HTTP_TOO_MANY_REQUESTS = 429
302     # Other errrors
303     HTTP_INTERNAL_ERROR = 500
304
305     @staticmethod
306     def get_ok_status():
307         return '%d OK' % HTTPErrors.HTTP_OK
308
309     @staticmethod
310     def get_object_created_successfully_status():
311         return '%d Created' % HTTPErrors.HTTP_CREATED
312
313     @staticmethod
314     def get_request_not_ok_status():
315         return '%d Bad request' % HTTPErrors.HTTP_BAD_REQUEST
316
317     @staticmethod
318     def get_resource_not_found_status():
319         return '%d Not found' % HTTPErrors.HTTP_NOT_FOUND
320
321     @staticmethod
322     def get_unsupported_content_type_status():
323         return '%d Unsupported content type' % HTTPErrors.HTTP_UNSUPPORTED_MEDIA_TYPE
324
325     @staticmethod
326     def get_validation_error_status():
327         return '%d Validation error' % HTTPErrors.HTTP_UNPROCESSABLE_ENTITY
328
329     @staticmethod
330     def get_internal_error_status():
331         return '%d Internal error' % HTTPErrors.HTTP_INTERNAL_ERROR
332
333
334 class HTTPRPC(object):
335     def __init__(self):
336         self.req_body = ''
337         self.req_filter = ''
338         self.req_params = {}
339         self.req_method = ''
340         self.req_content_type = ''
341         self.req_content_size = 0
342         self.req_path = ''
343
344         self.rep_body = ''
345         self.rep_status = ''
346
347     def __str__(self):
348         return str.format('REQ: body:{body} filter:{filter} '
349                           'params:{params} method:{method} path:{path} '
350                           'content_type:{content_type} content_size:{content_size} '
351                           'REP: body:{rep_body} status:{status}',
352                           body=self.req_body, filter=self.req_filter,
353                           params=str(self.req_params), method=self.req_method, path=self.req_path,
354                           content_type=self.req_content_type, content_size=self.req_content_size,
355                           rep_body=self.rep_body, status=self.rep_status)
356
357 class WSGIHandler(object):
358     def __init__(self, server):
359         logging.debug('WSGIHandler constructor called')
360
361         self.server = server
362
363         self.mapper = routes.Mapper()
364         self.mapper.connect(None, '/apis', action='get_apis')
365         self.mapper.connect(None, '/{api}/installations', action='handle_installations')
366         self.mapper.connect(None, '/{api}/installations/{uuid}/state', action='handle_state')
367
368     def handle_installations(self, rpc):
369         if rpc.req_method == 'POST':
370             self._start_installation(rpc)
371         else:
372             rpc.rep_status = HTTPErrors.get_request_not_ok_status()
373             rpc.rep_status += ', only POST are possible to this resource'
374
375     def handle_state(self, rpc):
376         if rpc.req_method == 'GET':
377             self._get_state(rpc)
378         elif rpc.req_method == 'POST':
379             self._set_state(rpc)
380         else:
381             rpc.rep_status = HTTPErrors.get_request_not_ok_status()
382             rpc.rep_status += ', only GET/POST are possible to this resource'
383
384     def _start_installation(self, rpc):
385         """
386             Request: POST http://<ip:port>/v1/installations
387                 {
388                     'cloud-name': <name of the cloud>,
389                     'iso': <iso image name>,
390                     'provisioning-iso': <boot iso image name>
391                 }
392             Response: http status set correctly
393                 {
394                     'uuid': <operation identifier>
395                 }
396         """
397
398         logging.debug('_start_installation called')
399         try:
400             if not rpc.req_body:
401                 rpc.rep_status = HTTPErrors.get_request_not_ok_status()
402             else:
403                 request = json.loads(rpc.req_body)
404                 cloud_name = request['cloud-name']
405                 iso = request['iso']
406                 boot_iso = request['provisioning-iso']
407
408                 uuid = self.server.start_installation(cloud_name, iso, boot_iso)
409
410                 rpc.rep_status = HTTPErrors.get_ok_status()
411                 reply = {'uuid': uuid}
412                 rpc.rep_body = json.dumps(reply)
413         except KeyError as ex:
414             raise ServerError('Missing request parameter: {}'.format(str(ex)))
415         except Exception as exp:  # pylint: disable=broad-except
416             rpc.rep_status = HTTPErrors.get_internal_error_status()
417             rpc.rep_status += ','
418             rpc.rep_status += str(exp)
419
420     def _get_state(self, rpc):
421         """
422             Request: GET http://<ip:port>/v1/installations/<uuid>/state
423                 {
424                 }
425             Response: http status set correctly
426                 {
427                     'status': <ongoing|completed|failed>,
428                     'description': <description about the progress>,
429                     'percentage': <percentage completed of the installation>
430                 }
431         """
432
433         logging.debug('_get_state called')
434         try:
435             uuid = rpc.req_params['uuid']
436
437             reply = self.server.get_state(uuid)
438
439             rpc.rep_status = HTTPErrors.get_ok_status()
440             rpc.rep_body = json.dumps(reply)
441         except KeyError as ex:
442             raise ServerError('Missing request parameter: {}'.format(str(ex)))
443         except Exception as exp:  # pylint: disable=broad-except
444             rpc.rep_status = HTTPErrors.get_internal_error_status()
445             rpc.rep_status += ','
446             rpc.rep_status += str(exp)
447
448     def _set_state(self, rpc):
449         """
450             Request: POST http://<ip:port>/v1/installations/<uuid>/state
451                 {
452                     'status': <ongoing|completed|failed>,
453                     'description': <description about the progress>,
454                     'percentage': <percentage completed of the installation>
455                 }
456             Response: http status set correctly
457                 {
458                 }
459         """
460
461         logging.debug('_set_state called')
462         try:
463             if not rpc.req_body:
464                 rpc.rep_status = HTTPErrors.get_request_not_ok_status()
465             else:
466                 request = json.loads(rpc.req_body)
467                 uuid = rpc.req_params['uuid']
468                 status = request['status']
469                 description = request['description']
470                 percentage = request.get('percentage', None)
471
472                 self.server.set_state(uuid, status, description, percentage)
473
474                 rpc.rep_status = HTTPErrors.get_ok_status()
475                 reply = {}
476                 rpc.rep_body = json.dumps(reply)
477         except KeyError as ex:
478             rpc.rep_status = HTTPErrors.get_request_not_ok_status()
479             raise ServerError('Missing request parameter: {}'.format(str(ex)))
480         except Exception as exp:  # pylint: disable=broad-except
481             rpc.rep_status = HTTPErrors.get_internal_error_status()
482             rpc.rep_status += ','
483             rpc.rep_status += str(exp)
484
485     def _read_header(self, rpc, environ):
486         rpc.req_method = environ['REQUEST_METHOD']
487         rpc.req_path = environ['PATH_INFO']
488         try:
489             rpc.req_filter = urlparse.parse_qs(urllib.unquote(environ['QUERY_STRING']))
490         except KeyError:
491             rpc.req_filter = {}
492         rpc.req_content_type = environ['CONTENT_TYPE']
493         try:
494             content_len = environ['CONTENT_LENGTH']
495             if not content_len:
496                 rpc.req_content_size = 0
497             else:
498                 rpc.req_content_size = int(content_len)
499         except KeyError:
500             rpc.req_content_size = 0
501
502     def _get_action(self, rpc):
503         # get the action to be done
504         action = ''
505         match_result = self.mapper.match(rpc.req_path)
506         if not match_result:
507             rpc.rep_status = HTTPErrors.get_resource_not_found_status()
508             raise ServerError('URL does not match')
509
510         resultdict = {}
511         if isinstance(match_result, dict):
512             resultdict = match_result
513         else:
514             resultdict = match_result[0]
515
516         try:
517             action = resultdict['action']
518             for key, value in resultdict.iteritems():
519                 if key != 'action':
520                     rpc.req_params[key] = value
521         except KeyError:
522             rpc.rep_status = HTTPErrors.get_internal_error_status()
523             raise ServerError('No action found')
524
525         return action
526
527     def _read_body(self, rpc, environ):
528         # get the body if available
529         if rpc.req_content_size:
530             if rpc.req_content_type == 'application/json':
531                 rpc.req_body = environ['wsgi.input'].read(rpc.req_content_size)
532             else:
533                 rpc.rep_status = HTTPErrors.get_unsupported_content_type_status()
534                 raise ServerError('Content type is not json')
535
536     def __call__(self, environ, start_response):
537         logging.debug('Handling request started, environ=%s', str(environ))
538
539         # For request and resonse data
540         rpc = HTTPRPC()
541         rpc.rep_status = HTTPErrors.get_ok_status()
542
543         try:
544             self._read_header(rpc, environ)
545
546             action = self._get_action(rpc)
547
548             self._read_body(rpc, environ)
549
550             logging.debug('Calling %s with rpc=%s', action, str(rpc))
551             actionfunc = getattr(self, action)
552             actionfunc(rpc)
553         except ServerError as ex:
554             rpc.rep_status = HTTPErrors.get_request_not_ok_status()
555             rpc.rep_status += ', '
556             rpc.rep_status += str(ex)
557         except AttributeError:
558             rpc.rep_status = HTTPErrors.get_internal_error_status()
559             rpc.rep_status += ','
560             rpc.rep_status += 'Missing action function'
561         except Exception as exp:  # pylint: disable=broad-except
562             rpc.rep_status = HTTPErrors.get_internal_error_status()
563             rpc.rep_status += ', '
564             rpc.rep_status += str(exp)
565
566         logging.debug('Replying with rpc=%s', str(rpc))
567         response_headers = [('Content-type', 'application/json')]
568         start_response(rpc.rep_status, response_headers)
569         return [rpc.rep_body]
570
571 def wrap_socket(sock, keyfile=None, certfile=None,
572                 server_side=False, cert_reqs=ssl.CERT_NONE,
573                 ssl_version=ssl.PROTOCOL_SSLv23, ca_certs=None,
574                 do_handshake_on_connect=True,
575                 suppress_ragged_eofs=True,
576                 ciphers=None):
577
578     return LoggingSSLSocket(sock=sock, keyfile=keyfile, certfile=certfile,
579                             server_side=server_side, cert_reqs=cert_reqs,
580                             ssl_version=ssl_version, ca_certs=ca_certs,
581                             do_handshake_on_connect=do_handshake_on_connect,
582                             suppress_ragged_eofs=suppress_ragged_eofs,
583                             ciphers=ciphers)
584
585 def main():
586     parser = argparse.ArgumentParser()
587     parser = argparse.ArgumentParser()
588     parser.add_argument('-H', '--host', required=True, metavar='<bind ip>', help='binding ip of the server')
589     parser.add_argument('-P', '--listen', required=True, metavar='<bind port>', help='binding port of the server')
590     parser.add_argument('-S', '--server', required=False, metavar='<external ip>', help='externally visible ip of the server')
591     parser.add_argument('-B', '--port', required=False, metavar='<external port>', help='externally visible port of the server')
592     parser.add_argument('-C', '--cert', required=False, metavar='<server cert file>', help='path to server cert file')
593     parser.add_argument('-K', '--key', required=False, metavar='<server key file>', help='path to server private key file')
594     parser.add_argument('-c', '--client-cert', required=False, metavar='<client cert file>', help='path to client cert file')
595     parser.add_argument('-k', '--client-key', required=False, metavar='<client key file>', help='path to client key file')
596     parser.add_argument('-A', '--ca-cert', required=False, metavar='<CA cert file>', help='path to CA cert file')
597     parser.add_argument('-p', '--path', required=False, metavar='<path to installer files>', help='path to remote installer files')
598     parser.add_argument('-T', '--http-port', required=False, metavar='<HTTPD port>', help='port for HTTPD')
599     parser.add_argument('-d', '--debug', required=False, help='set debug level for logging',
600                         action='store_true')
601     parser.add_argument('--log-file', required=False, default='/var/log/remote-installer.log', metavar='<server log file>', help='path to server log file')
602     parser.add_argument('--log-file-max-size', type=int, default=5, required=False, metavar='<max size>', help='server log file max size in MB')
603     parser.add_argument('--log-file-max-count', type=int, default=10, required=False, metavar='<max count>', help='server log file count')
604
605     args = parser.parse_args()
606
607     if args.debug:
608         log_level = logging.DEBUG
609     else:
610         log_level = logging.INFO
611
612     logformat = '%(asctime)s %(threadName)s:%(levelname)s %(message)s'
613     logging.basicConfig(stream=sys.stdout, level=log_level, format=logformat)
614
615     log_file_handler = RotatingFileHandler(args.log_file,
616                                            maxBytes=(args.log_file_max_size*1024*1024),
617                                            backupCount=args.log_file_max_count)
618     log_file_handler.setFormatter(logging.Formatter(logformat))
619     log_file_handler.setLevel(log_level)
620     logging.getLogger().addHandler(log_file_handler)
621
622     logging.info('remote-installer started')
623     logging.debug('remote-installer args: %s', args)
624
625     host = args.server
626     if not host:
627         host = args.host
628
629     port = args.port
630     if not port:
631         port = args.listen
632
633     server = Server(host, port, args.cert, args.key, args.client_cert, args.client_key, args.ca_cert, args.path, args.http_port)
634
635     wsgihandler = WSGIHandler(server)
636
637     wsgi_server = make_server(args.host, int(args.listen), wsgihandler)
638
639     if args.cert:
640         server_keys = server.get_server_keys()
641         wsgi_server.socket = wrap_socket(wsgi_server.socket,
642                                          certfile=server_keys['cert'],
643                                          keyfile=server_keys['key'],
644                                          server_side=True,
645                                          ca_certs=server_keys['ca_cert'],
646                                          cert_reqs=ssl.CERT_REQUIRED)
647
648     wsgi_server.serve_forever()
649
650 if __name__ == "__main__":
651     sys.exit(main())