Exemple #1
0
    def _on_entering(self):
        """Acquire network resources and try to connect.

        The connection is authenticated by the mean of the server
        certificate, so it is trusted.
        """
        self.logger.info(u"Connecting to server...")
        self._context._internal_facade.set_global_status(
            GStatuses.NC_CONNECTING)
        if self._context.acquire_network_resources():
            self.logger.info(u"Server has successfully authenticated itself")
            self._context.num_connection_attempts = 0
            self._context._internal_facade.reset_pause_timer()
            self._set_next_state(StateRegister.get('ConnectedState'))
            self._context._input_queue.put(Command('HANDSHAKE'),
                                           'sessioncommand')
        else:
            self._context._internal_facade.set_global_status(
                GStatuses.NC_NOSERVER)
            if self._context.num_connection_attempts > self._context.max_connection_attempts:
                self.logger.error(
                    u'Server has been unreachable for too long, giving up.')
                return self._context._internal_facade.pause_and_restart()
            self._set_next_state(StateRegister.get('DisconnectedState'))
            self._context._input_queue.put(Command('CONNECT'),
                                           'sessioncommand')
Exemple #2
0
def setup_fixtures(adapter_mock, workerpool_mock, connection_lifekeeper,
                   disconnectedstate_cls_mock, downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister

    components = setup_server_session(__file__, adapter_mock, workerpool_mock,
                                      connection_lifekeeper,
                                      disconnectedstate_cls_mock,
                                      downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    def fail():
        msg = "ServerSession has attempted to start downloading although an" \
              " integrity error was expected"
        assert_true(False, msg)

    # If we get into SyncDownloadingLeavesState, it means we have
    # passed the integrity check. But we shouldn't had to!
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = fail

    def ask_user(what, content, client_basis, server_basis):
        if what == 'accept_sync':
            msg = "The user has been asked to accept the synchronization" \
              " although an integrity error was expected"
            assert_true(False, msg)
        return 'ok'

    # If the sync dialog is shown to the user, it means we have
    # passed the integrity check. But we shouldn't had to!
    components['mock'][
        'ui_controller'].ask_for_user_input.side_effect = ask_user

    def terminate():
        components['real']['server_session'].terminate()
        return integrity_failure_state

    # If the integrity check fails, it's fine
    integrity_failure_state = StateRegister.get('BasisMismatchState')
    integrity_failure_state.do_execute = MagicMock(side_effect=terminate)
    components['mock']['integrity_failure_state'] = integrity_failure_state
    components['mock']['downloading_state'] = download_state_mock

    return components
    def _handle_message_SYNC_ENCRYPTED_FILES_IVS(self, message):
        """We have asked the server to send the IVs for some of the
        encrypted files, here they are. Now it's possible to proceed
        with the synchronization as usual.

        ServerSession needs the etag for both the cleartext and the
        encrypted versions of an encrypted file to correctly detect
        changes. If the storage cache is not available then we need
        to encrypt the files again in order to read their etag. The
        same IVs used the encrypt the first time must be used.

        Basically ServerSession receives this message only if there
        are some encrypted files but there is no storage cache available
        for them.
        """
        self.logger.debug(u"Recomputing the etag for encrypted conflicted pathnames...")
        server_ivs = message.getParameter('ivs')
        server_ivs_notNone = {
            key: server_ivs[key]
            for key in filter(
                lambda key: server_ivs[key] is not None, server_ivs
            )
        }

        try:
            self.logger.debug("I'm going to recalculate all encrypted etags")
            enc_etags = CryptoHelpers.recalc_encrypted_etag(
                                                    server_ivs_notNone,
                                                    self._context.warebox,
                                                    self._context.cfg,
                                                    self._context.must_die)
            self.logger.debug("Encrypted etags recalculated")
        except ExecutionInterrupted:
            self.logger.debug(u'ExecutionInterrupted, terminating...')
            self._set_next_state(StateRegister.get('DisconnectedState'))
            return
        #self.logger.debug(u"Finished recomputing the etag for encrypted conflicted pathnames.")
        #self.logger.debug('IVs received from server\n %r' % server_ivs)
        #self.logger.debug('IVs NotNone\n %r' % server_ivs_notNone)
        #self.logger.debug('encrypted Etag\n %r' % enc_etags)
        #self.logger.debug('Remote Etag\n %r' % self._context.startup_synchronization.remote_etag)
        self._context.startup_synchronization.update_conflicts_of_encrypted_pathnames(enc_etags)

        try:
            if self._check_hash_mismatch():
                self._start_syncing()
            else:
#                self._internal_facade.terminate()
                self._context._internal_facade.pause()
        except HashMismatchException as excp:
            self.logger.critical('Integrity Error %s' % excp)
            self._set_next_state(StateRegister.get('IntegrityErrorState'))
    def _handle_message_SYNC_ENCRYPTED_FILES_IVS(self, message):
        """We have asked the server to send the IVs for some of the
        encrypted files, here they are. Now it's possible to proceed
        with the synchronization as usual.

        ServerSession needs the etag for both the cleartext and the
        encrypted versions of an encrypted file to correctly detect
        changes. If the storage cache is not available then we need
        to encrypt the files again in order to read their etag. The
        same IVs used the encrypt the first time must be used.

        Basically ServerSession receives this message only if there
        are some encrypted files but there is no storage cache available
        for them.
        """
        self.logger.debug(
            u"Recomputing the etag for encrypted conflicted pathnames...")
        server_ivs = message.getParameter('ivs')
        server_ivs_notNone = {
            key: server_ivs[key]
            for key in filter(lambda key: server_ivs[key] is not None,
                              server_ivs)
        }

        try:
            self.logger.debug("I'm going to recalculate all encrypted etags")
            enc_etags = CryptoHelpers.recalc_encrypted_etag(
                server_ivs_notNone, self._context.warebox, self._context.cfg,
                self._context.must_die)
            self.logger.debug("Encrypted etags recalculated")
        except ExecutionInterrupted:
            self.logger.debug(u'ExecutionInterrupted, terminating...')
            self._set_next_state(StateRegister.get('DisconnectedState'))
            return
        #self.logger.debug(u"Finished recomputing the etag for encrypted conflicted pathnames.")
        #self.logger.debug('IVs received from server\n %r' % server_ivs)
        #self.logger.debug('IVs NotNone\n %r' % server_ivs_notNone)
        #self.logger.debug('encrypted Etag\n %r' % enc_etags)
        #self.logger.debug('Remote Etag\n %r' % self._context.startup_synchronization.remote_etag)
        self._context.startup_synchronization.update_conflicts_of_encrypted_pathnames(
            enc_etags)

        try:
            if self._check_hash_mismatch():
                self._start_syncing()
            else:
                #                self._internal_facade.terminate()
                self._context._internal_facade.pause()
        except HashMismatchException as excp:
            self.logger.critical('Integrity Error %s' % excp)
            self._set_next_state(StateRegister.get('IntegrityErrorState'))
def setup_fixtures(adapter_mock, workerpool_mock, connection_lifekeeper,
                   disconnectedstate_cls_mock, downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister

    components = setup_server_session(
                        __file__,
                        adapter_mock, workerpool_mock, connection_lifekeeper,
                        disconnectedstate_cls_mock, downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    def fail():
        msg = "ServerSession has attempted to start downloading although an" \
              " integrity error was expected"
        assert_true(False, msg)

    # If we get into SyncDownloadingLeavesState, it means we have
    # passed the integrity check. But we shouldn't had to!
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = fail

    def ask_user(what, content, client_basis, server_basis):
        if what == 'accept_sync':
            msg = "The user has been asked to accept the synchronization" \
              " although an integrity error was expected"
            assert_true(False, msg)
        return 'ok'

    # If the sync dialog is shown to the user, it means we have
    # passed the integrity check. But we shouldn't had to!
    components['mock']['ui_controller'].ask_for_user_input.side_effect = ask_user

    def terminate():
        components['real']['server_session'].terminate()
        return integrity_failure_state

    # If the integrity check fails, it's fine
    integrity_failure_state = StateRegister.get('BasisMismatchState')
    integrity_failure_state.do_execute = MagicMock(side_effect=terminate)
    components['mock']['integrity_failure_state'] = integrity_failure_state
    components['mock']['downloading_state'] = download_state_mock

    return components
Exemple #6
0
 def _handle_command_HANDSHAKE(self, message):
     """Positive reply from the server, tell him the protocol version
     that we can support.
     """
     self._context.output_message_queue.put(
         PROTOCOL_VERSION('PROTOCOL_VERSION', {'version': 1}))
     self._set_next_state(StateRegister.get('ProtocolVersionState'))
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis declared by the server and, if it is
        valid, then finalize the commit by updating all internal data
        structures. Finally go back to the Replication & Transfer state,
        we start again.
        """
        server_basis = message.getParameter('new_basis')
        self.logger.info(u'Commit done')
        self.logger.debug(u"Server basis: %s" % (server_basis))
        previous_basis = self._context.integrity_manager.getCurrentBasis()
        self.logger.debug(u"Previous basis: %s" % previous_basis)

        # Check the server basis against the one we have computed
        self._check_integrity(server_basis)

        # Everything OK, persist the new trusted basis and related metadata
        new_basis = self._context.integrity_manager.getCurrentBasis()
        completed_ops = self._context.transaction_manager.get_completed_operations()
        self._persist_integrity_metadata(previous_basis,
                                         new_basis,
                                         completed_ops)

        self.logger.info(u"Updated basis: %s" % new_basis)
        self._context.transaction_manager.clear()
        self._context.operation_responses.clear()
        self._context.refused_declare_count = 0
        self.logger.debug(
            u"Current transaction has been committed successfully.")
        for (_, operation) in completed_ops:
            operation.notify_pathname_status_change(PStatuses.ALIGNED)
        self._update_user_interfaces(message)
        self._try_set_global_status_aligned()
        self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis sent by the server against our
        candidate basis, if it's valid then finalize the commit by
        updating all internal data structures.
        Finally go into the sync phase, the session can begin at last.
        """
        self.logger.info(u'Commit done')
        server_basis = message.getParameter('new_basis')
        candidate_basis = self._load_candidate_basis()
        previous_basis = self._load_trusted_basis()
        self.logger.debug(u"Server basis: %s" % server_basis)
        self.logger.debug(u"Candidate basis: %s" % candidate_basis)
        self.logger.debug(u"Last trusted basis: %s" % previous_basis)

        # Check the server basis against the candidate basis
        self._context.integrity_manager.setCurrentBasis(candidate_basis)
        self._check_integrity(server_basis)

        # Everything OK, persist the new trusted basis and related metadata
        operations = self._context.transaction_cache.get_all_records()
        operations = [(op_id, op) for (op_id, op, _) in operations]
        self._persist_integrity_metadata(previous_basis,
                                         server_basis,
                                         operations)
        self._context.integrity_manager.setCurrentBasis(server_basis)

        self.logger.info(u"Pending commit successfully recovered.")
        self.logger.info(u"Updated basis: %s" % server_basis)
        self._update_user_interfaces(message)
        self._set_next_state(StateRegister.get('ReadyForServiceState'))
        self._context._input_queue.put(
                                Command('STARTSYNCPHASE'), 'sessioncommand')
 def _on_complete_operation(self, operation):
     with self._lock:
         self._num_finished_operations += 1
         num_finished = self._num_finished_operations
         num_received = self._num_received_operations
         if self._received_all_operations and num_received == num_finished:
             self._set_next_state(StateRegister.get("SyncDoneState"))
Exemple #10
0
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis sent by the server against our
        candidate basis, if it's valid then finalize the commit by
        updating all internal data structures.
        Finally go into the sync phase, the session can begin at last.
        """
        self.logger.info(u'Commit done')
        server_basis = message.getParameter('new_basis')
        candidate_basis = self._load_candidate_basis()
        previous_basis = self._load_trusted_basis()
        self.logger.debug(u"Server basis: %s" % server_basis)
        self.logger.debug(u"Candidate basis: %s" % candidate_basis)
        self.logger.debug(u"Last trusted basis: %s" % previous_basis)

        # Check the server basis against the candidate basis
        self._context.integrity_manager.setCurrentBasis(candidate_basis)
        self._check_integrity(server_basis)

        # Everything OK, persist the new trusted basis and related metadata
        operations = self._context.transaction_cache.get_all_records()
        operations = [(op_id, op) for (op_id, op, _) in operations]
        self._persist_integrity_metadata(previous_basis, server_basis,
                                         operations)
        self._context.integrity_manager.setCurrentBasis(server_basis)

        self.logger.info(u"Pending commit successfully recovered.")
        self.logger.info(u"Updated basis: %s" % server_basis)
        self._update_user_interfaces(message)
        self._set_next_state(StateRegister.get('ReadyForServiceState'))
        self._context._input_queue.put(Command('STARTSYNCPHASE'),
                                       'sessioncommand')
Exemple #11
0
 def _on_entering(self):
     """Tell the server that we are ready.
     """
     self._context.output_message_queue.put(
         COMMIT_START("COMMIT_START",
                      {'achieved_operations': 'RECOVER_FROM_CRASH'}))
     self._set_next_state(StateRegister.get('PendingCommitStartState'))
Exemple #12
0
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis declared by the server and, if it is
        valid, then finalize the commit by updating all internal data
        structures. Finally go back to the Replication & Transfer state,
        we start again.
        """
        server_basis = message.getParameter('new_basis')
        self.logger.info(u'Commit done')
        self.logger.debug(u"Server basis: %s" % (server_basis))
        previous_basis = self._context.integrity_manager.getCurrentBasis()
        self.logger.debug(u"Previous basis: %s" % previous_basis)

        # Check the server basis against the one we have computed
        self._check_integrity(server_basis)

        # Everything OK, persist the new trusted basis and related metadata
        new_basis = self._context.integrity_manager.getCurrentBasis()
        completed_ops = self._context.transaction_manager.get_completed_operations(
        )
        self._persist_integrity_metadata(previous_basis, new_basis,
                                         completed_ops)

        self.logger.info(u"Updated basis: %s" % new_basis)
        self._context.transaction_manager.clear()
        self._context.operation_responses.clear()
        self._context.refused_declare_count = 0
        self.logger.debug(
            u"Current transaction has been committed successfully.")
        for (_, operation) in completed_ops:
            operation.notify_pathname_status_change(PStatuses.ALIGNED)
        self._update_user_interfaces(message)
        self._try_set_global_status_aligned()
        self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
 def _handle_command_HANDSHAKE(self, message):
     """Positive reply from the server, tell him the protocol version
     that we can support.
     """
     self._context.output_message_queue.put(
         PROTOCOL_VERSION('PROTOCOL_VERSION', {'version': 1}))
     self._set_next_state(StateRegister.get('ProtocolVersionState'))
Exemple #14
0
    def _on_entering(self):
        """ServerSession waits for all operations currently handled by
        workers to be completed, afterwards it begins the commit.

        Integrity of the transaction is checked and the "candidate basis"
        (the expected basis after our modifications to the storage) is
        computed.
        """
        self.logger.debug(u"Committing the current transaction...")
        self.logger.debug(u"Waiting for the transaction to be finished...")
        self._context.transaction_manager.wait_until_finished()
        self.logger.debug(u"Transaction is finished!")
        self.logger.info(u"Committing the following pathnames:")
        operations = self._context.transaction_manager.get_completed_operations()
        for (op_id, op) in operations:
            self.logger.info(u'    %s "%s"' % (op.verb, op.pathname))
            self.logger.debug(u"    id=%s %s" % (op_id, op))
        self._check_transaction_integrity(operations)
        candidate_basis = self._context.integrity_manager.getCandidateBasis()
        self._context._persist_candidate_basis(candidate_basis)
        self.logger.info("Candidate basis: %s" % candidate_basis)
        self._update_transaction_cache(operations)
        completed_operations_id = [op_id for (op_id, _) in operations]
        self._context.output_message_queue.put(COMMIT_START(
            "COMMIT_START", {'achieved_operations': completed_operations_id}))
        self._set_next_state(StateRegister.get('CommitStartState'))
Exemple #15
0
 def _on_complete_operation(self, operation):
     with self._lock:
         self._num_finished_operations += 1
         num_finished = self._num_finished_operations
         num_received = self._num_received_operations
         if self._received_all_operations and num_received == num_finished:
             self._set_next_state(StateRegister.get('SyncDoneState'))
Exemple #16
0
 def _handle_command_OPERATIONSFINISHED(self, command):
     """Receiving this command means that all the internal operations
     have been served. Wait until all of them are complete.
     """
     self._set_next_state(StateRegister.get('SyncWaitingForCompletionState'))
     cmd = Command('GOTOONCOMPLETION')
     cmd.next_state = 'SyncDoneState'
     self._context._input_queue.put(cmd, 'sessioncommand')
Exemple #17
0
 def _handle_message_REPLICATION_START(self, message):
     """The server is ready to leave the sync phase, too. Let's
     start the Replication & Transfer phase.
     """
     self._set_next_state(
         StateRegister.get('EnteringReplicationAndTransferState'))
     self._context._input_queue.put(
         Command('UPDATEBEFOREREPLICATION'), 'sessioncommand')
Exemple #18
0
 def _handle_message_REPLICATION_START(self, message):
     """The server is ready to leave the sync phase, too. Let's
     start the Replication & Transfer phase.
     """
     self._set_next_state(
         StateRegister.get('EnteringReplicationAndTransferState'))
     self._context._input_queue.put(
         Command('UPDATEBEFOREREPLICATION'), 'sessioncommand')
Exemple #19
0
 def _start_syncing(self):
     """The list of modification to perform has been accepted, make
     the synchronization phase start.
     """
     self._context.integrity_manager.setCurrentBasis(self.server_basis.encode())
     self._context._internal_facade.set_global_status(GStatuses.C_NOTALIGNED)
     self._context.startup_synchronization.execute()
     self._context.startup_synchronization.generate_downlink_events()
     self._set_next_state(StateRegister.get('SyncDownloadingLeavesState'))
Exemple #20
0
    def _handle_message_PROTOCOL_VERSION_AGREEMENT(self, message):
        """The server replied to our greeting message and then closed
        the connection. Is our client version obsolete?

        Note: this is just post-disconnection handling, the natural
        handler would be ProtocolVersionState.
        """
        state = StateRegister.get('ProtocolVersionState')
        state._check_protocol_mismatch(message)
    def _handle_message_PROTOCOL_VERSION_AGREEMENT(self, message):
        """The server replied to our greeting message and then closed
        the connection. Is our client version obsolete?

        Note: this is just post-disconnection handling, the natural
        handler would be ProtocolVersionState.
        """
        state = StateRegister.get('ProtocolVersionState')
        state._check_protocol_mismatch(message)
Exemple #22
0
def test_nothing_to_sync_from_clean_state(adapter_mock, workerpool_mock,
                                          connection_lifekeeper,
                                          disconnectedstate_cls_mock,
                                          downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister
    from FileRockSharedLibraries.Communication.Messages import SYNC_FILES_LIST

    components = setup_server_session(__file__, adapter_mock, workerpool_mock,
                                      connection_lifekeeper,
                                      disconnectedstate_cls_mock,
                                      downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    def terminate():
        components['real']['server_session'].terminate()
        return syncstart_state

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    # Stop the test when we get to SyncDownloadingLeavesState
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = terminate

    # There is nothing in the warebox
    components['mock']['warebox'].get_content.return_value = []

    # Send ServerSession a scenario with no data on the storage.
    components['real']['metadata'].set('trusted_basis', 'TRUSTEDBASIS')
    msg = SYNC_FILES_LIST(
        'SYNC_FILES_LIST', {
            'basis': 'TRUSTEDBASIS',
            'dataset': [],
            'last_commit_client_id': '0',
            'last_commit_client_hostname': 'myhostname',
            'last_commit_client_platform': 'myplatform',
            'last_commit_timestamp': 'mytimestamp',
            'user_quota': '0',
            'used_space': '100'
        })
    components['real']['input_queue'].put(msg, 'servermessage')

    components['real']['server_session'].start()
    components['real']['server_session'].join()

    # No data on the storage, in the warebox or in the storage cache.
    # We expect that nothing has happened.
    assert_false(components['mock']['ui_controller'].ask_for_user_input.called)
    assert_equal(components['real']['storage_cache'].get_all_records(), [])
    assert_true(components['real']['input_queue'].empty(['operation']))
 def on_commit_necessary_to_proceed(self):
     """Session can decide to commit the current transaction. It
     happens if the transaction has got too large, if it's passed too
     much time from the last commit or if a commit is needed to honor
     some session constraint.
     """
     self.logger.info(
         u"The application has decided that a commit is necessary")
     self._set_next_state(
         StateRegister.get('WaitingForUnauthorizedOperationsState'))
 def on_commit_necessary_to_proceed(self):
     """Session can decide to commit the current transaction. It
     happens if the transaction has got too large, if it's passed too
     much time from the last commit or if a commit is needed to honor
     some session constraint.
     """
     self.logger.info(
         u"The application has decided that a commit is necessary")
     self._set_next_state(
         StateRegister.get('WaitingForUnauthorizedOperationsState'))
Exemple #25
0
 def _handle_message_CHALLENGE_VERIFY_RESPONSE(self, message):
     """Received a reply to our challenge. Did the server authenticate
     us? If not, maybe another client is already connected to this
     account.
     """
     auth_result = message.getParameter('result')
     if auth_result:
         self.logger.info(u"Client has successfully authenticated itself.")
         self._context.output_message_queue.put(
             READY_FOR_SERVICE('READY_FOR_SERVICE'))
         self._context.session_id = message.get_session_id()
         self.logger.debug(u"Received Session id %s from server" %
                           self._context.session_id)
         self._context.keepalive_timer.resume_execution()
         self._set_next_state(StateRegister.get('ReadyForServiceState'))
     else:
         self.logger.error(u"Server rejected client authentication.")
         self._context._internal_facade.set_global_status(
             GStatuses.NC_NOTAUTHORIZED)
         if message.is_other_client_connected():
             client = message.get_other_connected_client()
             if client["client_id"] == 0:
                 other_client_message = u"Your web client is already connected"
             else:
                 other_client_message = \
                     u"Client number %s from computer %s already connected" \
                     % (client["client_id"], client["hostname"])
             self.logger.warning(other_client_message)
             self._context.disconnect_other_client = True
             force_disconnection = self._context._ui_controller.ask_for_user_input(
                 "other_client_connected", client["client_id"],
                 client["hostname"])
             if not force_disconnection == 'ok':
                 self._context._internal_facade.pause()
                 return
             else:
                 self._context.current_state._set_next_state(
                     StateRegister.get('DisconnectedState'))
                 self._context._input_queue.put(Command('CONNECT'),
                                                'sessioncommand')
                 return
         else:
             relink_user(self)
def test_nothing_to_sync_from_clean_state(
                        adapter_mock, workerpool_mock, connection_lifekeeper,
                        disconnectedstate_cls_mock, downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister
    from FileRockSharedLibraries.Communication.Messages import SYNC_FILES_LIST

    components = setup_server_session(
                        __file__,
                        adapter_mock, workerpool_mock, connection_lifekeeper,
                        disconnectedstate_cls_mock, downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    def terminate():
        components['real']['server_session'].terminate()
        return syncstart_state

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    # Stop the test when we get to SyncDownloadingLeavesState
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = terminate

    # There is nothing in the warebox
    components['mock']['warebox'].get_content.return_value = []

    # Send ServerSession a scenario with no data on the storage.
    components['real']['metadata'].set('trusted_basis', 'TRUSTEDBASIS')
    msg = SYNC_FILES_LIST('SYNC_FILES_LIST', {
                'basis': 'TRUSTEDBASIS',
                'dataset': [],
                'last_commit_client_id': '0',
                'last_commit_client_hostname': 'myhostname',
                'last_commit_client_platform': 'myplatform',
                'last_commit_timestamp': 'mytimestamp',
                'user_quota': '0',
                'used_space': '100'
                })
    components['real']['input_queue'].put(msg, 'servermessage')

    components['real']['server_session'].start()
    components['real']['server_session'].join()

    # No data on the storage, in the warebox or in the storage cache.
    # We expect that nothing has happened.
    assert_false(components['mock']['ui_controller'].ask_for_user_input.called)
    assert_equal(components['real']['storage_cache'].get_all(), [])
    assert_true(components['real']['input_queue'].empty(['operation']))
    def _handle_message_PROTOCOL_VERSION_AGREEMENT(self, message):
        """Received the reply from the server, check if he agreed our
        protocol version. If so, ask the server to authenticate.
        """
        self._check_protocol_mismatch(message)

        # Protocol agreed, starting authentication
        challenge_request_msg = CHALLENGE_REQUEST(
            "CHALLENGE_REQUEST",
            {'client_id': self._context.client_id, 'username': self._context.username})
        self._context.output_message_queue.put(challenge_request_msg)
        self._set_next_state(StateRegister.get('ChallengeRequestState'))
 def _handle_command_UPDATEBEFOREREPLICATION(self, command):
     """Initialize all data structures and components.
     """
     storage_content = self._context.storage_cache.get_all_records()
     self._update_client_status(storage_content)
     self._update_filesystem_watcher(storage_content)
     self._context.filesystem_watcher.resume_execution()
     self.logger.info(u'Started filesystem monitoring.')
     self._context.refused_declare_count = 0
     self._context.id = 0
     self._try_set_global_status_aligned()
     self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
    def _on_entering(self):
        """Acquire network resources and try to connect.

        The connection is authenticated by the mean of the server
        certificate, so it is trusted.
        """
        self.logger.info(u"Connecting to server...")
        self._context._internal_facade.set_global_status(GStatuses.NC_CONNECTING)
        if self._context.acquire_network_resources():
            self.logger.info(u"Server has successfully authenticated itself")
            self._context.num_connection_attempts = 0
            self._context._internal_facade.reset_pause_timer()
            self._set_next_state(StateRegister.get('ConnectedState'))
            self._context._input_queue.put(Command('HANDSHAKE'), 'sessioncommand')
        else:
            self._context._internal_facade.set_global_status(GStatuses.NC_NOSERVER)
            if self._context.num_connection_attempts > self._context.max_connection_attempts:
                self.logger.error(u'Server has been unreachable for too long, giving up.')
                return self._context._internal_facade.pause_and_restart()
            self._set_next_state(StateRegister.get('DisconnectedState'))
            self._context._input_queue.put(Command('CONNECT'), 'sessioncommand')
 def _handle_command_UPDATEBEFOREREPLICATION(self, command):
     """Initialize all data structures and components.
     """
     storage_content = self._context.storage_cache.get_all_records()
     self._update_client_status(storage_content)
     self._update_filesystem_watcher(storage_content)
     self._context.filesystem_watcher.resume_execution()
     self.logger.info(u'Started filesystem monitoring.')
     self._context.refused_declare_count = 0
     self._context.id = 0
     self._try_set_global_status_aligned()
     self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
 def _start_syncing(self):
     """The list of modification to perform has been accepted, make
     the synchronization phase start.
     """
     self._context.integrity_manager.setCurrentBasis(
         self.server_basis.encode())
     self._context._internal_facade.set_global_status(
         GStatuses.C_NOTALIGNED)
     self._context.startup_synchronization.execute()
     self._context.startup_synchronization.generate_downlink_events()
     self._set_next_state(
         StateRegister.get('ResolvingDeletionConflictsState'))
 def _handle_message_CHALLENGE_VERIFY_RESPONSE(self, message):
     """Received a reply to our challenge. Did the server authenticate
     us? If not, maybe another client is already connected to this
     account.
     """
     auth_result = message.getParameter('result')
     if auth_result:
         self.logger.info(u"Client has successfully authenticated itself.")
         self._context.output_message_queue.put(READY_FOR_SERVICE('READY_FOR_SERVICE'))
         self._context.session_id = message.get_session_id()
         self.logger.debug(
             u"Received Session id %s from server" % self._context.session_id)
         self._context.keepalive_timer.resume_execution()
         self._set_next_state(StateRegister.get('ReadyForServiceState'))
     else:
         self.logger.error(u"Server rejected client authentication.")
         self._context._internal_facade.set_global_status(GStatuses.NC_NOTAUTHORIZED)
         if message.is_other_client_connected():
             client = message.get_other_connected_client()
             if client["client_id"]==0:
                 other_client_message = u"Your web client is already connected"
             else:
                 other_client_message = \
                     u"Client number %s from computer %s already connected" \
                     % (client["client_id"], client["hostname"])
             self.logger.warning(other_client_message)
             self._context.disconnect_other_client = True
             force_disconnection = self._context._ui_controller.ask_for_user_input(
                 "other_client_connected", client["client_id"],
                 client["hostname"])
             if not force_disconnection == 'ok':
                 self._context._internal_facade.pause()
                 return
             else:
                 self._context.current_state._set_next_state(
                     StateRegister.get('DisconnectedState'))
                 self._context._input_queue.put(Command('CONNECT'), 'sessioncommand')
                 return
         else:
             relink_user(self)
    def _immediate_commit(self):
        """Pre-emptive style commit.

        Usually ServerSession wait for authorization from the server
        for those operations that were already declared, before actually
        start a commit. But sometimes you just can't wait, so all
        still unauthorized operations are postponed. For example this
        happens when the user asked to commit - hey, it's better not to
        contradict the boss.
        """
        unauthorized = self._context.transaction_manager.flush_unauthorized_operations()
        map(lambda x: self.postpone_operation(x), unauthorized)
        self._set_next_state(StateRegister.get('CommitState'))
Exemple #34
0
 def _handle_command_INTEGRITYERRORONDELETELOCAL(self, command):
     """
     Attributes available in the command object:
         - pathname
         - proof
         - reason
         - expected_basis
         - computed_basis
     """
     self.logger.critical(u"Detected an integrity error while deleting "
                          "locally pathname %s. Reason: %s" %
                          (command.pathname, command.reason))
     self._set_next_state(StateRegister.get('IntegrityErrorState'))
Exemple #35
0
    def _handle_message_PROTOCOL_VERSION_AGREEMENT(self, message):
        """Received the reply from the server, check if he agreed our
        protocol version. If so, ask the server to authenticate.
        """
        self._check_protocol_mismatch(message)

        # Protocol agreed, starting authentication
        challenge_request_msg = CHALLENGE_REQUEST(
            "CHALLENGE_REQUEST", {
                'client_id': self._context.client_id,
                'username': self._context.username
            })
        self._context.output_message_queue.put(challenge_request_msg)
        self._set_next_state(StateRegister.get('ChallengeRequestState'))
Exemple #36
0
def on_download_integrity_error(state, command):
    """
    Attributes available in the command object:
        - operation
        - proof
        - reason
        - expected_etag
        - expected_basis
        - actual_etag
        - computed_basis
    """
    operation = command.operation
    state.logger.critical(u"Detected an integrity error while downloading "
                          "pathname %s. Reason: %s" %
                          (operation.pathname, command.reason))
    state._set_next_state(StateRegister.get('IntegrityErrorState'))
Exemple #37
0
    def run(self):
        """Implementation of the threading.Thread.run() method."""
        self._started = True
        try:
            self.worker_pool.start_workers()
            self.keepalive_timer.start()
            self.current_state = StateRegister.get('DisconnectedState')
            curr_basis = self.current_state._load_trusted_basis()
            self.integrity_manager.setCurrentBasis(curr_basis)
            self.logger.info(u'Current basis is: %s' % curr_basis)
            self.current_state._on_entering()
            self._internal_facade.set_global_status(GStatuses.NC_STOPPED)
            if self.auto_start:
                self._input_queue.put(Command('CONNECT'), 'sessioncommand')
            self.cryptoAdapter.start()
            self._scheduler.schedule_action(self.check_encrypted_folder,
                                            name='check_encrypted_folder',
                                            seconds=5,
                                            repeating=True)

            # The event loop
            self._main_loop()

        except UnexpectedMessageException as e:
            self.logger.critical(
                u"Received an unexpected message from the Server while in "
                u"state '%s': %s. Forcing termination." %
                (self.current_state.__class__, str(e)))
            raise

        except ProtocolException as e:
            self.logger.critical(
                u"Detected an unrecoverable error, forcing termination: %s" %
                str(e))
            # Pre-emptive release, just stop before messing up the server
            self.release_network_resources()
            raise

        except Exception as e:
            self.logger.critical(
                u"Forcing termination due to uncaught exception '%s': %s" %
                (e.__class__, e))
            self.logger.debug(u"Last error stacktrace:\n%s" %
                              traceback.format_exc())
            # Pre-emptive release, just stop before messing up the server
            self.release_network_resources()
            raise
    def run(self):
        """Implementation of the threading.Thread.run() method."""
        self._started = True
        try:
            self.worker_pool.start_workers()
            self.keepalive_timer.start()
            self.current_state = StateRegister.get('DisconnectedState')
            curr_basis = self.current_state._load_trusted_basis()
            self.integrity_manager.setCurrentBasis(curr_basis)
            self.logger.info(u'Current basis is: %s' % curr_basis)
            self.current_state._on_entering()
            self._internal_facade.set_global_status(GStatuses.NC_STOPPED)
            if self.auto_start:
                self._input_queue.put(Command('CONNECT'), 'sessioncommand')
            self.cryptoAdapter.start()
            self._scheduler.schedule_action(
                self.check_encrypted_folder, name='check_encrypted_folder',
                seconds=5, repeating=True)

            # The event loop
            self._main_loop()

        except UnexpectedMessageException as e:
            self.logger.critical(
                u"Received an unexpected message from the Server while in "
                u"state '%s': %s. Forcing termination."
                % (self.current_state.__class__, str(e)))
            raise

        except ProtocolException as e:
            self.logger.critical(
                u"Detected an unrecoverable error, forcing termination: %s"
                % str(e))
            # Pre-emptive release, just stop before messing up the server
            self.release_network_resources()
            raise

        except Exception as e:
            self.logger.critical(
                u"Forcing termination due to uncaught exception '%s': %s"
                % (e.__class__, e))
            self.logger.debug(
                u"Last error stacktrace:\n%s" % traceback.format_exc())
            # Pre-emptive release, just stop before messing up the server
            self.release_network_resources()
            raise
    def _immediate_commit(self):
        """Pre-emptive style commit.

        Usually ServerSession wait for authorization from the server
        for those operations that were already declared, before actually
        start a commit. But sometimes you just can't wait, so all
        still unauthorized operations are postponed. For example this
        happens when the user asked to commit - hey, it's better not to
        contradict the boss.
        """
        unauthorized = self._context.transaction_manager.flush_unauthorized_operations()
        for operation in unauthorized:
            self.postpone_operation(operation)
            self._context.worker_pool.release_worker()
            if __debug__: 
                self._context.worker_pool.track_release_unassigned_worker(operation)
        self._set_next_state(StateRegister.get('CommitState'))
Exemple #40
0
    def _handle_operation(self, operation):
        if operation == 'NO_MORE_OPERATIONS':
            self._received_all_operations = True
            with self._lock:
                num_finished = self._num_finished_operations
                num_received = self._num_received_operations
                if num_received == num_finished:
                    self._set_next_state(StateRegister.get('SyncDoneState'))
            return

        self._num_received_operations += 1

        if not self._context.worker_pool.acquire_worker():
            raise FileRockException(
                u"Concurrency trouble in %s: could not acquire a worker"
                " although some should have been available"
                % (self.__class__.__name__ + "._handle_file_operation"))

        if __debug__:
            self._context.worker_pool.track_acquire_anonymous_worker(
                operation.pathname)

        if not self._context.worker_pool.exist_free_workers():
            self._listening_operations = False

        self.logger.info(u'Synchronizing pathname: %s "%s"'
                         % (operation.verb, operation.pathname))

        with operation.lock:
            if operation.is_working():
                operation.register_complete_handler(self._on_complete_operation)
                operation.register_abort_handler(self._on_complete_operation)
                operation.register_reject_handler(sync_on_operation_rejected)
            else:
                self.logger.debug(u"Ignoring aborted operation:%s" % operation)
                self._num_received_operations -= 1
                self._context.worker_pool.release_worker()
                return

        CryptoUtils.prepare_operation(operation, self._context.temp_dir)
        self._pathname2operation[operation.pathname] = operation
        request = SYNC_GET_REQUEST("SYNC_GET_REQUEST",
                                   {'pathname': operation.pathname})
        #self.logger.debug(u"Produced Request message: %s", request)
        self._context.output_message_queue.put(request)
 def _handle_message_CHALLENGE_REQUEST_RESPONSE(self, message):
     """The server has agreed to start the authentication and has
     sent us a challange to sign.
     """
     challenge = str(message.getParameter("challenge"))
     # Sign challenge with private key
     signed = self.crypto.challenge_sign(challenge, open(self._context.priv_key, "r").read())
     # Create CHALLENGE_RESPONSE with signed challenge
     challenge_response_msg = CHALLENGE_RESPONSE(
         "CHALLENGE_RESPONSE", {"client_id": self._context.client_id, "response": signed}
     )
     if self._context.disconnect_other_client:
         key = "force_other_client_disconnection"
         challenge_response_msg.parameters[key] = True
     #            challenge_response_msg.set_force_other_client_disconnection()
     self._context.disconnect_other_client = False
     self._context.output_message_queue.put(challenge_response_msg)
     self._set_next_state(StateRegister.get("ChallengeResponseState"))
Exemple #42
0
    def _on_entering(self):
        """Persist the new basis, the storage cache and tell the UI
        about the completion of the sync phase.
        """
        temp = [op.is_completed() for op in self._context._sync_operations]
        all_done = reduce(lambda x, y: x and y, temp, True)

        if all_done:
            # All expected operations have been completed.
            self.logger.info(
                u"Startup Synchronization phase has completed successfully")
            self._context.output_message_queue.put(SYNC_DONE('SYNC_DONE'))

            completed_operations = self._context._sync_operations
            self._update_storage_cache(completed_operations)
            self._context.transaction.clear()
            # In case the server had sent a fresher basis
            # TODO: save into the basis history as well, if the basis has
            # changed (it happens, for example, if the client had crashed
            # on the last COMMIT_DONE message)
            self._persist_trusted_basis(self._context.integrity_manager.getCurrentBasis())

            self._context.metadataDB.delete_key(metadata.LASTACCEPTEDSTATEKEY)
            self._clear_candidate_basis()
            self._context.transaction_cache.clear()

            self._context._internal_facade.first_startup_end()
            self._context._ui_controller.update_session_info(
                {'basis': self._context.integrity_manager.getCurrentBasis()})
        else:
            # Some operations have been aborted. The basis can't be persisted,
            # because it doesn't match what is on disk. We must stop here and
            # repeat the synchronization.
            self.logger.info(u"Startup Synchronization interrupted due to"
                             " the abort of some operations.")
            self._context._internal_facade.pause()
            state = StateRegister.get('WaitingForTerminationState')
            self._set_next_state(state)

        self._context.worker_pool.clean_download_dir()
        self._context._sync_operations = []
Exemple #43
0
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis declared by the server and, if it is
        valid, then finalize the commit by updating all internal data
        structures. Finally go back to the Replication & Transfer state,
        we start again.
        """
        server_basis = message.getParameter('new_basis')
        self.logger.info(u'Commit done')
        self.logger.debug(u"Server basis: %s" % (server_basis))
        previous_basis = self._context.integrity_manager.getCurrentBasis()
        self.logger.debug(u"Previous basis: %s" % previous_basis)
        try:
            self._context.integrity_manager.checkCommitResult(server_basis)
        except WrongBasisAfterUpdatingException as e:
            state = GStatuses.C_HASHMISMATCHONCOMMIT
            self._context._internal_facade.set_global_status(state)
            self.logger.critical(
                u"Detected an integrity problem with data on the storage:"
                " server basis %s doesn't match our computed basis %s"
                % (server_basis, e.computed_basis))
            raise ProtocolException('WrongBasisAfterUpdatingException')
        new_basis = self._context.integrity_manager.getCurrentBasis()
        self._context._persist_basis(new_basis)
        self._context._save_basis_in_history(previous_basis, new_basis)
        self.logger.info(u"Updated basis: %s" % new_basis)
        completed_ops = self._context.transaction_manager.get_completed_operations()
        self._update_storage_cache(completed_ops)
        for (_, op) in self._context.transaction_manager.get_completed_operations():
            op.notify_pathname_status_change(PStatuses.ALIGNED)
        self._context.transaction_manager.clear()
        self._context.transaction_cache.clear()
        self._context.operation_responses.clear()
        self._context._clear_candidate_basis()
        self._context.refused_declare_count = 0
        self.logger.debug(
            u"Current transaction has been committed successfully.")
        self._update_user_interfaces(message)
        self._try_set_global_status_aligned()
        self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
Exemple #44
0
    def _on_entering(self):
        """ServerSession waits for all operations currently handled by
        workers to be completed, afterwards it begins the commit.

        Integrity of the transaction is checked and the "candidate basis"
        (the expected basis after our modifications to the storage) is
        computed.
        """
        self.logger.debug(u"Committing the current transaction...")

        # Block until all pending uploads are finished
        self.logger.debug(u"Waiting for the transaction to be finished...")
        self._context.transaction_manager.wait_until_finished()
        self.logger.debug(u"Transaction is finished!")

        self.logger.info(u"Committing the following pathnames:")
        operations = self._context.transaction_manager.get_completed_operations(
        )
        for (op_id, op) in operations:
            self.logger.info(u'    %s "%s"' % (op.verb, op.pathname))
            self.logger.debug(u"    id=%s %s" % (op_id, op))

        # Use the received proofs to compute the next expected basis
        self._check_transaction_integrity(operations)
        candidate_basis = self._context.integrity_manager.getCandidateBasis()
        self.logger.info("Candidate basis: %s" % candidate_basis)

        # Persist the expected state
        metadata = self._context.metadataDB
        transaction_cache = self._context.transaction_cache
        with metadata.transaction(transaction_cache) as (meta, trans):
            self._persist_candidate_basis(candidate_basis, metadata_db=meta)
            self._update_transaction_cache(trans, operations)

        # Ready to go, tell the server to start the commit!
        completed_operations_id = [op_id for (op_id, _) in operations]
        self._context.output_message_queue.put(
            COMMIT_START("COMMIT_START",
                         {'achieved_operations': completed_operations_id}))
        self._set_next_state(StateRegister.get('CommitStartState'))
    def _on_entering(self):
        """ServerSession waits for all operations currently handled by
        workers to be completed, afterwards it begins the commit.

        Integrity of the transaction is checked and the "candidate basis"
        (the expected basis after our modifications to the storage) is
        computed.
        """
        self.logger.debug(u"Committing the current transaction...")

        # Block until all pending uploads are finished
        self.logger.debug(u"Waiting for the transaction to be finished...")
        self._context.transaction_manager.wait_until_finished()
        self.logger.debug(u"Transaction is finished!")

        self.logger.info(u"Committing the following pathnames:")
        operations = self._context.transaction_manager.get_completed_operations()
        for (op_id, op) in operations:
            self.logger.info(u'    %s "%s"' % (op.verb, op.pathname))
            self.logger.debug(u"    id=%s %s" % (op_id, op))

        # Use the received proofs to compute the next expected basis
        self._check_transaction_integrity(operations)
        candidate_basis = self._context.integrity_manager.getCandidateBasis()
        self.logger.info("Candidate basis: %s" % candidate_basis)

        # Persist the expected state
        metadata = self._context.metadataDB
        transaction_cache = self._context.transaction_cache
        with metadata.transaction(transaction_cache) as (meta, trans):
            self._persist_candidate_basis(candidate_basis, metadata_db=meta)
            self._update_transaction_cache(trans, operations)

        # Ready to go, tell the server to start the commit!
        completed_operations_id = [op_id for (op_id, _) in operations]
        self._context.output_message_queue.put(COMMIT_START(
            "COMMIT_START", {'achieved_operations': completed_operations_id}))
        self._set_next_state(StateRegister.get('CommitStartState'))
Exemple #46
0
    def _handle_message_COMMIT_DONE(self, message):
        """Everything went well, the server has completed the commit.

        Check the resulting basis sent by the server against our
        candidate basis, if it's valid then finalize the commit by
        updating all internal data structures.
        Finally go into the sync phase, the session can begin at last.
        """
        self.logger.info(u'Commit done')
        server_basis = message.getParameter('new_basis')
        # candidate_basis = self._context._load_candidate_basis()
        self.logger.debug(u"Server basis: %s" % server_basis)
        # self.logger.debug(u"Candidate basis: %s" % candidate_basis)
        # self._context.integrity_manager.setCurrentBasis(candidate_basis)
        # try:
        #     self._context.integrity_manager.checkCommitResult(server_basis)
        # except WrongBasisAfterUpdatingException:
        #     state = GStatus.C_HASHMISMATCHONCOMMIT
        #     self._context._internal_facade.set_global_status(state)
        #     self.logger.critical(
        #         u"Detected an integrity problem with data on the storage:"
        #         " server basis %s doesn't match our expected basis %s"
        #         % (server_basis, candidate_basis))
        #     raise ProtocolException('WrongBasisAfterUpdatingException')
        previous_basis = self._context._load_trusted_basis()
        self.logger.debug(u"Last trusted basis: %s" % previous_basis)
        self._context._persist_basis(server_basis)
        self._context._save_basis_in_history(previous_basis, server_basis)
        self._context.integrity_manager.setCurrentBasis(server_basis)
        self._context._clear_candidate_basis()
        self._context.transaction_cache.clear()
        self.logger.info(u"Pending commit successfully recovered.")
        self.logger.info(u"Updated basis: %s" % server_basis)
        self._update_user_interfaces(message)
        self._set_next_state(StateRegister.get('ReadyForServiceState'))
        self._context._input_queue.put(Command('STARTSYNCPHASE'),
                                       'sessioncommand')
Exemple #47
0
    def _handle_message_CHALLENGE_REQUEST_RESPONSE(self, message):
        """The server has agreed to start the authentication and has
        sent us a challange to sign.
        """
        challenge = str(message.getParameter('challenge'))
        # Sign challenge with private key
        signed = self.crypto.challenge_sign(
            challenge,
            open(self._context.priv_key, 'r').read())
        # Create CHALLENGE_RESPONSE with signed challenge
        challenge_response_msg = CHALLENGE_RESPONSE(
            'CHALLENGE_RESPONSE', {
                'client_id': self._context.client_id,
                'response': signed
            })
        if self._context.disconnect_other_client:
            key = "force_other_client_disconnection"
            challenge_response_msg.parameters[key] = True


#            challenge_response_msg.set_force_other_client_disconnection()
        self._context.disconnect_other_client = False
        self._context.output_message_queue.put(challenge_response_msg)
        self._set_next_state(StateRegister.get('ChallengeResponseState'))
 def _handle_command_USERCOMMIT(self, message):
     """The user has asked to commit, the operation will be recovered on
     next R&T loop.
     """
     self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
     self._context._input_queue.put(Command('USERCOMMIT'), 'sessioncommand')
Exemple #49
0
 def _pass_to_next_state(self):
     self._context.transaction.wait_until_finished()
     self._set_next_state(StateRegister.get(self._next_state))
Exemple #50
0
    def _handle_message_SYNC_FILES_LIST(self, message):
        """Received the remote filelist from the server. Compute
        differences, perform integrity checks on it and ask the user
        confirmation to proceed with the synchronization.

        Message: SYNC_FILES_LIST
        Parameters:
            last_commit_client_id: String or None
            last_commit_client_hostname: String or None
            last_commit_client_platform: String or None
            last_commit_timestamp: Number
            used_space: Number
            basis: String
            user_quota: Number
        """
        storage_content = message.getParameter('dataset')
        self.storage_content = storage_content
        self._validate_storage_content(storage_content)

        self.server_basis = message.getParameter('basis')
        self.candidate_basis = self._context._try_load_candidate_basis()
        self.client_basis = self._context._load_trusted_basis()

        fields = [
            'last_commit_client_id',
            'last_commit_client_hostname',
            'last_commit_client_platform',
            'last_commit_timestamp',
            'used_space',
            'user_quota'
        ]
        info = dict(map(lambda f: (f, message.getParameter(f)), fields))
        self._context._ui_controller.update_session_info(info)

        # No blacklisted pathname should be found on the storage. If any, tell
        # the user and then shut down the application.
        remote_pathnames = [entry['key'] for entry in storage_content]
        blacklisted = filter(
            lambda x: self._context.warebox.is_blacklisted(x),
            remote_pathnames)
        if len(blacklisted) > 0:
            self.logger.critical(
                'The following blacklisted pathnames have been found on the '
                'storage %s' % format_to_log(blacklisted))
            self._context._ui_controller.ask_for_user_input(
                'blacklisted_pathname_on_storage', blacklisted)
#            self._internal_facade.terminate()
            self._context._internal_facade.pause()
            return

        # Detect actions to be taken for synchronization as well as conflicts
        self.logger.debug(u"Starting computing the three-way diff...")
        self._context.startup_synchronization.prepare(storage_content)
        self.logger.debug(u"Finished computing the three-way diff.")

        # Conflicts on encrypted files need extra data from the server to be
        # solved. If there are any, handle them.
        encrypted_pathnames = CryptoUtils.filter_encrypted_pathname(
            self._context.startup_synchronization.edit_conflicts)
        if len(encrypted_pathnames) > 0:
            self.logger.debug(
                u'Encrypted file in conflict: %r' % encrypted_pathnames)
            message = SYNC_GET_ENCRYPTED_FILES_IVS(
                'SYNC_GET_ENCRYPTED_FILES_IVS',
                {'requested_files_list': encrypted_pathnames})
            self._context.output_message_queue.put(message)
            return

        # If there are no conflicts on encrypted files, just proceed normally.
        try:
            if self._check_hash_mismatch():
                self._start_syncing()
            else:
#                self._internal_facade.terminate()
                self._context._internal_facade.pause()
        except HashMismatchException as excp:
            self.logger.critical('BASISMISMATCH %s' % excp)
            self._set_next_state(StateRegister.get('BasisMismatchState'))
 def _handle_command_COMMIT(self, command):
     """Go to the commit state to start a commit.
     """
     self.logger.info(u"Committing the current transaction")
     self._set_next_state(
         StateRegister.get('WaitingForUnauthorizedOperationsState'))
 def _handle_command_REDECLAREOPERATION(self, command):
     """It's time, go back to R&T and let's declare the operation one
     more time.
     """
     self._set_next_state(StateRegister.get('ReplicationAndTransferState'))
def test_download_operations_from_clean_state(
                        adapter_mock, workerpool_mock, connection_lifekeeper,
                        disconnectedstate_cls_mock, downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister
    from FileRockSharedLibraries.Communication.Messages import SYNC_FILES_LIST
    from filerockclient.interfaces import PStatuses

    components = setup_server_session(
                        __file__,
                        adapter_mock, workerpool_mock, connection_lifekeeper,
                        disconnectedstate_cls_mock, downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    def terminate():
        components['real']['server_session'].terminate()
        return syncstart_state

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    # Stop the test when we get to SyncDownloadingLeavesState
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = terminate

    # There is nothing in the warebox
    components['mock']['warebox'].get_content.return_value = []

    components['real']['metadata'].set('trusted_basis', 'TRUSTEDBASIS')

    # Send ServerSession a scenario with one file to download.
    storage_content = [
        {
            u'key': u'File.txt',
            u'etag': u'"d41d8cd98f00b204e9800998ecf8427e"',
            u'lmtime': u'1970-01-01T10:00:00.000Z',
            u'size': u'1'
        }
    ]
    msg = SYNC_FILES_LIST('SYNC_FILES_LIST', {
                'basis': 'NEWBASIS',
                'dataset': storage_content,
                'last_commit_client_id': '0',
                'last_commit_client_hostname': 'myhostname',
                'last_commit_client_platform': 'myplatform',
                'last_commit_timestamp': 'mytimestamp',
                'user_quota': '0',
                'used_space': '100'
                })
    components['real']['input_queue'].put(msg, 'servermessage')

    def user_accepts_sync(what, content, client_basis, server_basis):
        assert_equal(client_basis, 'TRUSTEDBASIS')
        assert_equal(server_basis, 'NEWBASIS')
        assert_equal(len(content), 1)
        assert_equal(content[0]['pathname'], 'File.txt')
        assert_equal(content[0]['status'], PStatuses.DOWNLOADNEEDED)
        assert_equal(content[0]['size'], 1)
        return 'ok'

    components['mock']['ui_controller'].ask_for_user_input.side_effect = user_accepts_sync

    components['real']['server_session'].start()
    components['real']['server_session'].join()

    # We expect to download a file
    assert_true(components['mock']['ui_controller'].ask_for_user_input.called)
    assert_equal(components['real']['storage_cache'].get_all(), [])
    assert_false(components['real']['input_queue'].empty(['operation']))
    operation, _ = components['real']['input_queue'].get(['operation'])
    assert_equal(operation.pathname, 'File.txt')
    assert_equal(operation.verb, 'DOWNLOAD')
Exemple #54
0
def test_download_operations_from_clean_state(adapter_mock, workerpool_mock,
                                              connection_lifekeeper,
                                              disconnectedstate_cls_mock,
                                              downloadstate_cls_mock):

    from filerockclient.serversession.states.register import StateRegister
    from FileRockSharedLibraries.Communication.Messages import SYNC_FILES_LIST
    from filerockclient.interfaces import PStatuses

    components = setup_server_session(__file__, adapter_mock, workerpool_mock,
                                      connection_lifekeeper,
                                      disconnectedstate_cls_mock,
                                      downloadstate_cls_mock)

    # Note: the StateRegister singleton has been initialized by
    # ServerSession's constructor
    syncstart_state = StateRegister.get('SyncStartState')

    def terminate():
        components['real']['server_session'].terminate()
        return syncstart_state

    # DisconnectedState is the default initial state, but we want to start
    # from SyncStartState
    disconnected_state_mock = disconnectedstate_cls_mock()
    disconnected_state_mock.do_execute.return_value = syncstart_state

    # Stop the test when we get to SyncDownloadingLeavesState
    download_state_mock = downloadstate_cls_mock()
    download_state_mock.do_execute.side_effect = terminate

    # There is nothing in the warebox
    components['mock']['warebox'].get_content.return_value = []

    components['real']['metadata'].set('trusted_basis', 'TRUSTEDBASIS')

    # Send ServerSession a scenario with one file to download.
    storage_content = [{
        u'key': u'File.txt',
        u'etag': u'"d41d8cd98f00b204e9800998ecf8427e"',
        u'lmtime': u'1970-01-01T10:00:00.000Z',
        u'size': u'1'
    }]
    msg = SYNC_FILES_LIST(
        'SYNC_FILES_LIST', {
            'basis': 'NEWBASIS',
            'dataset': storage_content,
            'last_commit_client_id': '0',
            'last_commit_client_hostname': 'myhostname',
            'last_commit_client_platform': 'myplatform',
            'last_commit_timestamp': 'mytimestamp',
            'user_quota': '0',
            'used_space': '100'
        })
    components['real']['input_queue'].put(msg, 'servermessage')

    def user_accepts_sync(what, content, client_basis, server_basis):
        assert_equal(client_basis, 'TRUSTEDBASIS')
        assert_equal(server_basis, 'NEWBASIS')
        assert_equal(len(content), 1)
        assert_equal(content[0]['pathname'], 'File.txt')
        assert_equal(content[0]['status'], PStatuses.DOWNLOADNEEDED)
        assert_equal(content[0]['size'], 1)
        return 'ok'

    components['mock'][
        'ui_controller'].ask_for_user_input.side_effect = user_accepts_sync

    components['real']['server_session'].start()
    components['real']['server_session'].join()

    # We expect to download a file
    assert_true(components['mock']['ui_controller'].ask_for_user_input.called)
    assert_equal(components['real']['storage_cache'].get_all_records(), [])
    assert_false(components['real']['input_queue'].empty(['operation']))
    operation, _ = components['real']['input_queue'].get(['operation'])
    assert_equal(operation.pathname, 'File.txt')
    assert_equal(operation.verb, 'DOWNLOAD')
 def _pass_to_next_state(self):
     """Go to the commit state.
     """
     self.logger.debug(
         u"All operations have been authorized, let's commit.")
     self._set_next_state(StateRegister.get('CommitState'))
    def _handle_message_SYNC_FILES_LIST(self, message):
        """Received the remote filelist from the server. Compute
        differences, perform integrity checks on it and ask the user
        confirmation to proceed with the synchronization.

        Message: SYNC_FILES_LIST
        Parameters:
            last_commit_client_id: String or None
            last_commit_client_hostname: String or None
            last_commit_client_platform: String or None
            last_commit_timestamp: Number
            used_space: Number
            basis: String
            user_quota: Number

            optional parameters:

            plan: a dictionary as follows (mandatory)
                      { id: <plan_id>,    # a number
                        space: <plan_space_in_GB>,   # a number (within a plan this is mandatory and 'not None')
                        price: <price_in_$>,      # a number    (if absent or ==None it means "free")
                        payment_type: <(SINGLE|SUBSCRIPTION)>,   # unicode  (present if price is not None)
                        payment_recurrence: <(MONTHLY|YEARLY)>   # unicode  (present if price is not None)
                        }
            expires_on: <GMT-Date-or-None>    # a number representing a unix timestamp UTC (mandatory)
                        (it might None if plan is "forever", this is the expiration date of the subscription,
                         it does not change when in grace time).
            status: <(TRIAL|ACTIVE|GRACE|SUSPENDED|MAINTAINANCE)>  # unicode (mandatory)


        """
        storage_content = message.getParameter('dataset')
        self.storage_content = storage_content
        self._validate_storage_content(storage_content)

        self.server_basis = message.getParameter('basis')
        self.candidate_basis = self._try_load_candidate_basis()
        self.client_basis = self._load_trusted_basis()

        fields = [
            'last_commit_client_id', 'last_commit_client_hostname',
            'last_commit_client_platform', 'last_commit_timestamp',
            'used_space', 'user_quota', 'plan', 'status', 'expires_on'
        ]
        info = dict(map(lambda f: (f, message.getParameter(f)), fields))

        # trial
        # info.update(status='ACTIVE_TRIAL',
        #             expires_on=1366814411,
        #             plan=dict(
        #                 space=1
        #                 )
        #             )

        # beta
        # info.update(status='ACTIVE_BETA',
        #             expires_on=None,
        #             plan=dict(
        #                 space=3
        #                 )
        #             )

        # expired
        # info.update(status='ACTIVE_GRACE',
        #             expires_on=1366810000,
        #             plan=dict(
        #                 space=1,
        #                 payment_type='SUBSCRIPTION',
        #                 payment_recurrence='MONTHLY'
        #                 )
        #             )

        # good yearly
        # info.update(status='ACTIVE_PAID',
        #             expires_on=1366810000,
        #             plan=dict(
        #                 space=1,
        #                 payment_type='SUBSCRIPTION',
        #                 payment_recurrence='YEARLY'
        #                 )
        #             )

        self._context._ui_controller.update_session_info(info)

        # No blacklisted pathname should be found on the storage. If any, tell
        # the user and then shut down the application.
        remote_pathnames = [entry['key'] for entry in storage_content]
        blacklisted = filter(lambda x: self._context.warebox.is_blacklisted(x),
                             remote_pathnames)
        if len(blacklisted) > 0:
            self.logger.critical(
                'The following blacklisted pathnames have been found on the '
                'storage %s' % format_to_log(blacklisted))
            self._context._ui_controller.ask_for_user_input(
                'blacklisted_pathname_on_storage', blacklisted)
            #            self._internal_facade.terminate()
            self._context._internal_facade.pause()
            return

        # Detect actions to be taken for synchronization as well as conflicts
        self.logger.debug(u"Starting computing the three-way diff...")
        try:
            self._context.startup_synchronization.prepare(
                storage_content, self._context.must_die)
        except ExecutionInterrupted:
            self.logger.debug(u'ExecutionInterrupted, terminating...')
            self._set_next_state(StateRegister.get('DisconnectedState'))
            return
        self.logger.debug(u"Finished computing the three-way diff.")

        # Conflicts on encrypted files need extra data from the server to be
        # solved. If there are any, handle them.
        encrypted_pathnames = CryptoUtils.filter_encrypted_pathname(
            self._context.startup_synchronization.edit_conflicts)
        if len(encrypted_pathnames) > 0:
            self.logger.debug(u'Encrypted file in conflict: %r' %
                              encrypted_pathnames)
            message = SYNC_GET_ENCRYPTED_FILES_IVS(
                'SYNC_GET_ENCRYPTED_FILES_IVS',
                {'requested_files_list': encrypted_pathnames})
            self._context.output_message_queue.put(message)
            return

        # If there are no conflicts on encrypted files, just proceed normally.
        try:
            if self._check_hash_mismatch():
                self._start_syncing()
            else:
                #                self._internal_facade.terminate()
                self._context._internal_facade.pause()
        except HashMismatchException as excp:
            self.logger.critical('Integrity error %s' % excp)
            self._set_next_state(StateRegister.get('IntegrityErrorState'))