def init_sql(self): """ Initializes SQL connections, first to ODB and then any user-defined ones. """ # We need a store first self.sql_pool_store = PoolStore() # Connect to ODB self.sql_pool_store[ZATO_ODB_POOL_NAME] = self.worker_config.odb_data self.odb = SessionWrapper() self.odb.init_session(ZATO_ODB_POOL_NAME, self.worker_config.odb_data, self.sql_pool_store[ZATO_ODB_POOL_NAME].pool) # Any user-defined SQL connections left? for pool_name in self.worker_config.out_sql: config = self.worker_config.out_sql[pool_name]['config'] self.sql_pool_store[pool_name] = config
class WorkerStore(BrokerMessageReceiver): """ Each worker thread has its own configuration store. The store is assigned to the thread's threading.local variable. All the methods assume the data's being already validated and sanitized by one of Zato's internal services. There are exactly two threads willing to access the data at any time - the worker thread this store belongs to - the background ZeroMQ thread which may wish to update the store's configuration hence the need for employing RLocks yet there shouldn't be much contention because configuration updates are extremaly rare when compared to regular access by worker threads. """ def __init__(self, worker_config=None, server=None): self.logger = logging.getLogger(self.__class__.__name__) self.is_ready = False self.worker_config = worker_config self.server = server self.update_lock = RLock() self.kvdb = server.kvdb self.broker_client = None def init(self): # Statistics maintenance self.stats_maint = MaintenanceTool(self.kvdb.conn) self.msg_ns_store = NamespaceStore() self.elem_path_store = ElemPathStore() self.xpath_store = XPathStore() # Message-related config - init_msg_ns_store must come before init_xpath_store # so the latter has access to the former's namespace map. self.init_msg_ns_store() self.init_elem_path_store() self.init_xpath_store() # Request dispatcher - matches URLs, checks security and dispatches HTTP # requests to services. self.request_dispatcher = RequestDispatcher(simple_io_config=self.worker_config.simple_io) self.request_dispatcher.url_data = URLData( deepcopy(self.worker_config.http_soap), self.server.odb.get_url_security(self.server.cluster_id, 'channel')[0], self.worker_config.basic_auth, self.worker_config.oauth, self.worker_config.tech_acc, self.worker_config.wss, self.kvdb, self.broker_client, self.server.odb, self.elem_path_store, self.xpath_store) self.request_dispatcher.request_handler = RequestHandler(self.server) # Create all the expected connections self.init_sql() self.init_ftp() self.init_http_soap() # All set, whoever is waiting for us, if anyone at all, can now proceed self.is_ready = True def filter(self, msg): # TODO: Fix it, worker doesn't need to accept all the messages return True def _http_soap_wrapper_from_config(self, config, has_sec_config=True): """ Creates a new HTTP/SOAP connection wrapper out of a configuration dictionary. """ security_name = config.get('security_name') sec_config = {'security_name':security_name, 'sec_type':None, 'username':None, 'password':None, 'password_type':None} _sec_config = None # This will be set to True only if the method's invoked on a server's starting up if has_sec_config: # It's possible that there is no security config attached at all if security_name: _sec_config = config else: if security_name: sec_type = config.sec_type func = getattr(self.request_dispatcher.url_data, sec_type + '_get') _sec_config = func(security_name).config if logger.isEnabledFor(TRACE1): logger.log(TRACE1, 'has_sec_config:[{}], security_name:[{}], _sec_config:[{}]'.format( has_sec_config, security_name, _sec_config)) if _sec_config: sec_config['sec_type'] = _sec_config['sec_type'] sec_config['username'] = _sec_config['username'] sec_config['password'] = _sec_config['password'] sec_config['password_type'] = _sec_config.get('password_type') sec_config['salt'] = _sec_config.get('salt') wrapper_config = {'id':config.id, 'is_active':config.is_active, 'method':config.method, 'data_format':config.get('data_format'), 'name':config.name, 'transport':config.transport, 'address_host':config.host, 'address_url_path':config.url_path, 'soap_action':config.soap_action, 'soap_version':config.soap_version, 'ping_method':config.ping_method, 'pool_size':config.pool_size,} wrapper_config.update(sec_config) return HTTPSOAPWrapper(wrapper_config) # ############################################################################## def init_sql(self): """ Initializes SQL connections, first to ODB and then any user-defined ones. """ # We need a store first self.sql_pool_store = PoolStore() # Connect to ODB self.sql_pool_store[ZATO_ODB_POOL_NAME] = self.worker_config.odb_data self.odb = SessionWrapper() self.odb.init_session(ZATO_ODB_POOL_NAME, self.worker_config.odb_data, self.sql_pool_store[ZATO_ODB_POOL_NAME].pool) # Any user-defined SQL connections left? for pool_name in self.worker_config.out_sql: config = self.worker_config.out_sql[pool_name]['config'] self.sql_pool_store[pool_name] = config def init_ftp(self): """ Initializes FTP connetions. The method replaces whatever value self.out_ftp previously had (initially this would be a ConfigDict of connection definitions). """ config_list = self.worker_config.out_ftp.get_config_list() self.worker_config.out_ftp = FTPStore() self.worker_config.out_ftp.add_params(config_list) def init_http_soap(self): """ Initializes plain HTTP/SOAP connections. """ for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) for name in config_dict: config = config_dict[name].config wrapper = self._http_soap_wrapper_from_config(config) config_dict[name].conn = wrapper # To make the API consistent with that of SQL connection pools config_dict[name].ping = wrapper.ping # ############################################################################## def init_msg_ns_store(self): for k, v in self.worker_config.msg_ns.items(): self.msg_ns_store.create(k, v.config) def init_xpath_store(self): for k, v in self.worker_config.xpath.items(): self.xpath_store.create(k, v.config, self.msg_ns_store.ns_map) def init_elem_path_store(self): for k, v in self.worker_config.elem_path.items(): self.elem_path_store.create(k, v.config, {}) # ############################################################################## def _update_auth(self, msg, action_name, sec_type, visit_wrapper, keys=None): """ A common method for updating auth-related configuration. """ with self.update_lock: # Channels handler = getattr(self.request_dispatcher.url_data, 'on_broker_msg_' + action_name) handler(msg) for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) # Wrappers and static configuration for outgoing connections for name in config_dict.copy_keys(): config = config_dict[name].config wrapper = config_dict[name].conn if config['sec_type'] == sec_type: if keys: visit_wrapper(wrapper, msg, keys) else: visit_wrapper(wrapper, msg) def _visit_wrapper_edit(self, wrapper, msg, keys): """ Updates a given wrapper's security configuration. """ if wrapper.config['security_name'] == msg['old_name']: for key in keys: # All's good except for 'name', the msg's 'name' is known # as 'security_name' in wrapper's config. if key == 'name': key1 = 'security_name' key2 = key else: key1, key2 = key, key wrapper.config[key1] = msg[key2] wrapper.set_auth() def _visit_wrapper_delete(self, wrapper, msg): """ Deletes a wrapper. """ config_dict = getattr(self.worker_config, 'out_' + wrapper.config['transport']) if wrapper.config['security_name'] == msg['name']: del config_dict[wrapper.config['name']] def _visit_wrapper_change_password(self, wrapper, msg): """ Changes a wrapper's password. """ if wrapper.config['security_name'] == msg['name']: wrapper.config['password'] = msg['password'] wrapper.set_auth() # ############################################################################## def basic_auth_get(self, name): """ Returns the configuration of the HTTP Basic Auth security definition of the given name. """ self.request_dispatcher.url_data.basic_auth_get(name) def on_broker_msg_SECURITY_BASIC_AUTH_CREATE(self, msg, *args): """ Creates a new HTTP Basic Auth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_BASIC_AUTH_CREATE(msg, *args) def on_broker_msg_SECURITY_BASIC_AUTH_EDIT(self, msg, *args): """ Updates an existing HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_BASIC_AUTH_DELETE(self, msg, *args): """ Deletes an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_delete) def on_broker_msg_SECURITY_BASIC_AUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_change_password) # ############################################################################## def oauth_get(self, name): """ Returns the configuration of the OAuth security definition of the given name. """ self.request_dispatcher.url_data.oauth_get(name) def on_broker_msg_SECURITY_OAUTH_CREATE(self, msg, *args): """ Creates a new OAuth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_OAUTH_CREATE(msg, *args) def on_broker_msg_SECURITY_OAUTH_EDIT(self, msg, *args): """ Updates an existing OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_OAUTH_DELETE(self, msg, *args): """ Deletes an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_delete) def on_broker_msg_SECURITY_OAUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_change_password) # ############################################################################## def tech_acc_get(self, name): """ Returns the configuration of the technical account of the given name. """ self.request_dispatcher.url_data.tech_acc_get(name) def on_broker_msg_SECURITY_TECH_ACC_CREATE(self, msg, *args): """ Creates a new technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CREATE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_EDIT(self, msg, *args): """ Updates an existing technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_EDIT(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_DELETE(self, msg, *args): """ Deletes a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_DELETE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(msg, *args) # ############################################################################## def wss_get(self, name): """ Returns the configuration of the WSS definition of the given name. """ self.request_dispatcher.url_data.wss_get(name) def on_broker_msg_SECURITY_WSS_CREATE(self, msg, *args): """ Creates a new WS-Security definition. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_WSS_CREATE(msg, *args) def on_broker_msg_SECURITY_WSS_EDIT(self, msg, *args): """ Updates an existing WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_edit, keys=('is_active', 'username', 'name', 'nonce_freshness_time', 'reject_expiry_limit', 'password_type', 'reject_empty_nonce_creat', 'reject_stale_tokens')) def on_broker_msg_SECURITY_WSS_DELETE(self, msg, *args): """ Deletes a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_delete) def on_broker_msg_SECURITY_WSS_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_change_password) # ############################################################################## def _set_service_response_data(self, service, **ignored): if not isinstance(service.response.payload, basestring): service.response.payload = service.response.payload.getvalue() def _on_message_invoke_service(self, msg, channel, action, args=None): """ Triggered by external processes, such as AMQP or the singleton's scheduler, creates a new service instance and invokes it. """ # WSGI environment is the best place we have to store raw msg in wsgi_environ = {'zato.request_ctx.async_msg':msg} service = self.server.service_store.new_instance_by_name(msg.service) service.update_handle(self._set_service_response_data, service, msg.payload, channel, msg.get('data_format'), msg.get('transport'), self.server, self.broker_client, self, msg.cid, self.worker_config.simple_io, job_type=msg.get('job_type'), wsgi_environ=wsgi_environ) # ############################################################################## def on_broker_msg_SCHEDULER_JOB_EXECUTED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.SCHEDULER, 'SCHEDULER_JOB_EXECUTED', args) def on_broker_msg_CHANNEL_AMQP_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.AMQP, 'CHANNEL_AMQP_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_JMS_WMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.JMS_WMQ, 'CHANNEL_JMS_WMQ_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_ZMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.ZMQ, 'CHANNEL_ZMQ_MESSAGE_RECEIVED', args) # ############################################################################## def on_broker_msg_OUTGOING_SQL_CREATE_EDIT(self, msg, *args): """ Creates or updates an SQL connection, including changing its password. """ # Is it a rename? If so, delete the connection first if msg.get('old_name') and msg.get('old_name') != msg['name']: del self.sql_pool_store[msg['old_name']] self.sql_pool_store[msg['name']] = msg def on_broker_msg_OUTGOING_SQL_CHANGE_PASSWORD(self, msg, *args): """ Deletes an outgoing SQL connection pool and recreates it using the new password. """ self.sql_pool_store.change_password(msg['name'], msg['password']) def on_broker_msg_OUTGOING_SQL_DELETE(self, msg, *args): """ Deletes an outgoing SQL connection pool. """ del self.sql_pool_store[msg['name']] # ############################################################################## def on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(self, msg, *args): """ Creates or updates an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(msg, *args) def on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(self, msg, *args): """ Deletes an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(msg, *args) # ############################################################################## def _delete_outgoing_http_soap(self, name, transport, log_func): """ Actually deletes an outgoing HTTP/SOAP connection. """ # Are we dealing with plain HTTP or SOAP? config_dict = getattr(self.worker_config, 'out_' + transport) # Delete the connection first, if it exists at all .. try: try: wrapper = config_dict[name].conn except (KeyError, AttributeError), e: log_func('Could not access a wrapper, e:[{}]'.format(format_exc(e))) else:
def sql_pool_store(self): return PoolStore()
class WorkerStore(BrokerMessageReceiver): """ Each worker thread has its own configuration store. The store is assigned to the thread's threading.local variable. All the methods assume the data's being already validated and sanitized by one of Zato's internal services. There are exactly two threads willing to access the data at any time - the worker thread this store belongs to - the background ZeroMQ thread which may wish to update the store's configuration hence the need for employing RLocks yet there shouldn't be much contention because configuration updates are extremaly rare when compared to regular access by worker threads. """ def __init__(self, worker_config=None, server=None): self.logger = logging.getLogger(self.__class__.__name__) self.is_ready = False self.worker_config = worker_config self.server = server self.update_lock = RLock() self.kvdb = server.kvdb self.broker_client = None self.pubsub = None """:type: zato.common.pubsub.PubSubAPI""" def init(self): # Statistics maintenance self.stats_maint = MaintenanceTool(self.kvdb.conn) self.msg_ns_store = NamespaceStore() self.elem_path_store = ElemPathStore() self.xpath_store = XPathStore() # Message-related config - init_msg_ns_store must come before init_xpath_store # so the latter has access to the former's namespace map. self.init_msg_ns_store() self.init_elem_path_store() self.init_xpath_store() # Request dispatcher - matches URLs, checks security and dispatches HTTP # requests to services. self.request_dispatcher = RequestDispatcher(simple_io_config=self.worker_config.simple_io) self.request_dispatcher.url_data = URLData( deepcopy(self.worker_config.http_soap), self.server.odb.get_url_security(self.server.cluster_id, 'channel')[0], self.worker_config.basic_auth, self.worker_config.ntlm, self.worker_config.oauth, self.worker_config.tech_acc, self.worker_config.wss, self.worker_config.apikey, self.worker_config.aws, self.worker_config.openstack_security, self.worker_config.xpath_sec, self.kvdb, self.broker_client, self.server.odb, self.elem_path_store, self.xpath_store) self.request_dispatcher.request_handler = RequestHandler(self.server) # Create all the expected connections and objects self.init_sql() self.init_ftp() self.init_http_soap() self.init_cloud() self.init_pubsub() self.init_notifiers() # All set, whoever is waiting for us, if anyone at all, can now proceed self.is_ready = True def filter(self, msg): # TODO: Fix it, worker doesn't need to accept all the messages return True def _update_aws_config(self, msg): """ Parses the address to AWS we store into discrete components S3Connection objects expect. Also turns metadata string into a dictionary """ url_info = urlparse(msg.address) msg.is_secure = True if url_info.scheme == 'https' else False msg.port = url_info.port if url_info.port else (443 if msg.is_secure else 80) msg.host = url_info.netloc msg.metadata = parse_extra_into_dict(msg.metadata_) def _http_soap_wrapper_from_config(self, config, has_sec_config=True): """ Creates a new HTTP/SOAP connection wrapper out of a configuration dictionary. """ security_name = config.get('security_name') sec_config = {'security_name':security_name, 'sec_type':None, 'username':None, 'password':None, 'password_type':None} _sec_config = None # This will be set to True only if the method's invoked on a server's starting up if has_sec_config: # It's possible that there is no security config attached at all if security_name: _sec_config = config else: if security_name: sec_type = config.sec_type func = getattr(self.request_dispatcher.url_data, sec_type + '_get') _sec_config = func(security_name).config if logger.isEnabledFor(TRACE1): logger.log(TRACE1, 'has_sec_config:[{}], security_name:[{}], _sec_config:[{}]'.format( has_sec_config, security_name, _sec_config)) if _sec_config: sec_config['sec_type'] = _sec_config['sec_type'] sec_config['username'] = _sec_config['username'] sec_config['password'] = _sec_config['password'] sec_config['password_type'] = _sec_config.get('password_type') sec_config['salt'] = _sec_config.get('salt') wrapper_config = {'id':config.id, 'is_active':config.is_active, 'method':config.method, 'data_format':config.get('data_format'), 'name':config.name, 'transport':config.transport, 'address_host':config.host, 'address_url_path':config.url_path, 'soap_action':config.soap_action, 'soap_version':config.soap_version, 'ping_method':config.ping_method, 'pool_size':config.pool_size, 'serialization_type':config.serialization_type, 'timeout':config.timeout} wrapper_config.update(sec_config) if wrapper_config['serialization_type'] == HTTP_SOAP_SERIALIZATION_TYPE.SUDS.id: wrapper_config['queue_build_cap'] = float(self.server.fs_server_config.misc.queue_build_cap) wrapper = SudsSOAPWrapper(wrapper_config) wrapper.build_client_queue() return wrapper return HTTPSOAPWrapper(wrapper_config) # ################################################################################################################################ def init_sql(self): """ Initializes SQL connections, first to ODB and then any user-defined ones. """ # We need a store first self.sql_pool_store = PoolStore() # Connect to ODB self.sql_pool_store[ZATO_ODB_POOL_NAME] = self.worker_config.odb_data self.odb = SessionWrapper() self.odb.init_session(ZATO_ODB_POOL_NAME, self.worker_config.odb_data, self.sql_pool_store[ZATO_ODB_POOL_NAME].pool) # Any user-defined SQL connections left? for pool_name in self.worker_config.out_sql: config = self.worker_config.out_sql[pool_name]['config'] self.sql_pool_store[pool_name] = config def init_ftp(self): """ Initializes FTP connetions. The method replaces whatever value self.out_ftp previously had (initially this would be a ConfigDict of connection definitions). """ config_list = self.worker_config.out_ftp.get_config_list() self.worker_config.out_ftp = FTPStore() self.worker_config.out_ftp.add_params(config_list) def init_http_soap(self): """ Initializes plain HTTP/SOAP connections. """ for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) for name in config_dict: config = config_dict[name].config wrapper = self._http_soap_wrapper_from_config(config) config_dict[name].conn = wrapper # To make the API consistent with that of SQL connection pools config_dict[name].ping = wrapper.ping def init_cloud(self): """ Initializes all the cloud connections. """ data = ( ('cloud_openstack_swift', SwiftWrapper), ('cloud_aws_s3', S3Wrapper), ) for config_key, wrapper in data: config_attr = getattr(self.worker_config, config_key) for name in config_attr: config = config_attr[name]['config'] if isinstance(wrapper, S3Wrapper): self._update_aws_config(config) config.queue_build_cap = float(self.server.fs_server_config.misc.queue_build_cap) config_attr[name].conn = wrapper(config) config_attr[name].conn.build_queue() def _update_cloud_openstack_swift_container(self, config_dict): """ Makes sure OpenStack Swift containers always have a path to prefix queries with. """ config_dict.containers = [elem.split(':') for elem in config_dict.containers.splitlines()] for item in config_dict.containers: # No path specified so we use an empty string to catch everything. if len(item) == 1: item.append('') item.append('{}:{}'.format(item[0], item[1])) def init_notifiers(self): for config_dict in self.worker_config.notif_cloud_openstack_swift.values(): self._update_cloud_openstack_swift_container(config_dict.config) # ################################################################################################################################ def _topic_from_topic_data(self, data): return Topic(data.name, data.is_active, True, data.max_depth) def _add_pubsub_topic(self, data): self.pubsub.add_topic(self._topic_from_topic_data(data)) def init_pubsub(self): """ Initializes publish/subscribe mechanisms. """ self.pubsub.set_default_consumer(self.worker_config.pubsub.default_consumer) self.pubsub.set_default_producer(self.worker_config.pubsub.default_producer) for topic_name, topic_data in self.worker_config.pubsub.topics.items(): self._add_pubsub_topic(topic_data.config) for list_value in self.worker_config.pubsub.producers.values(): for config in list_value: self.pubsub.add_producer(Client(config.client_id, config.name, config.is_active), Topic(config.topic_name)) for list_value in self.worker_config.pubsub.consumers.values(): for config in list_value: callback_type = PUB_SUB.CALLBACK_TYPE.OUTCONN_SOAP if bool(config.soap_version) else \ PUB_SUB.CALLBACK_TYPE.OUTCONN_PLAIN_HTP self.pubsub.add_consumer( Consumer( config.client_id, config.name, config.is_active, config.sub_key, config.max_backlog, config.delivery_mode, config.callback_id, config.callback_name, callback_type), Topic(config.topic_name)) # ################################################################################################################################ def init_msg_ns_store(self): for k, v in self.worker_config.msg_ns.items(): self.msg_ns_store.create(k, v.config) def init_xpath_store(self): for k, v in self.worker_config.xpath.items(): self.xpath_store.create(k, v.config, self.msg_ns_store.ns_map) def init_elem_path_store(self): for k, v in self.worker_config.elem_path.items(): self.elem_path_store.create(k, v.config, {}) # ################################################################################################################################ def _update_auth(self, msg, action_name, sec_type, visit_wrapper, keys=None): """ A common method for updating auth-related configuration. """ with self.update_lock: # Channels handler = getattr(self.request_dispatcher.url_data, 'on_broker_msg_' + action_name) handler(msg) for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) # Wrappers and static configuration for outgoing connections for name in config_dict.copy_keys(): config = config_dict[name].config wrapper = config_dict[name].conn if config['sec_type'] == sec_type: if keys: visit_wrapper(wrapper, msg, keys) else: visit_wrapper(wrapper, msg) def _visit_wrapper_edit(self, wrapper, msg, keys): """ Updates a given wrapper's security configuration. """ if wrapper.config['security_name'] == msg['old_name']: for key in keys: # All's good except for 'name', the msg's 'name' is known # as 'security_name' in wrapper's config. if key == 'name': key1 = 'security_name' key2 = key else: key1, key2 = key, key wrapper.config[key1] = msg[key2] wrapper.set_auth() def _visit_wrapper_delete(self, wrapper, msg): """ Deletes a wrapper. """ config_dict = getattr(self.worker_config, 'out_' + wrapper.config['transport']) if wrapper.config['security_name'] == msg['name']: del config_dict[wrapper.config['name']] def _visit_wrapper_change_password(self, wrapper, msg): """ Changes a wrapper's password. """ if wrapper.config['security_name'] == msg['name']: wrapper.config['password'] = msg['password'] wrapper.set_auth() # ################################################################################################################################ def apikey_get(self, name): """ Returns the configuration of the API key of the given name. """ self.request_dispatcher.url_data.apikey_get(name) def on_broker_msg_SECURITY_APIKEY_CREATE(self, msg, *args): """ Creates a new API key security definition. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_APIKEY_CREATE(msg, *args) def on_broker_msg_SECURITY_APIKEY_EDIT(self, msg, *args): """ Updates an existing API key security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.APIKEY, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_APIKEY_DELETE(self, msg, *args): """ Deletes an API key security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.APIKEY, self._visit_wrapper_delete) def on_broker_msg_SECURITY_APIKEY_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an API key security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.APIKEY, self._visit_wrapper_change_password) # ################################################################################################################################ def aws_get(self, name): """ Returns the configuration of the AWS security definition of the given name. """ self.request_dispatcher.url_data.aws_get(name) def on_broker_msg_SECURITY_AWS_CREATE(self, msg, *args): """ Creates a new AWS security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_AWS_CREATE(msg, *args) def on_broker_msg_SECURITY_AWS_EDIT(self, msg, *args): """ Updates an existing AWS security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.AWS, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_AWS_DELETE(self, msg, *args): """ Deletes an AWS security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.AWS, self._visit_wrapper_delete) def on_broker_msg_SECURITY_AWS_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an AWS security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.AWS, self._visit_wrapper_change_password) # ################################################################################################################################ def openstack_get(self, name): """ Returns the configuration of the OpenStack security definition of the given name. """ self.request_dispatcher.url_data.openstack_get(name) def on_broker_msg_SECURITY_OPENSTACK_CREATE(self, msg, *args): """ Creates a new OpenStack security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_OPENSTACK_CREATE(msg, *args) def on_broker_msg_SECURITY_OPENSTACK_EDIT(self, msg, *args): """ Updates an existing OpenStack security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OPENSTACK, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_OPENSTACK_DELETE(self, msg, *args): """ Deletes an OpenStack security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OPENSTACK, self._visit_wrapper_delete) def on_broker_msg_SECURITY_OPENSTACK_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an OpenStack security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OPENSTACK, self._visit_wrapper_change_password) # ################################################################################################################################ def ntlm_get(self, name): """ Returns the configuration of the NTLM security definition of the given name. """ self.request_dispatcher.url_data.ntlm_get(name) def on_broker_msg_SECURITY_NTLM_CREATE(self, msg, *args): """ Creates a new NTLM security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_NTLM_CREATE(msg, *args) def on_broker_msg_SECURITY_NTLM_EDIT(self, msg, *args): """ Updates an existing NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.NTLM, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_NTLM_DELETE(self, msg, *args): """ Deletes an NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.NTLM, self._visit_wrapper_delete) def on_broker_msg_SECURITY_NTLM_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.NTLM, self._visit_wrapper_change_password) # ################################################################################################################################ def basic_auth_get(self, name): """ Returns the configuration of the HTTP Basic Auth security definition of the given name. """ self.request_dispatcher.url_data.basic_auth_get(name) def on_broker_msg_SECURITY_BASIC_AUTH_CREATE(self, msg, *args): """ Creates a new HTTP Basic Auth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_BASIC_AUTH_CREATE(msg, *args) def on_broker_msg_SECURITY_BASIC_AUTH_EDIT(self, msg, *args): """ Updates an existing HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.BASIC_AUTH, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_BASIC_AUTH_DELETE(self, msg, *args): """ Deletes an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.BASIC_AUTH, self._visit_wrapper_delete) def on_broker_msg_SECURITY_BASIC_AUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.BASIC_AUTH, self._visit_wrapper_change_password) # ################################################################################################################################ def oauth_get(self, name): """ Returns the configuration of the OAuth security definition of the given name. """ self.request_dispatcher.url_data.oauth_get(name) def on_broker_msg_SECURITY_OAUTH_CREATE(self, msg, *args): """ Creates a new OAuth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_OAUTH_CREATE(msg, *args) def on_broker_msg_SECURITY_OAUTH_EDIT(self, msg, *args): """ Updates an existing OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OAUTH, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_OAUTH_DELETE(self, msg, *args): """ Deletes an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OAUTH, self._visit_wrapper_delete) def on_broker_msg_SECURITY_OAUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.OAUTH, self._visit_wrapper_change_password) # ################################################################################################################################ def tech_acc_get(self, name): """ Returns the configuration of the technical account of the given name. """ self.request_dispatcher.url_data.tech_acc_get(name) def on_broker_msg_SECURITY_TECH_ACC_CREATE(self, msg, *args): """ Creates a new technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CREATE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_EDIT(self, msg, *args): """ Updates an existing technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_EDIT(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_DELETE(self, msg, *args): """ Deletes a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_DELETE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(msg, *args) # ################################################################################################################################ def wss_get(self, name): """ Returns the configuration of the WSS definition of the given name. """ self.request_dispatcher.url_data.wss_get(name) def on_broker_msg_SECURITY_WSS_CREATE(self, msg, *args): """ Creates a new WS-Security definition. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_WSS_CREATE(msg, *args) def on_broker_msg_SECURITY_WSS_EDIT(self, msg, *args): """ Updates an existing WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.WSS, self._visit_wrapper_edit, keys=('is_active', 'username', 'name', 'nonce_freshness_time', 'reject_expiry_limit', 'password_type', 'reject_empty_nonce_creat', 'reject_stale_tokens')) def on_broker_msg_SECURITY_WSS_DELETE(self, msg, *args): """ Deletes a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.WSS, self._visit_wrapper_delete) def on_broker_msg_SECURITY_WSS_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.WSS, self._visit_wrapper_change_password) # ################################################################################################################################ def xpath_sec_get(self, name): """ Returns the configuration of an XPath security definition of the given name. """ self.request_dispatcher.url_data.xpath_sec_get(name) def on_broker_msg_SECURITY_XPATH_SEC_CREATE(self, msg, *args): """ Creates a new XPath security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_XPATH_SEC_CREATE(msg, *args) def on_broker_msg_SECURITY_XPATH_SEC_EDIT(self, msg, *args): """ Updates an existing XPath security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.XPATH_SEC, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_XPATH_SEC_DELETE(self, msg, *args): """ Deletes an XPath security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.XPATH_SEC, self._visit_wrapper_delete) def on_broker_msg_SECURITY_XPATH_SEC_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an XPath security definition. """ self._update_auth(msg, code_to_name[msg.action], SEC_DEF_TYPE.XPATH_SEC, self._visit_wrapper_change_password) # ################################################################################################################################ def _set_service_response_data(self, service, **ignored): if not isinstance(service.response.payload, basestring): service.response.payload = service.response.payload.getvalue() def _on_message_invoke_service(self, msg, channel, action, args=None): """ Triggered by external processes, such as AMQP or the singleton's scheduler, creates a new service instance and invokes it. """ # WSGI environment is the best place we have to store raw msg in wsgi_environ = {'zato.request_ctx.async_msg':msg} service = self.server.service_store.new_instance_by_name(msg['service']) service.update_handle(self._set_service_response_data, service, msg['payload'], channel, msg.get('data_format'), msg.get('transport'), self.server, self.broker_client, self, msg['cid'], self.worker_config.simple_io, job_type=msg.get('job_type'), wsgi_environ=wsgi_environ) # ################################################################################################################################ def on_broker_msg_SCHEDULER_JOB_EXECUTED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.SCHEDULER, 'SCHEDULER_JOB_EXECUTED', args) def on_broker_msg_CHANNEL_AMQP_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.AMQP, 'CHANNEL_AMQP_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_JMS_WMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.JMS_WMQ, 'CHANNEL_JMS_WMQ_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_ZMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.ZMQ, 'CHANNEL_ZMQ_MESSAGE_RECEIVED', args) # ################################################################################################################################ def on_broker_msg_OUTGOING_SQL_CREATE_EDIT(self, msg, *args): """ Creates or updates an SQL connection, including changing its password. """ # Is it a rename? If so, delete the connection first if msg.get('old_name') and msg.get('old_name') != msg['name']: del self.sql_pool_store[msg['old_name']] self.sql_pool_store[msg['name']] = msg def on_broker_msg_OUTGOING_SQL_CHANGE_PASSWORD(self, msg, *args): """ Deletes an outgoing SQL connection pool and recreates it using the new password. """ self.sql_pool_store.change_password(msg['name'], msg['password']) def on_broker_msg_OUTGOING_SQL_DELETE(self, msg, *args): """ Deletes an outgoing SQL connection pool. """ del self.sql_pool_store[msg['name']] # ################################################################################################################################ def on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(self, msg, *args): """ Creates or updates an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(msg, *args) def on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(self, msg, *args): """ Deletes an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(msg, *args) # ################################################################################################################################ def _delete_config_close_wrapper(self, name, config_dict, conn_type, log_func): """ Deletes a wrapper-based connection's config and closes its underlying wrapper. """ # Delete the connection first, if it exists at all .. try: try: wrapper = config_dict[name].conn except (KeyError, AttributeError), e: log_func('Could not access wrapper, e:[{}]'.format(format_exc(e))) else:
class WorkerStore(BrokerMessageReceiver): """ Each worker thread has its own configuration store. The store is assigned to the thread's threading.local variable. All the methods assume the data's being already validated and sanitized by one of Zato's internal services. There are exactly two threads willing to access the data at any time - the worker thread this store belongs to - the background ZeroMQ thread which may wish to update the store's configuration hence the need for employing RLocks yet there shouldn't be much contention because configuration updates are extremaly rare when compared to regular access by worker threads. """ def __init__(self, worker_config=None, server=None): self.logger = logging.getLogger(self.__class__.__name__) self.worker_config = worker_config self.server = server self.update_lock = RLock() self.kvdb = server.kvdb self.broker_client = None def init(self): plain_http_config = MultiDict() soap_config = MultiDict() dol = deepcopy(self.worker_config.http_soap).dict_of_lists() for url_path in dol: for item in dol[url_path]: for soap_action, channel_info in item.items(): if channel_info['connection'] == 'channel': if channel_info.transport == 'plain_http': config = plain_http_config.setdefault(url_path, Bunch()) config[soap_action] = deepcopy(channel_info) else: config = soap_config.setdefault(url_path, Bunch()) config[soap_action] = deepcopy(channel_info) self.request_dispatcher = RequestDispatcher(simple_io_config=self.worker_config.simple_io) self.request_dispatcher.soap_handler = SOAPHandler(soap_config, self.server) self.request_dispatcher.plain_http_handler = PlainHTTPHandler(plain_http_config, self.server) # Statistics maintenance self.stats_maint = MaintenanceTool(self.kvdb.conn) self.request_dispatcher.security = ConnectionHTTPSOAPSecurity( self.server.odb.get_url_security(self.server.cluster_id)[0], self.worker_config.basic_auth, self.worker_config.tech_acc, self.worker_config.wss) # Create all the expected connections self.init_sql() self.init_ftp() self.init_http_soap() def filter(self, msg): # TODO: Fix it, worker doesn't need to accept all the messages return True def _http_soap_wrapper_from_config(self, config, has_sec_config=True): """ Creates a new HTTP/SOAP connection wrapper out of a configuration dictionary. """ security_name = config.get('security_name') sec_config = {'security_name':security_name, 'sec_type':None, 'username':None, 'password':None, 'password_type':None} _sec_config = None # This will be set to True only if the method's invoked on a server's starting up if has_sec_config: # It's possible that there is no security config attached at all if security_name: _sec_config = config else: if security_name: sec_type = config.sec_type meth = getattr(self.request_dispatcher.security, sec_type + '_get') _sec_config = meth(security_name).config if logger.isEnabledFor(TRACE1): logger.log(TRACE1, 'has_sec_config:[{}], security_name:[{}], _sec_config:[{}]'.format( has_sec_config, security_name, _sec_config)) if _sec_config: sec_config['sec_type'] = _sec_config['sec_type'] sec_config['username'] = _sec_config['username'] sec_config['password'] = _sec_config['password'] sec_config['password_type'] = _sec_config.get('password_type') wrapper_config = {'id':config.id, 'is_active':config.is_active, 'method':config.method, 'name':config.name, 'transport':config.transport, 'address':config.host + config.url_path, 'soap_action':config.soap_action, 'soap_version':config.soap_version} wrapper_config.update(sec_config) return HTTPSOAPWrapper(wrapper_config) def init_sql(self): """ Initializes SQL connections, first to ODB and then any user-defined ones. """ # We need a store first self.sql_pool_store = PoolStore() # Connect to ODB self.sql_pool_store[ZATO_ODB_POOL_NAME] = self.worker_config.odb_data self.odb = SessionWrapper() self.odb.init_session(self.sql_pool_store[ZATO_ODB_POOL_NAME]) # Any user-defined SQL connections left? for pool_name in self.worker_config.out_sql: config = self.worker_config.out_sql[pool_name]['config'] self.sql_pool_store[pool_name] = config def init_ftp(self): """ Initializes FTP connetions. The method replaces whatever value self.out_ftp previously had (initially this would be a ConfigDict of connection definitions). """ config_list = self.worker_config.out_ftp.get_config_list() self.worker_config.out_ftp = FTPStore() self.worker_config.out_ftp.add_params(config_list) def init_http_soap(self): """ Initializes plain HTTP/SOAP connections. """ for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) for name in config_dict: config = config_dict[name].config wrapper = self._http_soap_wrapper_from_config(config) config_dict[name].conn = wrapper # To make the API consistent with that of SQL connection pools config_dict[name].ping = wrapper.ping def _update_auth(self, msg, action_name, sec_type, visit_wrapper, keys=None): """ A common method for updating auth-related configuration. """ with self.update_lock: # Channels handler = getattr(self.request_dispatcher.security, 'on_broker_msg_' + action_name) handler(msg) for transport in('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) # Wrappers and static configuration for outgoing connections for name in config_dict.copy_keys(): config = config_dict[name].config wrapper = config_dict[name].conn if config['sec_type'] == sec_type: if keys: visit_wrapper(wrapper, msg, keys) else: visit_wrapper(wrapper, msg) def _visit_wrapper_edit(self, wrapper, msg, keys): """ Updates a given wrapper's security configuration. """ if wrapper.config['security_name'] == msg['old_name']: for key in keys: # All's good except for 'name', the msg's 'name' is known # as 'security_name' in wrapper's config. if key == 'name': key1 = 'security_name' key2 = key else: key1, key2 = key, key wrapper.config[key1] = msg[key2] wrapper.set_auth() def _visit_wrapper_delete(self, wrapper, msg): """ Deletes a wrapper. """ config_dict = getattr(self.worker_config, 'out_' + wrapper.config['transport']) if wrapper.config['security_name'] == msg['name']: del config_dict[wrapper.config['name']] def _visit_wrapper_change_password(self, wrapper, msg): """ Changes a wrapper's password. """ if wrapper.config['security_name'] == msg['name']: wrapper.config['password'] = msg['password'] wrapper.set_auth() # ############################################################################## def basic_auth_get(self, name): """ Returns the configuration of the HTTP Basic Auth security definition of the given name. """ self.request_dispatcher.security.basic_auth_get(name) def on_broker_msg_SECURITY_BASIC_AUTH_CREATE(self, msg, *args): """ Creates a new HTTP Basic Auth security definition """ self.request_dispatcher.security.on_broker_msg_SECURITY_BASIC_AUTH_CREATE(msg, *args) def on_broker_msg_SECURITY_BASIC_AUTH_EDIT(self, msg, *args): """ Updates an existing HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_BASIC_AUTH_DELETE(self, msg, *args): """ Deletes an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_delete) def on_broker_msg_SECURITY_BASIC_AUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_change_password) # ############################################################################## def tech_acc_get(self, name): """ Returns the configuration of the technical account of the given name. """ self.request_dispatcher.security.tech_acc_get(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_CREATE(self, msg, *args): """ Creates a new technical account. """ self.request_dispatcher.security.on_broker_msg_SECURITY_TECH_ACC_CREATE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_EDIT(self, msg, *args): """ Updates an existing technical account. """ self.request_dispatcher.security.on_broker_msg_SECURITY_TECH_ACC_EDIT(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_DELETE(self, msg, *args): """ Deletes a technical account. """ self.request_dispatcher.security.on_broker_msg_SECURITY_TECH_ACC_DELETE(msg, *args) def on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a technical account. """ self.request_dispatcher.security.on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(msg, *args) # ############################################################################## def wss_get(self, name): """ Returns the configuration of the WSS definition of the given name. """ self.request_dispatcher.security.wss_get(msg, *args) def on_broker_msg_SECURITY_WSS_CREATE(self, msg, *args): """ Creates a new WS-Security definition. """ self.request_dispatcher.security.on_broker_msg_SECURITY_WSS_CREATE(msg, *args) def on_broker_msg_SECURITY_WSS_EDIT(self, msg, *args): """ Updates an existing WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_edit, keys=('is_active', 'username', 'name', 'nonce_freshness_time', 'reject_expiry_limit', 'password_type', 'reject_empty_nonce_creat', 'reject_stale_tokens')) def on_broker_msg_SECURITY_WSS_DELETE(self, msg, *args): """ Deletes a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_delete) def on_broker_msg_SECURITY_WSS_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_change_password) # ############################################################################## def _on_message_invoke_service(self, msg, channel, action, args=None): """ Triggered by external processes, such as AMQP or the singleton's scheduler, creates a new service instance and invokes it. """ service_instance = self.server.service_store.new_instance(msg.service) service_instance.update(service_instance, self.server, self.broker_client, self, msg.cid, msg.payload, msg.payload, None, self.worker_config.simple_io, msg.data_format if hasattr(msg, 'data_format') else None) service_instance.pre_handle() service_instance.handle() if not isinstance(service_instance.response.payload, basestring): service_instance.response.payload = service_instance.response.payload.getvalue() service_instance.post_handle() if logger.isEnabledFor(logging.DEBUG): msg = 'Invoked [{0}], channel [{1}], action [{2}], response [{3}]'.format( msg.service, channel, action, repr(service_instance.response.payload)) logger.debug(msg) # ############################################################################## def on_broker_msg_SCHEDULER_JOB_EXECUTED(self, msg, args=None): return self._on_message_invoke_service(msg, 'scheduler', 'SCHEDULER_JOB_EXECUTED', args) def on_broker_msg_CHANNEL_AMQP_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, 'amqp', 'CHANNEL_AMQP_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_JMS_WMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, 'jms-wmq', 'CHANNEL_JMS_WMQ_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_ZMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, 'zmq', 'CHANNEL_ZMQ_MESSAGE_RECEIVED', args) # ############################################################################## def on_broker_msg_OUTGOING_SQL_CREATE_EDIT(self, msg, *args): """ Creates or updates an SQL connection, including changing its password. """ # Is it a rename? If so, delete the connection first if msg.get('old_name') and msg.get('old_name') != msg['name']: del self.sql_pool_store[msg['old_name']] self.sql_pool_store[msg['name']] = msg def on_broker_msg_OUTGOING_SQL_CHANGE_PASSWORD(self, msg, *args): """ Deletes an outgoing SQL connection pool and recreates it using the new password. """ self.sql_pool_store.change_password(msg['name'], msg['password']) def on_broker_msg_OUTGOING_SQL_DELETE(self, msg, *args): """ Deletes an outgoing SQL connection pool. """ del self.sql_pool_store[msg['name']] # ############################################################################## def on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(self, msg, *args): """ Creates or updates an HTTP/SOAP channel. """ # Security self.request_dispatcher.security.on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(msg, *args) # A mapping between a URL and a service handler = getattr(self.request_dispatcher, msg.transport + '_handler') handler.on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(msg, *args) def on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(self, msg, *args): """ Deletes an HTTP/SOAP channel. """ # Security self.request_dispatcher.security.on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(msg, *args) # A mapping between a URL and a service handler = getattr(self.request_dispatcher, msg.transport + '_handler') handler.on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(msg, *args) # ############################################################################## def _delete_outgoing_http_soap(self, name, transport, log_meth): """ Actually deletes an outgoing HTTP/SOAP connection. """ # Are we dealing with plain HTTP or SOAP? config_dict = getattr(self.worker_config, 'out_' + transport) # Delete the connection first, if it exists at all .. try: del config_dict[name] except(KeyError, AttributeError), e: log_meth('Could not delete an outgoing HTTP/SOAP connection, e:[{}]'.format(format_exc(e)))
class WorkerStore(BrokerMessageReceiver): """ Each worker thread has its own configuration store. The store is assigned to the thread's threading.local variable. All the methods assume the data's being already validated and sanitized by one of Zato's internal services. There are exactly two threads willing to access the data at any time - the worker thread this store belongs to - the background ZeroMQ thread which may wish to update the store's configuration hence the need for employing RLocks yet there shouldn't be much contention because configuration updates are extremaly rare when compared to regular access by worker threads. """ def __init__(self, worker_config=None, server=None): self.logger = logging.getLogger(self.__class__.__name__) self.is_ready = False self.worker_config = worker_config self.server = server self.update_lock = RLock() self.kvdb = server.kvdb self.broker_client = None def init(self): # Statistics maintenance self.stats_maint = MaintenanceTool(self.kvdb.conn) self.msg_ns_store = NamespaceStore() self.elem_path_store = ElemPathStore() self.xpath_store = XPathStore() # Message-related config - init_msg_ns_store must come before init_xpath_store # so the latter has access to the former's namespace map. self.init_msg_ns_store() self.init_elem_path_store() self.init_xpath_store() # Request dispatcher - matches URLs, checks security and dispatches HTTP # requests to services. self.request_dispatcher = RequestDispatcher( simple_io_config=self.worker_config.simple_io) self.request_dispatcher.url_data = URLData( deepcopy(self.worker_config.http_soap), self.server.odb.get_url_security(self.server.cluster_id, 'channel')[0], self.worker_config.basic_auth, self.worker_config.ntlm, self.worker_config.oauth, self.worker_config.tech_acc, self.worker_config.wss, self.kvdb, self.broker_client, self.server.odb, self.elem_path_store, self.xpath_store) self.request_dispatcher.request_handler = RequestHandler(self.server) # Create all the expected connections self.init_sql() self.init_ftp() self.init_http_soap() # All set, whoever is waiting for us, if anyone at all, can now proceed self.is_ready = True def filter(self, msg): # TODO: Fix it, worker doesn't need to accept all the messages return True def _http_soap_wrapper_from_config(self, config, has_sec_config=True): """ Creates a new HTTP/SOAP connection wrapper out of a configuration dictionary. """ security_name = config.get('security_name') sec_config = { 'security_name': security_name, 'sec_type': None, 'username': None, 'password': None, 'password_type': None } _sec_config = None # This will be set to True only if the method's invoked on a server's starting up if has_sec_config: # It's possible that there is no security config attached at all if security_name: _sec_config = config else: if security_name: sec_type = config.sec_type func = getattr(self.request_dispatcher.url_data, sec_type + '_get') _sec_config = func(security_name).config if logger.isEnabledFor(TRACE1): logger.log( TRACE1, 'has_sec_config:[{}], security_name:[{}], _sec_config:[{}]'. format(has_sec_config, security_name, _sec_config)) if _sec_config: sec_config['sec_type'] = _sec_config['sec_type'] sec_config['username'] = _sec_config['username'] sec_config['password'] = _sec_config['password'] sec_config['password_type'] = _sec_config.get('password_type') sec_config['salt'] = _sec_config.get('salt') wrapper_config = { 'id': config.id, 'is_active': config.is_active, 'method': config.method, 'data_format': config.get('data_format'), 'name': config.name, 'transport': config.transport, 'address_host': config.host, 'address_url_path': config.url_path, 'soap_action': config.soap_action, 'soap_version': config.soap_version, 'ping_method': config.ping_method, 'pool_size': config.pool_size, 'serialization_type': config.serialization_type } wrapper_config.update(sec_config) if wrapper_config[ 'serialization_type'] == HTTP_SOAP_SERIALIZATION_TYPE.SUDS.id: wrapper_config['queue_build_cap'] = float( self.server.fs_server_config.misc.suds_soap_queue_build_cap) wrapper = SudsSOAPWrapper(wrapper_config) wrapper.build_client_queue() return wrapper return HTTPSOAPWrapper(wrapper_config) # ############################################################################## def init_sql(self): """ Initializes SQL connections, first to ODB and then any user-defined ones. """ # We need a store first self.sql_pool_store = PoolStore() # Connect to ODB self.sql_pool_store[ZATO_ODB_POOL_NAME] = self.worker_config.odb_data self.odb = SessionWrapper() self.odb.init_session(ZATO_ODB_POOL_NAME, self.worker_config.odb_data, self.sql_pool_store[ZATO_ODB_POOL_NAME].pool) # Any user-defined SQL connections left? for pool_name in self.worker_config.out_sql: config = self.worker_config.out_sql[pool_name]['config'] self.sql_pool_store[pool_name] = config def init_ftp(self): """ Initializes FTP connetions. The method replaces whatever value self.out_ftp previously had (initially this would be a ConfigDict of connection definitions). """ config_list = self.worker_config.out_ftp.get_config_list() self.worker_config.out_ftp = FTPStore() self.worker_config.out_ftp.add_params(config_list) def init_http_soap(self): """ Initializes plain HTTP/SOAP connections. """ for transport in ('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) for name in config_dict: config = config_dict[name].config wrapper = self._http_soap_wrapper_from_config(config) config_dict[name].conn = wrapper # To make the API consistent with that of SQL connection pools config_dict[name].ping = wrapper.ping # ############################################################################## def init_msg_ns_store(self): for k, v in self.worker_config.msg_ns.items(): self.msg_ns_store.create(k, v.config) def init_xpath_store(self): for k, v in self.worker_config.xpath.items(): self.xpath_store.create(k, v.config, self.msg_ns_store.ns_map) def init_elem_path_store(self): for k, v in self.worker_config.elem_path.items(): self.elem_path_store.create(k, v.config, {}) # ############################################################################## def _update_auth(self, msg, action_name, sec_type, visit_wrapper, keys=None): """ A common method for updating auth-related configuration. """ with self.update_lock: # Channels handler = getattr(self.request_dispatcher.url_data, 'on_broker_msg_' + action_name) handler(msg) for transport in ('soap', 'plain_http'): config_dict = getattr(self.worker_config, 'out_' + transport) # Wrappers and static configuration for outgoing connections for name in config_dict.copy_keys(): config = config_dict[name].config wrapper = config_dict[name].conn if config['sec_type'] == sec_type: if keys: visit_wrapper(wrapper, msg, keys) else: visit_wrapper(wrapper, msg) def _visit_wrapper_edit(self, wrapper, msg, keys): """ Updates a given wrapper's security configuration. """ if wrapper.config['security_name'] == msg['old_name']: for key in keys: # All's good except for 'name', the msg's 'name' is known # as 'security_name' in wrapper's config. if key == 'name': key1 = 'security_name' key2 = key else: key1, key2 = key, key wrapper.config[key1] = msg[key2] wrapper.set_auth() def _visit_wrapper_delete(self, wrapper, msg): """ Deletes a wrapper. """ config_dict = getattr(self.worker_config, 'out_' + wrapper.config['transport']) if wrapper.config['security_name'] == msg['name']: del config_dict[wrapper.config['name']] def _visit_wrapper_change_password(self, wrapper, msg): """ Changes a wrapper's password. """ if wrapper.config['security_name'] == msg['name']: wrapper.config['password'] = msg['password'] wrapper.set_auth() # ############################################################################## def ntlm_get(self, name): """ Returns the configuration of the NTLM security definition of the given name. """ self.request_dispatcher.url_data.ntlm_get(name) def on_broker_msg_SECURITY_NTLM_CREATE(self, msg, *args): """ Creates a new NTLM security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_NTLM_CREATE( msg, *args) def on_broker_msg_SECURITY_NTLM_EDIT(self, msg, *args): """ Updates an existing NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.ntlm, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_NTLM_DELETE(self, msg, *args): """ Deletes an NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.ntlm, self._visit_wrapper_delete) def on_broker_msg_SECURITY_NTLM_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an NTLM security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.ntlm, self._visit_wrapper_change_password) # ############################################################################## def basic_auth_get(self, name): """ Returns the configuration of the HTTP Basic Auth security definition of the given name. """ self.request_dispatcher.url_data.basic_auth_get(name) def on_broker_msg_SECURITY_BASIC_AUTH_CREATE(self, msg, *args): """ Creates a new HTTP Basic Auth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_BASIC_AUTH_CREATE( msg, *args) def on_broker_msg_SECURITY_BASIC_AUTH_EDIT(self, msg, *args): """ Updates an existing HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_BASIC_AUTH_DELETE(self, msg, *args): """ Deletes an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_delete) def on_broker_msg_SECURITY_BASIC_AUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an HTTP Basic Auth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.basic_auth, self._visit_wrapper_change_password) # ############################################################################## def oauth_get(self, name): """ Returns the configuration of the OAuth security definition of the given name. """ self.request_dispatcher.url_data.oauth_get(name) def on_broker_msg_SECURITY_OAUTH_CREATE(self, msg, *args): """ Creates a new OAuth security definition """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_OAUTH_CREATE( msg, *args) def on_broker_msg_SECURITY_OAUTH_EDIT(self, msg, *args): """ Updates an existing OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_edit, keys=('is_active', 'username', 'name')) def on_broker_msg_SECURITY_OAUTH_DELETE(self, msg, *args): """ Deletes an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_delete) def on_broker_msg_SECURITY_OAUTH_CHANGE_PASSWORD(self, msg, *args): """ Changes password of an OAuth security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.oauth, self._visit_wrapper_change_password) # ############################################################################## def tech_acc_get(self, name): """ Returns the configuration of the technical account of the given name. """ self.request_dispatcher.url_data.tech_acc_get(name) def on_broker_msg_SECURITY_TECH_ACC_CREATE(self, msg, *args): """ Creates a new technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CREATE( msg, *args) def on_broker_msg_SECURITY_TECH_ACC_EDIT(self, msg, *args): """ Updates an existing technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_EDIT( msg, *args) def on_broker_msg_SECURITY_TECH_ACC_DELETE(self, msg, *args): """ Deletes a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_DELETE( msg, *args) def on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a technical account. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_TECH_ACC_CHANGE_PASSWORD( msg, *args) # ############################################################################## def wss_get(self, name): """ Returns the configuration of the WSS definition of the given name. """ self.request_dispatcher.url_data.wss_get(name) def on_broker_msg_SECURITY_WSS_CREATE(self, msg, *args): """ Creates a new WS-Security definition. """ self.request_dispatcher.url_data.on_broker_msg_SECURITY_WSS_CREATE( msg, *args) def on_broker_msg_SECURITY_WSS_EDIT(self, msg, *args): """ Updates an existing WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_edit, keys=('is_active', 'username', 'name', 'nonce_freshness_time', 'reject_expiry_limit', 'password_type', 'reject_empty_nonce_creat', 'reject_stale_tokens')) def on_broker_msg_SECURITY_WSS_DELETE(self, msg, *args): """ Deletes a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_delete) def on_broker_msg_SECURITY_WSS_CHANGE_PASSWORD(self, msg, *args): """ Changes the password of a WS-Security definition. """ self._update_auth(msg, code_to_name[msg.action], security_def_type.wss, self._visit_wrapper_change_password) # ############################################################################## def _set_service_response_data(self, service, **ignored): if not isinstance(service.response.payload, basestring): service.response.payload = service.response.payload.getvalue() def _on_message_invoke_service(self, msg, channel, action, args=None): """ Triggered by external processes, such as AMQP or the singleton's scheduler, creates a new service instance and invokes it. """ # WSGI environment is the best place we have to store raw msg in wsgi_environ = {'zato.request_ctx.async_msg': msg} service = self.server.service_store.new_instance_by_name(msg.service) service.update_handle(self._set_service_response_data, service, msg.payload, channel, msg.get('data_format'), msg.get('transport'), self.server, self.broker_client, self, msg.cid, self.worker_config.simple_io, job_type=msg.get('job_type'), wsgi_environ=wsgi_environ) # ############################################################################## def on_broker_msg_SCHEDULER_JOB_EXECUTED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.SCHEDULER, 'SCHEDULER_JOB_EXECUTED', args) def on_broker_msg_CHANNEL_AMQP_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service( msg, CHANNEL.AMQP, 'CHANNEL_AMQP_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_JMS_WMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service( msg, CHANNEL.JMS_WMQ, 'CHANNEL_JMS_WMQ_MESSAGE_RECEIVED', args) def on_broker_msg_CHANNEL_ZMQ_MESSAGE_RECEIVED(self, msg, args=None): return self._on_message_invoke_service(msg, CHANNEL.ZMQ, 'CHANNEL_ZMQ_MESSAGE_RECEIVED', args) # ############################################################################## def on_broker_msg_OUTGOING_SQL_CREATE_EDIT(self, msg, *args): """ Creates or updates an SQL connection, including changing its password. """ # Is it a rename? If so, delete the connection first if msg.get('old_name') and msg.get('old_name') != msg['name']: del self.sql_pool_store[msg['old_name']] self.sql_pool_store[msg['name']] = msg def on_broker_msg_OUTGOING_SQL_CHANGE_PASSWORD(self, msg, *args): """ Deletes an outgoing SQL connection pool and recreates it using the new password. """ self.sql_pool_store.change_password(msg['name'], msg['password']) def on_broker_msg_OUTGOING_SQL_DELETE(self, msg, *args): """ Deletes an outgoing SQL connection pool. """ del self.sql_pool_store[msg['name']] # ############################################################################## def on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT(self, msg, *args): """ Creates or updates an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_CREATE_EDIT( msg, *args) def on_broker_msg_CHANNEL_HTTP_SOAP_DELETE(self, msg, *args): """ Deletes an HTTP/SOAP channel. """ self.request_dispatcher.url_data.on_broker_msg_CHANNEL_HTTP_SOAP_DELETE( msg, *args) # ############################################################################## def _delete_outgoing_http_soap(self, name, transport, log_func): """ Actually deletes an outgoing HTTP/SOAP connection. """ # Are we dealing with plain HTTP or SOAP? config_dict = getattr(self.worker_config, 'out_' + transport) # Delete the connection first, if it exists at all .. try: try: wrapper = config_dict[name].conn except (KeyError, AttributeError), e: log_func('Could not access a wrapper, e:[{}]'.format( format_exc(e))) else: