def __init__(self, bus_config): self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **bus_config) self._exchange = kombu.Exchange(bus_config['subscribe_exchange_name'], type='headers') self._queue = kombu.Queue(exclusive=True) self._pubsub = Pubsub()
def __init__(self, config): self._apps = [] self.config = config self._is_running = False self._should_delay_reconnect = True self._should_stop = False self._pubsub = Pubsub() self.client = ARIClientProxy(**config['connection'])
def __init__(self, global_config): self._events_pubsub = Pubsub() self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **global_config['bus']) self._exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exclusive=True) self._is_running = False
def __init__(self, global_config): self._events_pubsub = Pubsub() self._userevent_pubsub = Pubsub() self._events_pubsub.subscribe('UserEvent', lambda message: self._userevent_pubsub.publish(message['UserEvent'], message)) self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format(**global_config['bus']) exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exchange=exchange, routing_key=self._KEY, exclusive=True)
def __init__(self, amid_client, ari_client, services, state_factory, state_persistor, xivo_uuid): self.ari = ari_client self.amid = amid_client self.services = services self.xivo_uuid = xivo_uuid self.stasis_start_pubsub = Pubsub() self.stasis_start_pubsub.set_exception_handler(self.invalid_event) self.hangup_pubsub = Pubsub() self.hangup_pubsub.set_exception_handler(self.invalid_event) self.state_factory = state_factory self.state_persistor = state_persistor
def __init__(self, state_factory): self.uuid = str(uuid.uuid4()) self._state_factory = state_factory self.relocated_channel = None self.initiator_channel = None self.recipient_channel = None self.initiator = None self.completions = None self.set_state('ready') self._lock = threading.Lock() self.events = Pubsub()
class CoreBusConsumer(ConsumerMixin): def __init__(self, global_config): self._events_pubsub = Pubsub() self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **global_config['bus']) self._exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exclusive=True) self._is_running = False def run(self): logger.info("Running AMQP consumer") with Connection(self._bus_url) as connection: self.connection = connection super().run() def get_consumers(self, Consumer, channel): return [Consumer(self._queue, callbacks=[self._on_bus_message])] def on_connection_error(self, exc, interval): super().on_connection_error(exc, interval) self._is_running = False def on_connection_revived(self): super().on_connection_revived() self._is_running = True def is_running(self): return self._is_running def provide_status(self, status): status['bus_consumer']['status'] = Status.ok if self.is_running( ) else Status.fail def on_ami_event(self, event_type, callback): logger.debug('Added callback on AMI event "%s"', event_type) self._queue.bindings.add( binding(self._exchange, routing_key='ami.{}'.format(event_type))) self._events_pubsub.subscribe(event_type, callback) def _on_bus_message(self, body, message): event = body['data'] try: event_type = event['Event'] except KeyError: logger.error('Invalid AMI event message received: %s', event) else: self._events_pubsub.publish(event_type, event) finally: message.ack()
def __init__(self, global_config): self._events_pubsub = Pubsub() self._userevent_pubsub = Pubsub() self._events_pubsub.subscribe( 'UserEvent', lambda message: self._userevent_pubsub.publish( message['UserEvent'], message)) self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **global_config['bus']) exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exchange=exchange, routing_key=self._KEY, exclusive=True)
class BusConsumer(ConsumerMixin): def __init__(self, bus_config): self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **bus_config) self._exchange = kombu.Exchange(bus_config['subscribe_exchange_name'], type='headers') self._queue = kombu.Queue(exclusive=True) self._pubsub = Pubsub() def run(self): logger.info("Running AMQP consumer") with kombu.Connection(self._bus_url) as connection: self.connection = connection # For internal usage super().run() def get_consumers(self, Consumer, channel): return [Consumer(self._queue, callbacks=[self._on_bus_message])] def on_event(self, event_name, callback): logger.debug('Added callback on event "%s"', event_name) arguments = {'x-match': 'all', 'name': event_name} self._queue.bindings.add( kombu.binding(self._exchange, arguments=arguments)) self._pubsub.subscribe(event_name, callback) def _on_bus_message(self, body, message): try: event_type = body['name'] event = body['data'] except KeyError: logger.error('Invalid event message received: %s', event) message.reject() return try: self._pubsub.publish(event_type, event) except Exception: message.reject() return message.ack()
class CoreBusConsumer(ConsumerMixin): _KEY = 'ami.*' def __init__(self, global_config): self._events_pubsub = Pubsub() self._userevent_pubsub = Pubsub() self._events_pubsub.subscribe( 'UserEvent', lambda message: self._userevent_pubsub.publish( message['UserEvent'], message)) self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **global_config['bus']) exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exchange=exchange, routing_key=self._KEY, exclusive=True) def run(self): logger.info("Running AMQP consumer") with Connection(self._bus_url) as connection: self.connection = connection super(CoreBusConsumer, self).run() def get_consumers(self, Consumer, channel): return [ Consumer(self._queue, callbacks=[self._on_bus_message]), ] def on_ami_event(self, event_type, callback): self._events_pubsub.subscribe(event_type, callback) def on_ami_userevent(self, userevent_type, callback): self._userevent_pubsub.subscribe(userevent_type, callback) def _on_bus_message(self, body, message): event = body['data'] event_type = event['Event'] self._events_pubsub.publish(event_type, event) message.ack()
def __init__(self, global_config): self._all_events_pubsub = Pubsub() self._is_running = False self.connection = None self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format( **global_config['bus']) self._upstream_exchange = kombu.Exchange( global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._exchange = kombu.Exchange( global_config['bus']['exchange_headers_name'], type='headers') self._consumers = {} self._new_consumers = deque() self._stale_consumers = deque() self._updated_consumers = deque()
class CoreBusConsumer(ConsumerMixin): _KEY = 'ami.*' def __init__(self, global_config): self._events_pubsub = Pubsub() self._userevent_pubsub = Pubsub() self._events_pubsub.subscribe('UserEvent', lambda message: self._userevent_pubsub.publish(message['UserEvent'], message)) self._bus_url = 'amqp://{username}:{password}@{host}:{port}//'.format(**global_config['bus']) exchange = Exchange(global_config['bus']['exchange_name'], type=global_config['bus']['exchange_type']) self._queue = kombu.Queue(exchange=exchange, routing_key=self._KEY, exclusive=True) def run(self): logger.info("Running AMQP consumer") with Connection(self._bus_url) as connection: self.connection = connection super(CoreBusConsumer, self).run() def get_consumers(self, Consumer, channel): return [ Consumer(self._queue, callbacks=[self._on_bus_message]), ] def on_ami_event(self, event_type, callback): self._events_pubsub.subscribe(event_type, callback) def on_ami_userevent(self, userevent_type, callback): self._userevent_pubsub.subscribe(userevent_type, callback) def _on_bus_message(self, body, message): event = body['data'] event_type = event['Event'] self._events_pubsub.publish(event_type, event) message.ack()
def set_wazo_instance(context, instance_name, instance_config, debug_config): logger.info("Adding instance %s...", instance_name) context.token_pubsub = Pubsub() setup.setup_config(context, instance_config) logger.info('wazo_host: %s', context.wazo_config['wazo_host']) logger.debug("setup ssh client...") setup.setup_ssh_client(context) logger.debug("setup auth token...") setup.setup_auth_token(context) logger.debug("setup agentd client...") setup.setup_agentd_client(context) logger.debug("setup amid client...") setup.setup_amid_client(context) logger.debug("setup ari client...") setup.setup_ari_client(context) logger.debug("setup call logd client...") setup.setup_call_logd_client(context) logger.debug("setup calld client...") setup.setup_calld_client(context) logger.debug("setup chatd client...") setup.setup_chatd_client(context) logger.debug("setup confd client...") setup.setup_confd_client(context) logger.debug("setup dird client...") setup.setup_dird_client(context) logger.debug("setup provd client...") setup.setup_provd_client(context) logger.debug("setup setupd client...") setup.setup_setupd_client(context) logger.debug("setup websocketd client...") setup.setup_websocketd_client(context) logger.debug("setup tenant...") setup.setup_tenant(context) logger.debug("setup consul...") setup.setup_consul(context) logger.debug("setup remote sysutils...") setup.setup_remote_sysutils(context) logger.debug("setup helpers...") setup.setup_helpers(context) logger.debug("setup phone...") setup.setup_phone(context, debug_config['linphone'])
def setUp(self): self.pubsub = Pubsub()
class Relocate: def __init__(self, state_factory): self.uuid = str(uuid.uuid4()) self._state_factory = state_factory self.relocated_channel = None self.initiator_channel = None self.recipient_channel = None self.recipient_variables = {} self.initiator = None self.completions = None self.set_state('ready') self._lock = threading.Lock() self.events = Pubsub() def set_state(self, state_name): logger.debug('Relocate %s: setting state to "%s"', self.uuid, state_name) self._state = self._state_factory.make(state_name) if state_name == 'ended': self.end() def initiate(self, destination): logger.debug('Relocate %s: initiate', self.uuid) self._state.initiate(self, destination) def recipient_answered(self): logger.debug('Relocate %s: recipient answered', self.uuid) self._state.recipient_answered(self) def relocated_answered(self): logger.debug('Relocate %s: relocated answered', self.uuid) self._state.relocated_answered(self) def initiator_hangup(self): logger.debug('Relocate %s: initiator hangup', self.uuid) self._state.initiator_hangup(self) def recipient_hangup(self): logger.debug('Relocate %s: recipient hangup', self.uuid) self._state.recipient_hangup(self) def relocated_hangup(self): logger.debug('Relocate %s: relocated hangup', self.uuid) self._state.relocated_hangup(self) def cancel(self): logger.debug('Relocate %s: cancel', self.uuid) self._state.cancel(self) def complete(self): logger.debug('Relocate %s: complete', self.uuid) self._state.complete(self) def end(self): logger.debug('Relocate %s: end', self.uuid) self.events.publish('ended', self) def role(self, channel_id): if channel_id == self.relocated_channel: return RelocateRole.relocated elif channel_id == self.initiator_channel: return RelocateRole.initiator elif channel_id == self.recipient_channel: return RelocateRole.recipient else: raise KeyError(channel_id) @contextmanager def locked(self): logger.debug('Relocate %s: acquired lock', self.uuid) with self._lock: yield logger.debug('Relocate %s: released lock', self.uuid)
class TestPubsub(unittest.TestCase): def setUp(self): self.pubsub = Pubsub() def test_subscribe_and_publish(self): callback = Mock() self.pubsub.subscribe(SOME_TOPIC, callback) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) callback.assert_called_once_with(SOME_MESSAGE) def test_multiple_subscribe_on_same_topic_and_one_publish(self): callback_1 = Mock() callback_2 = Mock() self.pubsub.subscribe(SOME_TOPIC, callback_1) self.pubsub.subscribe(SOME_TOPIC, callback_2) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) callback_1.assert_called_once_with(SOME_MESSAGE) callback_2.assert_called_once_with(SOME_MESSAGE) def test_multiple_subscribe_on_different_topics_and_two_publish(self): callback = Mock() message_1 = Mock() message_2 = Mock() topic_1 = 'abcd' topic_2 = 'efgh' self.pubsub.subscribe(topic_1, callback) self.pubsub.subscribe(topic_2, callback) self.pubsub.publish(topic_1, message_1) self.pubsub.publish(topic_2, message_2) callback.assert_any_call(message_1) callback.assert_any_call(message_2) self.assertEquals(callback.call_count, 2) def test_unsubscribe_when_never_subscribed(self): callback = Mock() try: self.pubsub.unsubscribe(SOME_TOPIC, callback) except Exception: self.fail('unsubscribe should not raise exceptions') def test_unsubscribed_when_subscribed(self): callback = Mock() self.pubsub.subscribe(SOME_TOPIC, callback) self.pubsub.unsubscribe(SOME_TOPIC, callback) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) self.assertEquals(callback.call_count, 0) def publish_when_nobody_subscribed(self): try: self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) except Exception: self.fail('publish should not raise exceptions') def test_unsubscribe_when_multiple_subscribers_on_same_topic(self): callback_1 = Mock() callback_2 = Mock() self.pubsub.subscribe(SOME_TOPIC, callback_1) self.pubsub.subscribe(SOME_TOPIC, callback_2) self.pubsub.unsubscribe(SOME_TOPIC, callback_1) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) assert_that(not_(callback_1.called)) callback_2.assert_called_once_with(SOME_MESSAGE) def test_when_exception_then_exception_is_handled(self): callback = Mock() exception = callback.side_effect = Exception() handler = Mock() self.pubsub.set_exception_handler(handler) self.pubsub.subscribe(SOME_TOPIC, callback) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) handler.assert_called_once_with(callback, SOME_MESSAGE, exception) @patch('xivo.pubsub.logger') def test_when_exception_then_exception_is_logged_by_default(self, logger): callback = Mock() exception = callback.side_effect = Exception() self.pubsub.subscribe(SOME_TOPIC, callback) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) logger.exception.assert_called_once_with(exception) def test_when_exception_then_other_callbacks_are_run(self): callback_1, callback_2, callback_3 = Mock(), Mock(), Mock() callback_2.side_effect = Exception() self.pubsub.subscribe(SOME_TOPIC, callback_1) self.pubsub.subscribe(SOME_TOPIC, callback_2) self.pubsub.subscribe(SOME_TOPIC, callback_3) self.pubsub.publish(SOME_TOPIC, SOME_MESSAGE) assert_that(callback_1.called) assert_that(callback_3.called)
class SubscriptionService: # NOTE(sileht): We share the pubsub object, so plugin that instanciate # another service (like push mobile) will continue work. pubsub = Pubsub() def __init__(self, config): self._engine = create_engine(config['db_uri']) self._Session = scoped_session(sessionmaker()) self._Session.configure(bind=self._engine) def close(self): self._Session.close() self._engine.dispose() @contextmanager def rw_session(self): session = self._Session() try: yield session session.commit() except BaseException: session.rollback() raise finally: self._Session.remove() @contextmanager def ro_session(self): session = self._Session() try: yield session finally: self._Session.remove() def list(self, owner_tenant_uuids=None, owner_user_uuid=None, search_metadata=None): with self.ro_session() as session: query = session.query(Subscription) if owner_tenant_uuids: query = query.filter( Subscription.owner_tenant_uuid.in_(owner_tenant_uuids)) if owner_user_uuid: query = query.filter( Subscription.owner_user_uuid == owner_user_uuid) if search_metadata: subquery = (session.query( SubscriptionMetadatum.subscription_uuid).filter( or_(*[ and_( SubscriptionMetadatum.key == key, SubscriptionMetadatum.value == value, ) for key, value in search_metadata.items() ])).group_by( SubscriptionMetadatum.subscription_uuid).having( func.count(distinct(SubscriptionMetadatum.key)) == len(search_metadata))) query = query.filter(Subscription.uuid.in_(subquery)) return query.all() def _get(self, session, subscription_uuid, owner_tenant_uuids=None, owner_user_uuid=None): query = session.query(Subscription) query = query.filter(Subscription.uuid == subscription_uuid) if owner_tenant_uuids: query = query.filter( Subscription.owner_tenant_uuid.in_(owner_tenant_uuids)) if owner_user_uuid: query = query.filter( Subscription.owner_user_uuid == owner_user_uuid) result = query.one_or_none() if result is None: raise NoSuchSubscription(subscription_uuid) return result def get(self, subscription_uuid, owner_tenant_uuids=None, owner_user_uuid=None): with self.ro_session() as session: return self._get(session, subscription_uuid, owner_tenant_uuids, owner_user_uuid) def create(self, new_subscription): with self.rw_session() as session: subscription = Subscription() subscription.from_schema(**new_subscription) subscription = session.merge(subscription) session.flush() session.expire_all() subscription.make_transient() self.pubsub.publish('created', subscription) return subscription def update( self, subscription_uuid, new_subscription, owner_tenant_uuids=None, owner_user_uuid=None, ): with self.rw_session() as session: subscription = self._get(session, subscription_uuid, owner_tenant_uuids, owner_user_uuid) subscription.clear_relations() session.flush() subscription.from_schema(**new_subscription) session.flush() session.expire_all() subscription.make_transient() self.pubsub.publish('updated', subscription) return subscription def delete(self, subscription_uuid, owner_tenant_uuids=None, owner_user_uuid=None): with self.rw_session() as session: subscription = self._get(session, subscription_uuid, owner_tenant_uuids, owner_user_uuid) session.delete(subscription) self.pubsub.publish('deleted', subscription) def get_logs( self, subscription_uuid, from_date=None, limit=None, offset=None, order='started_at', direction='desc', search=None, ): with self.ro_session() as session: query = session.query(SubscriptionLog).filter( SubscriptionLog.subscription_uuid == subscription_uuid) if from_date is not None: query = query.filter(SubscriptionLog.started_at >= from_date) order_column = getattr(SubscriptionLog, order) order_column = (order_column.asc() if direction == 'asc' else order_column.desc()) query = query.order_by(order_column) if limit is not None: query = query.limit(limit) if offset is not None: query = query.offset(offset) if search is not None: # TODO(sileht): search is not implemented yet logger.warning("search parameter have been used while " "not implemented") pass return query.all() def create_hook_log( self, uuid, subscription_uuid, status, attempts, max_attempts, started_at, ended_at, event, detail, ): with self.rw_session() as session: hooklog = SubscriptionLog( uuid=uuid, subscription_uuid=subscription_uuid, status=status, attempts=attempts, max_attempts=max_attempts, started_at=started_at, ended_at=ended_at, event=event, detail=detail, ) session.add(hooklog) try: session.commit() except exc.IntegrityError as e: if "violates foreign key constraint" in str(e): logger.warning( "subscription %s have been deleted in the meantime", subscription_uuid, ) session.rollback() else: raise
def __init__(self): self.token_pubsub = Pubsub()
class TransfersStasis(object): def __init__(self, amid_client, ari_client, services, state_factory, state_persistor, xivo_uuid): self.ari = ari_client self.amid = amid_client self.services = services self.xivo_uuid = xivo_uuid self.stasis_start_pubsub = Pubsub() self.stasis_start_pubsub.set_exception_handler(self.invalid_event) self.hangup_pubsub = Pubsub() self.hangup_pubsub.set_exception_handler(self.invalid_event) self.state_factory = state_factory self.state_persistor = state_persistor def subscribe(self): self.ari.on_application_registered(APPLICATION_NAME, self.process_lost_hangups) self.ari.on_channel_event('ChannelEnteredBridge', self.release_hangup_lock) self.ari.on_channel_event('ChannelDestroyed', self.bypass_hangup_lock_from_source) self.ari.on_bridge_event('BridgeDestroyed', self.clean_bridge_variables) self.ari.on_channel_event('ChannelLeftBridge', self.clean_bridge) self.ari.on_channel_event('StasisStart', self.stasis_start) self.stasis_start_pubsub.subscribe('transfer_recipient_called', self.transfer_recipient_answered) self.stasis_start_pubsub.subscribe('create_transfer', self.create_transfer) self.ari.on_channel_event('ChannelDestroyed', self.hangup) self.ari.on_channel_event('StasisEnd', self.hangup) self.hangup_pubsub.subscribe(TransferRole.recipient, self.recipient_hangup) self.hangup_pubsub.subscribe(TransferRole.initiator, self.initiator_hangup) self.hangup_pubsub.subscribe(TransferRole.transferred, self.transferred_hangup) self.ari.on_channel_event('ChannelCallerId', self.update_transfer_caller_id) def invalid_event(self, _, __, exception): if isinstance(exception, InvalidEvent): event = exception.event logger.error('invalid stasis event received: %s', event) elif (isinstance(exception, XiVOAmidError) or isinstance(exception, TransferException)): self.handle_error(exception) else: raise def handle_error(self, exception): logger.error('%s: %s', exception.message, exception.details) def process_lost_hangups(self): transfers = list(self.state_persistor.list()) logger.debug('Processing lost hangups since last stop...') for transfer in transfers: transfer_state = self.state_factory.make(transfer) if not ari_helpers.channel_exists(self.ari, transfer.transferred_call): logger.debug('Transferred hangup from transfer %s', transfer.id) transfer_state = transfer_state.transferred_hangup() if not ari_helpers.channel_exists(self.ari, transfer.initiator_call): logger.debug('Initiator hangup from transfer %s', transfer.id) transfer_state = transfer_state.initiator_hangup() if not ari_helpers.channel_exists(self.ari, transfer.recipient_call): logger.debug('Recipient hangup from transfer %s', transfer.id) transfer_state = transfer_state.recipient_hangup() logger.debug('Done.') def stasis_start(self, event_objects, event): channel = event_objects['channel'] try: app_action = event['args'][1] except IndexError: logger.debug('ignoring StasisStart event: channel %s, app %s, args %s', event['channel']['name'], event['application'], event['args']) return self.stasis_start_pubsub.publish(app_action, (channel, event)) def hangup(self, channel, event): try: transfer = self.state_persistor.get_by_channel(channel.id) except KeyError: logger.debug('ignoring StasisEnd event: channel %s, app %s', event['channel']['name'], event['application']) return transfer_role = transfer.role(channel.id) self.hangup_pubsub.publish(transfer_role, transfer) def transfer_recipient_answered(self, (channel, event)): event = TransferRecipientAnsweredEvent(event) try: transfer_bridge = self.ari.bridges.get(bridgeId=event.transfer_bridge) transfer_bridge.addChannel(channel=channel.id) except ARINotFound: logger.error('recipient answered, but transfer was hung up') return try: transfer = self.state_persistor.get(event.transfer_bridge) except KeyError: logger.debug('recipient answered, but transfer was abandoned') for channel_id in transfer_bridge.json['channels']: try: ari_helpers.unring_initiator_call(self.ari, channel_id) except ARINotFound: pass else: logger.debug('recipient answered, transfer continues normally') transfer_state = self.state_factory.make(transfer) transfer_state.recipient_answer()
class SubscriptionService(object): def __init__(self, config): engine = create_engine(config['db_uri']) self._Session = scoped_session(sessionmaker()) self._Session.configure(bind=engine) self.pubsub = Pubsub() @contextmanager def rw_session(self): session = self._Session() try: yield session session.commit() except: session.rollback() raise finally: self._Session.remove() @contextmanager def ro_session(self): session = self._Session() try: yield session finally: self._Session.remove() def list(self, owner_user_uuid=None, search_metadata=None): with self.ro_session() as session: query = session.query(Subscription) if owner_user_uuid: query = query.filter( Subscription.owner_user_uuid == owner_user_uuid) if search_metadata: subquery = (session.query( SubscriptionMetadatum.subscription_uuid).filter( or_(*[ and_(SubscriptionMetadatum.key == key, SubscriptionMetadatum.value == value) for key, value in search_metadata.items() ])).group_by( SubscriptionMetadatum.subscription_uuid).having( func.count(distinct(SubscriptionMetadatum.key)) == len(search_metadata))) query = query.filter(Subscription.uuid.in_(subquery)) return query.all() def get(self, subscription_uuid): with self.ro_session() as session: result = session.query(Subscription).get(subscription_uuid) if result is None: raise NoSuchSubscription(subscription_uuid) return result def get_as_user(self, subscription_uuid, owner_user_uuid): self._assert_subscription_owned_by_user(subscription_uuid, owner_user_uuid) return self.get(subscription_uuid) def create(self, subscription): with self.rw_session() as session: new_subscription = Subscription(**subscription) session.add(new_subscription) self.pubsub.publish('created', new_subscription) return new_subscription def update(self, subscription_uuid, new_subscription): with self.rw_session() as session: subscription = session.query(Subscription).get(subscription_uuid) if subscription is None: raise NoSuchSubscription(subscription_uuid) subscription.clear_relations() session.flush() subscription.update(**new_subscription) session.flush() self.pubsub.publish('updated', subscription) session.expunge_all() return subscription def update_as_user(self, subscription_uuid, new_subscription, owner_user_uuid): self._assert_subscription_owned_by_user(subscription_uuid, owner_user_uuid) self.update(subscription_uuid, new_subscription) def delete(self, subscription_uuid): with self.rw_session() as session: subscription = session.query(Subscription).get(subscription_uuid) if subscription is None: raise NoSuchSubscription(subscription_uuid) session.query(Subscription).filter( Subscription.uuid == subscription_uuid).delete() self.pubsub.publish('deleted', subscription) def delete_as_user(self, subscription_uuid, owner_user_uuid): self._assert_subscription_owned_by_user(subscription_uuid, owner_user_uuid) self.delete(subscription_uuid) def _assert_subscription_owned_by_user(self, subscription_uuid, user_uuid): with self.ro_session() as session: subscription = (session.query(Subscription).filter( Subscription.uuid == subscription_uuid).filter( Subscription.owner_user_uuid == user_uuid).first()) if subscription is None: raise NoSuchSubscription(subscription_uuid)
def __init__(self, config): engine = create_engine(config['db_uri']) self._Session = scoped_session(sessionmaker()) self._Session.configure(bind=engine) self.pubsub = Pubsub()
class TransfersStasis: def __init__(self, amid_client, ari_client, services, state_factory, state_persistor, xivo_uuid): self.ari = ari_client self.amid = amid_client self.services = services self.xivo_uuid = xivo_uuid self.stasis_start_pubsub = Pubsub() self.stasis_start_pubsub.set_exception_handler(self.invalid_event) self.hangup_pubsub = Pubsub() self.hangup_pubsub.set_exception_handler(self.invalid_event) self.state_factory = state_factory self.state_persistor = state_persistor def subscribe(self): self.ari.on_application_registered(DEFAULT_APPLICATION_NAME, self.process_lost_hangups) self.ari.on_channel_event('ChannelEnteredBridge', self.release_hangup_lock) self.ari.on_channel_event('ChannelDestroyed', self.bypass_hangup_lock_from_source) self.ari.on_bridge_event('BridgeDestroyed', self.clean_bridge_variables) self.ari.on_channel_event('ChannelLeftBridge', self.clean_bridge) self.ari.on_channel_event('StasisStart', self.stasis_start) self.stasis_start_pubsub.subscribe('transfer_recipient_called', self.transfer_recipient_answered) self.stasis_start_pubsub.subscribe('create_transfer', self.create_transfer) self.ari.on_channel_event('ChannelDestroyed', self.hangup) self.ari.on_channel_event('StasisEnd', self.hangup) self.ari.on_channel_event('ChannelMohStop', self.moh_stop) self.hangup_pubsub.subscribe(TransferRole.recipient, self.recipient_hangup) self.hangup_pubsub.subscribe(TransferRole.initiator, self.initiator_hangup) self.hangup_pubsub.subscribe(TransferRole.transferred, self.transferred_hangup) self.ari.on_channel_event('ChannelCallerId', self.update_transfer_caller_id) def moh_stop(self, channel, event): logger.debug('received ChannelMohStop for channel %s (%s)', channel.id, event['channel']['name']) try: transfer = self.state_persistor.get_by_channel(channel.id) except KeyError: logger.debug('ignoring ChannelMohStop event: channel %s, app %s', event['channel']['name'], event['application']) return transfer_role = transfer.role(channel.id) if transfer_role != TransferRole.transferred: logger.debug('ignoring ChannelMohStop event: channel %s, app %s', event['channel']['name'], event['application']) return transfer_state = self.state_factory.make(transfer) transfer_state.transferred_moh_stop() def invalid_event(self, _, __, exception): if isinstance(exception, InvalidEvent): event = exception.event logger.error('invalid stasis event received: %s', event) elif (isinstance(exception, XiVOAmidError) or isinstance(exception, TransferException)): self.handle_error(exception) else: raise exception def handle_error(self, exception): logger.error('%s: %s', exception.message, exception.details) def process_lost_hangups(self): transfers = list(self.state_persistor.list()) logger.debug('Processing lost hangups since last stop...') for transfer in transfers: transfer_state = self.state_factory.make(transfer) if not Channel(transfer.transferred_call, self.ari).exists(): logger.debug('Transferred hangup from transfer %s', transfer.id) transfer_state = transfer_state.transferred_hangup() if not Channel(transfer.initiator_call, self.ari).exists(): logger.debug('Initiator hangup from transfer %s', transfer.id) transfer_state = transfer_state.initiator_hangup() if not Channel(transfer.recipient_call, self.ari).exists(): logger.debug('Recipient hangup from transfer %s', transfer.id) transfer_state = transfer_state.recipient_hangup() logger.debug('Done.') def stasis_start(self, event_objects, event): channel = event_objects['channel'] try: app_action = event['args'][1] except IndexError: logger.debug( 'ignoring StasisStart event: channel %s, app %s, args %s', event['channel']['name'], event['application'], event['args']) return self.stasis_start_pubsub.publish(app_action, (channel, event)) def hangup(self, channel, event): try: transfer = self.state_persistor.get_by_channel(channel.id) except KeyError: logger.debug('ignoring StasisEnd event: channel %s, app %s', event['channel']['name'], event['application']) return transfer_role = transfer.role(channel.id) self.hangup_pubsub.publish(transfer_role, transfer) def transfer_recipient_answered(self, channel_event): channel, event = channel_event event = TransferRecipientAnsweredEvent(event) try: transfer_bridge = self.ari.bridges.get( bridgeId=event.transfer_bridge) transfer_bridge.addChannel(channel=channel.id) except ARINotFound: logger.error('recipient answered, but transfer was hung up') return try: transfer = self.state_persistor.get(event.transfer_bridge) except KeyError: logger.debug('recipient answered, but transfer was abandoned') for channel_id in transfer_bridge.json['channels']: try: ari_helpers.unring_initiator_call(self.ari, channel_id) except ARINotFound: pass else: logger.debug('recipient answered, transfer continues normally') transfer_state = self.state_factory.make(transfer) transfer_state.recipient_answer() def create_transfer(self, channel_event): channel, event = channel_event event = CreateTransferEvent(event) try: bridge = self.ari.bridges.get(bridgeId=event.transfer_id) except ARINotFound: bridge = self.ari.bridges.createWithId(type='mixing', name='transfer', bridgeId=event.transfer_id) bridge.addChannel(channel=channel.id) channel_ids = bridge.get().json['channels'] if len(channel_ids) == 2: transfer = self.state_persistor.get(event.transfer_id) try: context = self.ari.channels.getChannelVar( channelId=transfer.initiator_call, variable='XIVO_TRANSFER_RECIPIENT_CONTEXT')['value'] exten = self.ari.channels.getChannelVar( channelId=transfer.initiator_call, variable='XIVO_TRANSFER_RECIPIENT_EXTEN')['value'] variables_str = self.ari.channels.getChannelVar( channelId=transfer.initiator_call, variable='XIVO_TRANSFER_VARIABLES')['value'] timeout_str = self.ari.channels.getChannelVar( channelId=transfer.initiator_call, variable='XIVO_TRANSFER_TIMEOUT')['value'] except ARINotFound: logger.error('initiator hung up while creating transfer') try: variables = json.loads(variables_str) except ValueError: logger.warning('could not decode transfer variables "%s"', variables_str) variables = {} timeout = None if timeout_str == 'None' else int(timeout_str) transfer_state = self.state_factory.make(transfer) new_state = transfer_state.start(transfer, context, exten, variables, timeout) if new_state.transfer.flow == 'blind': new_state.complete() def recipient_hangup(self, transfer): logger.debug('recipient hangup = cancel transfer %s', transfer.id) transfer_state = self.state_factory.make(transfer) transfer_state.recipient_hangup() def initiator_hangup(self, transfer): logger.debug('initiator hangup = complete transfer %s', transfer.id) transfer_state = self.state_factory.make(transfer) transfer_state.initiator_hangup() def transferred_hangup(self, transfer): logger.debug('transferred hangup = abandon transfer %s', transfer.id) transfer_state = self.state_factory.make(transfer) transfer_state.transferred_hangup() def clean_bridge(self, channel, event): try: bridge = self.ari.bridges.get(bridgeId=event['bridge']['id']) except ARINotFound: return if bridge.json['bridge_type'] != 'mixing': return logger.debug('cleaning bridge %s', bridge.id) try: self.ari.channels.get(channelId=channel.id) channel_is_hungup = False except ARINotFound: logger.debug('channel who left was hungup') channel_is_hungup = True if len(bridge.json['channels']) == 1 and channel_is_hungup: logger.debug('one channel left in bridge %s', bridge.id) lone_channel_id = bridge.json['channels'][0] try: bridge_is_locked = HangupLock.from_target(self.ari, bridge.id) except InvalidLock: bridge_is_locked = False if not bridge_is_locked: logger.debug('emptying bridge %s', bridge.id) try: self.ari.channels.hangup(channelId=lone_channel_id) except ARINotFound: pass try: bridge = bridge.get() except ARINotFound: return if len(bridge.json['channels']) == 0: self.bypass_hangup_lock_from_target(bridge) logger.debug('destroying bridge %s', bridge.id) try: bridge.destroy() except (ARINotInStasis, ARINotFound): pass def clean_bridge_variables(self, bridge, event): global_variable = 'XIVO_BRIDGE_VARIABLES_{}'.format(bridge.id) self.ari.asterisk.setGlobalVar(variable=global_variable, value='') def release_hangup_lock(self, channel, event): lock_source = channel lock_target_candidate_id = event['bridge']['id'] try: lock = HangupLock(self.ari, lock_source.id, lock_target_candidate_id) lock.release() except InvalidLock: pass def bypass_hangup_lock_from_source(self, channel, event): lock_source = channel for lock in HangupLock.from_source(self.ari, lock_source.id): lock.kill_target() def bypass_hangup_lock_from_target(self, bridge): try: lock = HangupLock.from_target(self.ari, bridge.id) lock.kill_source() except InvalidLock: pass def update_transfer_caller_id(self, channel, event): try: transfer = self.state_persistor.get_by_channel(channel.id) except KeyError: logger.debug('ignoring ChannelCallerId event: channel %s', event['channel']['name']) return transfer_role = transfer.role(channel.id) if transfer_role != TransferRole.recipient: logger.debug('ignoring ChannelCallerId event: channel %s', event['channel']['name']) return try: ari_helpers.update_connectedline(self.ari, self.amid, transfer.initiator_call, transfer.recipient_call) except ARINotFound: try: ari_helpers.update_connectedline(self.ari, self.amid, transfer.transferred_call, transfer.recipient_call) except ARINotFound: logger.debug( 'cannot update transfer callerid: everyone hung up')
class CoreARI: def __init__(self, config): self._apps = [] self.config = config self._is_running = False self._should_delay_reconnect = True self._should_stop = False self._pubsub = Pubsub() self.client = ARIClientProxy(**config['connection']) def _init_client(self): try: self.client.init() except requests.ConnectionError: logger.info('No ARI server found') return False except requests.HTTPError as e: if asterisk_is_loading(e): logger.info('ARI is not ready yet') return False else: raise self._pubsub.publish('client_initialized', message=None) return True def client_initialized_subscribe(self, callback): self._pubsub.subscribe('client_initialized', callback) def reload(self): self._should_delay_reconnect = False self._trigger_disconnect() def run(self): while not self._should_stop: initialized = self._init_client() if initialized: break connection_delay = self.config['startup_connection_delay'] logger.warning('ARI not initialized, retrying in %s seconds...', connection_delay) time.sleep(connection_delay) self._should_delay_reconnect = False while not self._should_stop: if self._should_delay_reconnect: delay = self.config['reconnection_delay'] logger.warning('Reconnecting to ARI in %s seconds', delay) time.sleep(delay) self._should_delay_reconnect = True self._connect() def _connect(self): logger.debug('ARI client listening...') try: with self._running(): self.client.run(apps=self._apps) except socket.error as e: if e.errno == errno.EPIPE: # bug in ari-py when calling client.close(): ignore it and stop logger.error('Error while listening for ARI events: %s', e) return else: self._connection_error(e) except (WebSocketException, HTTPError) as e: self._connection_error(e) except ValueError: logger.warning('Received non-JSON message from ARI... disconnecting') self.client.close() @contextmanager def _running(self): self._is_running = True try: yield finally: self._is_running = False def register_application(self, app): if app not in self._apps: self._apps.append(app) def deregister_application(self, app): if app in self._apps: self._apps.remove(app) def is_running(self): return self._is_running def provide_status(self, status): status['ari']['status'] = Status.ok if self.is_running() else Status.fail def _connection_error(self, error): logger.warning('ARI connection error: %s...', error) def _sync(self): '''self.sync() should be called before calling self.stop(), in case the ari client does not have the websocket yet''' while self._is_running: try: ari_websockets = self.client.websockets except AsteriskARINotInitialized: ari_websockets = None if ari_websockets: return time.sleep(0.1) def stop(self): self._should_stop = True self._trigger_disconnect() def _trigger_disconnect(self): self._sync() try: self.client.close() except RuntimeError: pass # bug in ari-py when calling client.close()