Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
0
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()
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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')
Ejemplo n.º 8
0
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()
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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()