Пример #1
0
class NginxHandler(ServiceCtlHandler):

    _nginx_v2_flag_filepath = os.path.join(bus.etc_path, "private.d/nginx_v2")

    def __init__(self):
        self._cnf = bus.cnf
        ServiceCtlHandler.__init__(self, BEHAVIOUR, initdv2.lookup('nginx'), NginxCnfController())

        self._logger = logging.getLogger(__name__)
        self.preset_provider = NginxPresetProvider()
        self.api = NginxAPI()
        self._terminating_servers = []
        preset_service.services[BEHAVIOUR] = self.preset_provider

        bus.define_events("nginx_upstream_reload")
        bus.on(init=self.on_init, reload=self.on_reload)
        self.on_reload()

    def on_init(self):
        bus.on(start=self.on_start,
               before_host_up=self.on_before_host_up,
               host_init_response=self.on_host_init_response)

        self._insert_iptables_rules()

        if __node__['state'] == ScalarizrState.BOOTSTRAPPING:
            self._stop_service('Configuring')

    def on_reload(self):
        self._queryenv = bus.queryenv_service

        self._nginx_binary = __nginx__['binary_path']
        self._app_inc_path = __nginx__['app_include_path']
        self._app_port = __nginx__['app_port']
        try:
            self._upstream_app_role = __nginx__['upstream_app_role']
        except ConfigParser.Error:
            self._upstream_app_role = None

    def on_host_init_response(self, message):
        self._logger.debug('Handling on_host_init_response message')
        if hasattr(message, BEHAVIOUR):
            data = getattr(message, BEHAVIOUR)
            if not data and hasattr(message, 'nginx'):
                data = getattr(message, 'nginx')

            self._logger.debug('message data: %s' % data)
            if data and 'preset' in data:
                self.initial_preset = data['preset'].copy()
            if data and data.get('proxies'):
                self._set_nginx_v2_mode_flag(True)
                self._proxies = list(data.get('proxies', []))
            else:
                self._proxies = None
            self._logger.debug('proxies: %s' % self._proxies)


    def accept(self, message, queue, behaviour=None, platform=None, os=None, dist=None):
        return (BEHAVIOUR in behaviour or 'nginx' in behaviour) and \
            message.name in (Messages.HOST_UP,
                             Messages.HOST_DOWN,
                             Messages.BEFORE_HOST_TERMINATE,
                             Messages.VHOST_RECONFIGURE,
                             Messages.UPDATE_SERVICE_CONFIGURATION)

    def get_initialization_phases(self, hir_message):
        self._phase = 'Configure Nginx'
        self._step_setup_proxying = 'Setup proxying'
        self._step_copy_error_pages = 'Copy default html error pages'

        return {'before_host_up': [{
                    'name': self._phase,
                    'steps': [self._step_copy_error_pages,
                              self._step_copy_error_pages,
                              self._step_setup_proxying]}]}

    def _set_nginx_v2_mode_flag(self, on):
        if on and not self._get_nginx_v2_mode_flag():
            open(self._nginx_v2_flag_filepath, 'w').close()
        elif not on and self._get_nginx_v2_mode_flag():
            os.remove(self._nginx_v2_flag_filepath)

    def _get_nginx_v2_mode_flag(self):
        return os.path.exists(self._nginx_v2_flag_filepath)

    def on_start(self):
        self._logger.debug('Handling on_start message')
        if __node__['state'] == 'running':
            role_params = self._queryenv.list_farm_role_params(__node__['farm_role_id'])['params']
            nginx_params = role_params.get(BEHAVIOUR)
            v2_mode = (nginx_params and nginx_params.get('proxies')) \
                or self._get_nginx_v2_mode_flag()

            self._logger.debug('Updating main config')
            self._update_main_config(remove_server_section=v2_mode, reload_service=False)

            if v2_mode:
                self._set_nginx_v2_mode_flag(True)
                proxies = nginx_params.get('proxies', []) if nginx_params else []
                self._logger.debug('Recreating proxying with proxies:\n%s' % proxies)
                self.api.recreate_proxying(proxies)
            else:
                self._logger.debug('Compatibility mode proxying recreation')
                roles_for_proxy = []
                if __nginx__['upstream_app_role']:
                    roles_for_proxy = [__nginx__['upstream_app_role']]
                else:
                    roles_for_proxy = self.get_all_app_roles()
                self.make_default_proxy(roles_for_proxy)

                https_inc_path = os.path.join(os.path.dirname(self.api.app_inc_path),
                                              'https.include')
                if os.path.exists(https_inc_path):
                    self._logger.debug('Removing https.include')
                    os.remove(https_inc_path)


    def on_before_host_up(self, message):
        self._logger.debug('Handling on_before_host_up message')
        with bus.initialization_op as op:
            with op.phase(self._phase):

                with op.step(self._step_copy_error_pages):
                    self._copy_error_pages()

                with op.step(self._step_setup_proxying):
                    self._logger.debug('Updating main config')
                    v2_mode = bool(self._proxies) or self._get_nginx_v2_mode_flag()
                    self._update_main_config(remove_server_section=v2_mode,
                                             reload_service=False)

                    if v2_mode:
                        self._logger.debug('Recreating proxying with proxies:\n%s' % self._proxies)
                        self.api.recreate_proxying(self._proxies)
                    else:
                        # default behaviour
                        roles_for_proxy = []
                        if __nginx__['upstream_app_role']:
                            roles_for_proxy = [__nginx__['upstream_app_role']]
                        else:
                            roles_for_proxy = self.get_all_app_roles()
                        self.make_default_proxy(roles_for_proxy)

        bus.fire('service_configured',
                 service_name=SERVICE_NAME,
                 preset=self.initial_preset)

    def on_HostUp(self, message):
        server = ''
        role_id = message.farm_role_id
        role_name = message.role_name
        behaviours = message.behaviour
        if message.cloud_location == __node__['cloud_location']:
            server = message.local_ip
        else:
            server = message.remote_ip

        self._logger.debug('on host up backend table is %s' % self.api.backend_table)
        # Assuming backend `backend` can be only in default behaviour mode
        if not self._get_nginx_v2_mode_flag():
            upstream_role = __nginx__['upstream_app_role']
            self._logger.debug('upstream app role is %s and server is up in role %s',
                __nginx__['upstream_app_role'],
                role_name)
            if (upstream_role and upstream_role == role_name) or \
                (not upstream_role and BuiltinBehaviours.APP in behaviours):

                for default_backend in ['backend', 'backend.ssl']:
                    if default_backend not in self.api.backend_table:
                        continue

                    server_list = []
                    for dest in self.api.backend_table[default_backend]:
                        server_list.extend(dest['servers'])
                    if server in server_list:
                        continue

                    self.api.remove_server(default_backend,
                                           '127.0.0.1',
                                           reload_service=False,
                                           update_backend_table=True)
                    self._logger.info('adding new app server %s to default backend', server)
                    self.api.add_server(default_backend, server,
                                         update_backend_table=True)

        else:
            self._logger.info('adding new app server %s to role %s backend(s)', server, role_id)
            self.api.add_server_to_role(server, role_id)
        self._logger.info('After %s host up backend table is %s' % (server, self.api.backend_table))


    def _remove_shut_down_server(self,
                                 server,
                                 role_id,
                                 role_name,
                                 behaviours,
                                 cache_remove=False):
        if server in self._terminating_servers:
            self._terminating_servers.remove(server)
            return

        self._logger.debug('on host down backend table is %s' % self.api.backend_table)
        self._logger.debug('removing server %s from backends' % server)
        # Assuming backend `backend` can be only in default behaviour mode
        if not self._get_nginx_v2_mode_flag():
            upstream_role = __nginx__['upstream_app_role']
            if (upstream_role and upstream_role == role_name) or \
                (not upstream_role and BuiltinBehaviours.APP in behaviours):

                self._logger.info('removing server %s from default backend' %
                                   server)

                for default_backend in ['backend', 'backend.ssl']:
                    if default_backend not in self.api.backend_table:
                        continue
                    server_list = []
                    for dest in self.api.backend_table[default_backend]:
                        server_list.extend(dest['servers'])
                    if server not in server_list:
                        continue

                    if len(server_list) == 1:
                        self._logger.debug('adding localhost to default backend')
                        self.api.add_server(default_backend, '127.0.0.1',
                                            reload_service=False,
                                            update_backend_table=True)
                    self._logger.info('Removing %s server from backend' % server)
                    self.api.remove_server(default_backend, server, 
                                           update_backend_table=True)

        else:
            self._logger.info('removing server %s from role %s backend(s)', server, role_id)
            self.api.remove_server_from_role(server, role_id)
        self._logger.debug('After %s host down backend table is %s' %
                           (server, self.api.backend_table))

        if cache_remove:
            self._terminating_servers.append(server)

    def on_HostDown(self, message):
        server = ''
        role_id = message.farm_role_id
        role_name = message.role_name
        behaviours = message.behaviour
        if message.cloud_location == __node__['cloud_location']:
            server = message.local_ip
        else:
            server = message.remote_ip

        self._remove_shut_down_server(server, role_id, role_name, behaviours)

    def on_BeforeHostTerminate(self, message):
        server = ''
        role_id = message.farm_role_id
        role_name = message.role_name
        behaviours = message.behaviour
        if message.cloud_location == __node__['cloud_location']:
            server = message.local_ip
        else:
            server = message.remote_ip

        self._remove_shut_down_server(server, role_id, role_name, behaviours, True)

    def on_VhostReconfigure(self, message):
        if not self._get_nginx_v2_mode_flag():
            self._logger.debug('updating certificates')
            cert, key, cacert = self._queryenv.get_https_certificate()
            self.api.update_ssl_certificate('', cert, key, cacert)

            self._logger.debug('before vhost reconf backend table is %s' % self.api.backend_table)
            roles_for_proxy = []
            if __nginx__['upstream_app_role']:
                roles_for_proxy = [__nginx__['upstream_app_role']]
            else:
                roles_for_proxy = self.get_all_app_roles()
            self.make_default_proxy(roles_for_proxy)
            self._logger.debug('after vhost reconf backend table is %s' % self.api.backend_table)

    def on_SSLCertificateUpdate(self, message):
        ssl_cert_id = message.id  # TODO: check datastructure
        private_key = message.private_key
        certificate = message.certificate
        cacertificate = message.cacertificate
        self.api.update_ssl_certificate(ssl_cert_id,
                                        certificate,
                                        private_key,
                                        cacertificate)
        self.api._reload_service()

    def _copy_error_pages(self):
        pages_source = '/usr/share/scalr/nginx/html/'
        pages_destination = '/usr/share/nginx/html/'

        current_dir = ''
        for d in pages_destination.split(os.path.sep)[1:-1]:
            current_dir = current_dir + '/' + d
            if not os.path.exists(current_dir):
                os.makedirs(current_dir)

        if not os.path.exists(pages_destination + '500.html'):
            shutil.copy(pages_source + '500.html', pages_destination)
        if not os.path.exists(pages_destination + '502.html'):
            shutil.copy(pages_source + '502.html', pages_destination)
        if not os.path.exists(pages_destination + 'noapp.html'):
            shutil.copy(pages_source + 'noapp.html', pages_destination)

    def _https_config_exists(self):
        config_dir = os.path.dirname(self.api.app_inc_path)
        conf_path = os.path.join(config_dir, 'https.include')

        config = None
        try:
            config = Configuration('nginx')
            config.read(conf_path)
        except (Exception, BaseException), e:
            raise HandlerError('Cannot read/parse nginx main configuration file: %s' % str(e))

        return config.get('server') != None