import sys
import argparse
+from configparser import ConfigParser
import logging
+from logging.handlers import RotatingFileHandler
import os
from threading import Thread
import time
try:
result = super(LoggingSSLSocket, self).accept(*args, **kwargs)
except Exception as ex:
- logging.warn('SSLSocket.accept raised exception: %s', str(ex))
+ logging.warning('SSLSocket.accept raised exception: %s', str(ex))
raise
return result
class InstallationWorker(Thread):
- def __init__(self, server, uuid, admin_passwd, logdir, args=None):
+ def __init__(self, server, uuid, admin_passwd, yaml, logdir, args=None):
super(InstallationWorker, self).__init__(name=uuid)
self._server = server
self._uuid = uuid
self._admin_passwd = admin_passwd
+ self._yaml = yaml
self._logdir = logdir
self._args = args
def run(self):
- access_info = None
+ installer = Installer(self._server, self._uuid, self._yaml, self._logdir, self._args)
+ access_info = installer.get_access_info()
+
if self._args:
try:
- installer = Installer(self._args)
- #access_info = installer.install()
-
+ installer.install()
logging.info('Installation triggered for %s', self._uuid)
except InstallException as ex:
- logging.warn('Installation triggering failed for %s: %s', self._uuid, str(ex))
- self._server.set_state(self._uuid, 'failed', str(ex), 0)
+ logging.warning('Installation triggering failed for %s: %s', self._uuid, str(ex))
+ self._server.set_state(self._uuid, 'failed', str(ex))
return
installation_finished = False
if not state['status'] == 'ongoing':
installation_finished = True
else:
+ logging.info('Installation of %s still ongoing (%s%%): %s',
+ self._uuid,
+ state['percentage'],
+ state['description'])
time.sleep(10)
logging.info('Installation finished for %s: %s', self._uuid, state)
- if access_info:
- logging.info('Login details for installation %s: %s', self._uuid, str(access_info))
+ logging.info('Login details for installation %s: %s', self._uuid, str(access_info))
- logging.info('Getting logs for installation %s...', uuid)
- #installer.get_logs(self._logdir, self._admin_passwd)
- logging.info('Logs retrieved for %s', uuid)
+ logging.info('Getting logs for installation %s...', self._uuid)
+ installer.get_logs(self._admin_passwd)
+ logging.info('Logs retrieved for %s', self._uuid)
class Server(object):
DEFAULT_PATH = '/opt/remoteinstaller'
ISO_PATH = 'images'
CERTIFICATE_PATH = 'certificates'
INSTALLATIONS_PATH = 'installations'
- #CLOUD_ISO_PATH = '{}/rec.iso'.format(ISO_PATH)
- BOOT_ISO_PATH = '{}/boot.iso'.format(ISO_PATH)
-
- def __init__(self, host, port, cert=None, key=None, client_cert=None, client_key=None, ca_cert=None, path=None, http_port=None):
+ USER_CONFIG_NAME = 'user_config.yaml'
+ EXTRA_CONFIG_NAME = 'installation.ini'
+
+ def __init__(self,
+ host,
+ port,
+ cert=None,
+ key=None,
+ client_cert=None,
+ client_key=None,
+ ca_cert=None,
+ path=None,
+ http_port=None):
self._host = host
self._port = port
self._http_port = http_port
with open('{}/{}/{}/admin_passwd'.format(self._path,
Server.USER_CONFIG_PATH,
cloud_name)) as pwf:
- admin_passwd = pwf.readline()
+ admin_passwd = pwf.readline().strip()
return admin_passwd
+ def _get_yaml_path_for_cloud(self, cloud_name):
+ yaml = '{}/{}/{}/{}'.format(self._path,
+ Server.USER_CONFIG_PATH,
+ cloud_name,
+ Server.USER_CONFIG_NAME)
+ if not os.path.isfile(yaml):
+ raise ServerError('YAML file {} not found'.format(yaml))
+
+ return yaml
+
def _load_states(self):
uuid_list = os.listdir('{}/{}'.format(self._path, Server.INSTALLATIONS_PATH))
for uuid in uuid_list:
logdir = '{}/{}/{}'.format(self._path, Server.INSTALLATIONS_PATH, uuid)
cloud_name = self._ongoing_installations[uuid]['cloud_name']
admin_passwd = self._read_admin_passwd(cloud_name)
- worker = InstallationWorker(self, uuid, admin_passwd, logdir)
+ yaml = self._get_yaml_path_for_cloud(cloud_name)
+ worker = InstallationWorker(self, uuid, admin_passwd, yaml, logdir)
worker.start()
- def _set_state(self, uuid, status, description, percentage, cloud_name=None):
- self._ongoing_installations[uuid] = {}
+ def _set_state(self, uuid, status, description, percentage=None, cloud_name=None):
+ if not self._ongoing_installations.get(uuid, None):
+ self._ongoing_installations[uuid] = {}
self._ongoing_installations[uuid]['status'] = status
self._ongoing_installations[uuid]['description'] = description
- self._ongoing_installations[uuid]['percentage'] = percentage
+ if percentage is not None:
+ self._ongoing_installations[uuid]['percentage'] = percentage
if cloud_name:
self._ongoing_installations[uuid]['cloud_name'] = cloud_name
with open(state_file, 'w') as sf:
sf.write(json.dumps(self._ongoing_installations[uuid]))
- def set_state(self, uuid, status, description, percentage):
- logging.info('uuid=%s, status=%s, description=%s, percentage=%s',
- uuid, status, description, percentage)
+ def set_state(self, uuid, status, description, percentage=None):
+ logging.debug('set_state called for %s: status=%s, description=%s, percentage=%s',
+ uuid, status, description, percentage)
if not uuid in self._ongoing_installations:
raise ServerError('Installation id {} not found'.format(uuid))
self._set_state(uuid, status, description, percentage)
def get_state(self, uuid):
- logging.info('uuid=%s', uuid)
+ logging.debug('get_state called for %s', uuid)
if not uuid in self._ongoing_installations:
raise ServerError('Installation id {} not found'.format(uuid))
'description': self._ongoing_installations[uuid]['description'],
'percentage': self._ongoing_installations[uuid]['percentage']}
- def start_installation(self, cloud_name, iso):
- logging.info('start_installation(%s, %s)', cloud_name, iso)
+ def _read_extra_args(self, cloud_name):
+ extra = {}
+
+ extra_config_filename = '{}/{}/{}/{}'.format(self._path,
+ Server.USER_CONFIG_PATH,
+ cloud_name,
+ Server.EXTRA_CONFIG_NAME)
+
+ if os.path.isfile(extra_config_filename):
+ logging.debug('Read extra installation args from: %s', extra_config_filename)
+ extra_config = ConfigParser()
+ with open(extra_config_filename, 'r') as extra_config_file:
+ extra_config.readfp(extra_config_file)
- uuid = str(uuid_module.uuid4())
+ if extra_config.has_section('extra'):
+ for key, value in extra_config.items('extra'):
+ extra[key] = value
+
+ return extra
+
+ def start_installation(self, cloud_name, iso, boot_iso):
+ logging.debug('start_installation called with args: (%s, %s, %s)', cloud_name, iso, boot_iso)
+
+ uuid = str(uuid_module.uuid4())[:8]
args = argparse.Namespace()
- args.yaml = '{}/{}/{}/user_config.yml'.format(self._path,
- Server.USER_CONFIG_PATH,
- cloud_name)
- if not os.path.isfile(args.yaml):
- raise ServerError('YAML file {} not found'.format(args.yaml))
+ extra_args = self._read_extra_args(cloud_name)
+ vars(args).update(extra_args)
+
+ args.yaml = self._get_yaml_path_for_cloud(cloud_name)
iso_path = '{}/{}/{}'.format(self._path, Server.ISO_PATH, iso)
if not os.path.isfile(iso_path):
raise ServerError('ISO file {} not found'.format(iso_path))
+ boot_iso_path = '{}/{}/{}'.format(self._path, Server.ISO_PATH, boot_iso)
+ if not os.path.isfile(boot_iso_path):
+ raise ServerError('Provisioning ISO file {} not found'.format(boot_iso_path))
+
http_port_part = ''
if self._http_port:
http_port_part = ':{}'.format(self._http_port)
os.makedirs(args.logdir)
- args.boot_iso = '{}/{}'.format(self._path, Server.BOOT_ISO_PATH)
+ args.boot_iso = '{}/{}/{}'.format(self._path, Server.ISO_PATH, boot_iso)
args.tag = uuid
- args.callback_url = 'http://{}:{}/v1/installations/{}/state'.format(self._host,
- self._port,
- uuid)
+ args.callback_url = 'https://{}:{}/v1/installations/{}/state'.format(self._host,
+ self._port,
+ uuid)
args.client_cert = self._client_cert
args.client_key = self._client_key
self._set_state(uuid, 'ongoing', '', 0, cloud_name)
admin_passwd = self._read_admin_passwd(cloud_name)
- worker = InstallationWorker(self, uuid, admin_passwd, args.logdir, args)
+ worker = InstallationWorker(self, uuid, admin_passwd, args.yaml, args.logdir, args)
worker.start()
return uuid
{
'cloud-name': <name of the cloud>,
'iso': <iso image name>,
+ 'provisioning-iso': <boot iso image name>
}
Response: http status set correctly
{
request = json.loads(rpc.req_body)
cloud_name = request['cloud-name']
iso = request['iso']
+ boot_iso = request['provisioning-iso']
- uuid = self.server.start_installation(cloud_name, iso)
+ uuid = self.server.start_installation(cloud_name, iso, boot_iso)
rpc.rep_status = HTTPErrors.get_ok_status()
reply = {'uuid': uuid}
rpc.rep_body = json.dumps(reply)
except KeyError as ex:
- rpc.rep_status = HTTPErrors.get_request_not_ok_status()
raise ServerError('Missing request parameter: {}'.format(str(ex)))
except Exception as exp: # pylint: disable=broad-except
rpc.rep_status = HTTPErrors.get_internal_error_status()
logging.debug('_get_state called')
try:
- if not rpc.req_body:
- rpc.rep_status = HTTPErrors.get_request_not_ok_status()
- else:
- uuid = rpc.req_params['uuid']
+ uuid = rpc.req_params['uuid']
- reply = self.server.get_state(uuid)
+ reply = self.server.get_state(uuid)
- rpc.rep_status = HTTPErrors.get_ok_status()
- rpc.rep_body = json.dumps(reply)
+ rpc.rep_status = HTTPErrors.get_ok_status()
+ rpc.rep_body = json.dumps(reply)
except KeyError as ex:
- rpc.rep_status = HTTPErrors.get_request_not_ok_status()
raise ServerError('Missing request parameter: {}'.format(str(ex)))
except Exception as exp: # pylint: disable=broad-except
rpc.rep_status = HTTPErrors.get_internal_error_status()
}
"""
- logging.debug('set_state called')
+ logging.debug('_set_state called')
try:
if not rpc.req_body:
rpc.rep_status = HTTPErrors.get_request_not_ok_status()
uuid = rpc.req_params['uuid']
status = request['status']
description = request['description']
- percentage = request['percentage']
+ percentage = request.get('percentage', None)
self.server.set_state(uuid, status, description, percentage)
rpc.rep_status = HTTPErrors.get_ok_status()
reply = {}
rpc.rep_body = json.dumps(reply)
- except ServerError:
- raise
except KeyError as ex:
rpc.rep_status = HTTPErrors.get_request_not_ok_status()
raise ServerError('Missing request parameter: {}'.format(str(ex)))
rpc.req_filter = {}
rpc.req_content_type = environ['CONTENT_TYPE']
try:
- rpc.req_content_size = int(environ['CONTENT_LENGTH'])
+ content_len = environ['CONTENT_LENGTH']
+ if not content_len:
+ rpc.req_content_size = 0
+ else:
+ rpc.req_content_size = int(content_len)
except KeyError:
rpc.req_content_size = 0
self._read_body(rpc, environ)
- logging.info('Calling %s with rpc=%s', action, str(rpc))
+ logging.debug('Calling %s with rpc=%s', action, str(rpc))
actionfunc = getattr(self, action)
actionfunc(rpc)
except ServerError as ex:
rpc.rep_status = HTTPErrors.get_request_not_ok_status()
- rpc.rep_status += ','
+ rpc.rep_status += ', '
rpc.rep_status += str(ex)
except AttributeError:
rpc.rep_status = HTTPErrors.get_internal_error_status()
rpc.rep_status += 'Missing action function'
except Exception as exp: # pylint: disable=broad-except
rpc.rep_status = HTTPErrors.get_internal_error_status()
- rpc.rep_status += ','
+ rpc.rep_status += ', '
rpc.rep_status += str(exp)
- logging.info('Replying with rpc=%s', str(rpc))
+ logging.debug('Replying with rpc=%s', str(rpc))
response_headers = [('Content-type', 'application/json')]
start_response(rpc.rep_status, response_headers)
return [rpc.rep_body]
def main():
parser = argparse.ArgumentParser()
- parser.add_argument('-H', '--host', required=True, help='binding ip of the server')
- parser.add_argument('-P', '--listen', required=True, help='binding port of the server')
- parser.add_argument('-S', '--server', required=False, help='externally visible ip of the server')
- parser.add_argument('-B', '--port', required=False, help='externally visible port of the server')
- parser.add_argument('-C', '--cert', required=False, help='server cert file name')
- parser.add_argument('-K', '--key', required=False, help='server private key file name')
- parser.add_argument('-c', '--client-cert', required=False, help='client cert file name')
- parser.add_argument('-k', '--client-key', required=False, help='client key file name')
- parser.add_argument('-A', '--ca-cert', required=False, help='CA cert file name')
- parser.add_argument('-p', '--path', required=False, help='path for remote installer files')
- parser.add_argument('-T', '--http-port', required=False, help='port for HTTPD')
- parser.add_argument('-d', '--debug', required=False, help='Debug level for logging',
+ parser = argparse.ArgumentParser()
+ parser.add_argument('-H', '--host', required=True, metavar='<bind ip>', help='binding ip of the server')
+ parser.add_argument('-P', '--listen', required=True, metavar='<bind port>', help='binding port of the server')
+ parser.add_argument('-S', '--server', required=False, metavar='<external ip>', help='externally visible ip of the server')
+ parser.add_argument('-B', '--port', required=False, metavar='<external port>', help='externally visible port of the server')
+ parser.add_argument('-C', '--cert', required=False, metavar='<server cert file>', help='path to server cert file')
+ parser.add_argument('-K', '--key', required=False, metavar='<server key file>', help='path to server private key file')
+ parser.add_argument('-c', '--client-cert', required=False, metavar='<client cert file>', help='path to client cert file')
+ parser.add_argument('-k', '--client-key', required=False, metavar='<client key file>', help='path to client key file')
+ parser.add_argument('-A', '--ca-cert', required=False, metavar='<CA cert file>', help='path to CA cert file')
+ parser.add_argument('-p', '--path', required=False, metavar='<path to installer files>', help='path to remote installer files')
+ parser.add_argument('-T', '--http-port', required=False, metavar='<HTTPD port>', help='port for HTTPD')
+ parser.add_argument('-d', '--debug', required=False, help='set debug level for logging',
action='store_true')
+ parser.add_argument('--log-file', required=False, default='/var/log/remote-installer.log', metavar='<server log file>', help='path to server log file')
+ parser.add_argument('--log-file-max-size', type=int, default=5, required=False, metavar='<max size>', help='server log file max size in MB')
+ parser.add_argument('--log-file-max-count', type=int, default=10, required=False, metavar='<max count>', help='server log file count')
args = parser.parse_args()
else:
log_level = logging.INFO
- format = '%(asctime)s %(threadName)s:%(levelname)s %(message)s'
- logging.basicConfig(stream=sys.stdout, level=log_level, format=format)
+ logformat = '%(asctime)s %(threadName)s:%(levelname)s %(message)s'
+ logging.basicConfig(stream=sys.stdout, level=log_level, format=logformat)
+
+ log_file_handler = RotatingFileHandler(args.log_file,
+ maxBytes=(args.log_file_max_size*1024*1024),
+ backupCount=args.log_file_max_count)
+ log_file_handler.setFormatter(logging.Formatter(logformat))
+ log_file_handler.setLevel(log_level)
+ logging.getLogger().addHandler(log_file_handler)
- logging.debug('args: %s', args)
+ logging.info('remote-installer started')
+ logging.debug('remote-installer args: %s', args)
host = args.server
if not host: