Exemple #1
0
def form_session_handler(v, text, msg):
    """
    The form session handler will use the inbound text to answer the next question
    in the open SQLXformsSession for the associated contact. If no session is open,
    the handler passes. If multiple sessions are open, they are all closed and an
    error message is displayed to the user.
    """
    with critical_section_for_smsforms_sessions(v.owner_id):
        if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(v.domain):
            channel = get_channel_for_contact(v.owner_id, v.phone_number)
            running_session_info = XFormsSessionSynchronization.get_running_session_info_for_channel(
                channel)
            if running_session_info.session_id:
                session = SQLXFormsSession.by_session_id(
                    running_session_info.session_id)
                if not session.session_is_open:
                    # This should never happen. But if it does we should set the channel free
                    # and act like there was no available session
                    notify_error(
                        "The supposedly running session was not open and was released. "
                        'No known way for this to happen, so worth investigating.'
                    )
                    XFormsSessionSynchronization.clear_stale_channel_claim(
                        channel)
                    session = None
            else:
                session = None
        else:
            multiple, session = get_single_open_session_or_close_multiple(
                v.domain, v.owner_id)
            if multiple:
                send_sms_to_verified_number(
                    v, get_message(MSG_MULTIPLE_SESSIONS, v))
                return True

        if session:
            session.phone_number = v.phone_number
            session.modified_time = datetime.utcnow()
            session.save()

            # Metadata to be applied to the inbound message
            inbound_metadata = MessageMetadata(
                workflow=session.workflow,
                reminder_id=session.reminder_id,
                xforms_session_couch_id=session._id,
            )
            add_msg_tags(msg, inbound_metadata)

            try:
                answer_next_question(v, text, msg, session)
            except Exception:
                # Catch any touchforms errors
                log_sms_exception(msg)
                send_sms_to_verified_number(
                    v, get_message(MSG_TOUCHFORMS_DOWN, v))
            return True
        else:
            return False
def test_session_synchronization():
    phone_number_a = _clean_up_number('15555555555')
    phone_number_b = _clean_up_number('15555555554')
    session_a_1 = FakeSession(session_id=str(uuid4()),
                              phone_number=phone_number_a,
                              connection_id='Alpha')
    session_a_2 = FakeSession(session_id=str(uuid4()),
                              phone_number=phone_number_a,
                              connection_id='Beta')
    session_b_1 = FakeSession(session_id=str(uuid4()),
                              phone_number=phone_number_b,
                              connection_id='Kappa')

    # Nothing set yet, so it can be claimed
    assert XFormsSessionSynchronization.channel_is_available_for_session(
        session_a_1)
    # And so can the other one
    assert XFormsSessionSynchronization.channel_is_available_for_session(
        session_a_2)
    # Claim succeeds
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # Claim for same channel fails
    assert not XFormsSessionSynchronization.channel_is_available_for_session(
        session_a_2)
    assert not XFormsSessionSynchronization.claim_channel_for_session(
        session_a_2)
    # But same session can re-claim it
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_1)
    # And another session can claim another channel
    assert XFormsSessionSynchronization.claim_channel_for_session(session_b_1)
    # And if the first session releases the channel
    XFormsSessionSynchronization.release_channel_for_session(session_a_1)
    # Then the contact is still set
    assert XFormsSessionSynchronization.get_running_session_info_for_channel(
        SMSChannel(BACKEND_ID, phone_number_a)).contact_id == 'Alpha'
    # But the other session (that couldn't before) can claim it now
    assert XFormsSessionSynchronization.claim_channel_for_session(session_a_2)
    # Trying to clear the channel claim will fail because the session is still open
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(
        SMSChannel(BACKEND_ID, phone_number_a))
    # But if we close the session first
    session_a_2.close()
    # The session is now "stale" so we can clear that stale channel claim
    assert XFormsSessionSynchronization.clear_stale_channel_claim(
        SMSChannel(BACKEND_ID, phone_number_a))
    # If we try to clear it again it'll be a no-op and return false, since it's already cleared
    assert not XFormsSessionSynchronization.clear_stale_channel_claim(
        SMSChannel(BACKEND_ID, phone_number_a))
    def create_tasks(self):
        survey_sessions_due_for_action = self.get_survey_sessions_due_for_action(
        )
        all_open_session_ids = self.get_open_session_ids()
        for domain, connection_id, session_id, current_action_due, phone_number in survey_sessions_due_for_action:
            if skip_domain(domain):
                continue

            if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain):
                fake_session = SQLXFormsSession(
                    session_id=session_id,
                    connection_id=connection_id,
                    phone_number=phone_number,
                )
                if not XFormsSessionSynchronization.channel_is_available_for_session(
                        fake_session):
                    running_session_info = XFormsSessionSynchronization.get_running_session_info_for_channel(
                        fake_session.get_channel())
                    # First confirm the supposedly running session is even open
                    # and if it's not (should be exceedingly rare) release it and act like it wasn't there
                    if running_session_info.session_id \
                            and running_session_info.session_id not in all_open_session_ids:
                        notify_error(
                            "The supposedly running session was not open and was released. "
                            "No known way for this to happen, so worth investigating.",
                            details={
                                'running_session_info': running_session_info
                            })
                        XFormsSessionSynchronization.clear_stale_channel_claim(
                            fake_session.get_channel())
                    # This is the 99% case: there's a running session for the channel
                    # so leave this session/action in the queue for later and move on to the next one
                    else:
                        continue

            enqueue_lock = self.get_enqueue_lock(session_id,
                                                 current_action_due)
            if enqueue_lock.acquire(blocking=False):
                handle_due_survey_action.delay(domain, connection_id,
                                               session_id)