Ejemplo n.º 1
0
 def setUp(self):
     self.user = auth.User()
     self.io_loop = mock.Mock()
     self.write_message = mock.Mock()
     self.tokens = auth.AuthenticationTokenHandler(io_loop=self.io_loop)
     self.auth = auth.AuthMiddleware(self.user, self.get_auth_backend(),
                                     self.tokens, self.write_message)
Ejemplo n.º 2
0
 def test_not_authenticated_repr(self):
     # A not authenticated user is correctly represented.
     user = auth.User(username='******',
                      password='******',
                      is_authenticated=False)
     expected = '<User: the-doctor (not authenticated)>'
     self.assertEqual(expected, repr(user))
Ejemplo n.º 3
0
 def test_process_authentication_response(self):
     # It translates a normal authentication success.
     user = auth.User('user-admin', 'ADMINSECRET', True)
     response = {'RequestId': 42, 'Response': {}}
     self.assertEqual(
         dict(RequestId=42,
              Response=dict(AuthTag=user.username, Password=user.password)),
         self.tokens.process_authentication_response(response, user))
Ejemplo n.º 4
0
 def setUp(self):
     # Create a DeployMiddleware instance.
     super(TestDeployMiddleware, self).setUp()
     self.user = auth.User(
         username='******', password='******', is_authenticated=True)
     self.deployer = self.make_deployer()
     self.responses = []
     self.deployment = base.DeployMiddleware(
         self.user, self.deployer, self.responses.append)
Ejemplo n.º 5
0
 def test_unauthenticated_process_token_request(self):
     # Unauthenticated token requests get an informative error.
     user = auth.User(is_authenticated=False)
     write_message = mock.Mock()
     data = dict(RequestId=42, Type='GUIToken', Request='Create')
     self.tokens.process_token_request(data, user, write_message)
     write_message.assert_called_once_with(
         dict(RequestId=42,
              Error='tokens can only be created by authenticated users.',
              ErrorCode='unauthorized access',
              Response={}))
     self.assertEqual({}, self.tokens._data)
     self.assertFalse(self.io_loop.add_timeout.called)
Ejemplo n.º 6
0
    def make_view_request(self, params=None, is_authenticated=True):
        """Create and return a mock request to be passed to bundle views.

        The resulting request contains the given parameters and a
        guiserver.auth.User instance.
        If is_authenticated is True, the user in the request is logged in.
        """
        if params is None:
            params = {}
        user = auth.User(username='******',
                         password='******',
                         is_authenticated=is_authenticated)
        return mock.Mock(params=params, user=user)
Ejemplo n.º 7
0
 def test_token_request_and_authentication_collaborate(self):
     # process_token_request and process_authentication_request collaborate.
     # This is a small integration test of the two functions' interaction.
     user = auth.User('user-admin', 'ADMINSECRET', True)
     write_message = mock.Mock()
     request = dict(RequestId=42, Type='GUIToken', Request='Create')
     self.tokens.process_token_request(request, user, write_message)
     request = dict(RequestId=43,
                    Type='GUIToken',
                    Request='Login',
                    Params={'Token': 'DEFACED'})
     self.assertEqual(
         (user.username, user.password),
         self.tokens.process_authentication_request(request, write_message))
Ejemplo n.º 8
0
 def test_process_authentication_response(self):
     # It translates a normal authentication success.
     user = auth.User('user-admin', 'ADMINSECRET', True)
     original_response = {'RequestId': 42, 'Response': {'facades': {}}}
     expected_response = {
         'RequestId': 42,
         'Response': {
             'facades': {},
             'AuthTag': user.username,
             'Password': user.password,
         },
     }
     obtained_response = self.tokens.process_authentication_response(
         original_response, user)
     self.assertEqual(expected_response, obtained_response)
     # The original response is not mutated in the process.
     self.assertNotEqual(original_response, obtained_response)
Ejemplo n.º 9
0
 def test_process_token_request(self):
     # It correctly responds to token requests.
     user = auth.User('user-admin', 'ADMINSECRET', True)
     write_message = mock.Mock()
     data = dict(RequestId=42, Type='GUIToken', Request='Create')
     self.tokens.process_token_request(data, user, write_message)
     write_message.assert_called_once_with(
         dict(RequestId=42,
              Response=dict(Token='DEFACED',
                            Created='2013-11-21T21:00:00Z',
                            Expires='2013-11-21T21:01:00Z')))
     self.assertTrue('DEFACED' in self.tokens._data)
     self.assertEqual({'username', 'password', 'handle'},
                      set(self.tokens._data['DEFACED'].keys()))
     self.assertEqual(user.username,
                      self.tokens._data['DEFACED']['username'])
     self.assertEqual(user.password,
                      self.tokens._data['DEFACED']['password'])
     self.assertEqual(self.max_life,
                      self.io_loop.add_timeout.call_args[0][0])
     self.assertTrue('DEFACED' in self.tokens._data)
     expire_token = self.io_loop.add_timeout.call_args[0][1]
     expire_token()
     self.assertFalse('DEFACED' in self.tokens._data)
Ejemplo n.º 10
0
 def test_str(self):
     # The string representation of an user is correctly generated.
     user = auth.User(username='******')
     self.assertEqual('the-doctor', str(user))
Ejemplo n.º 11
0
 def test_anonymous_repr(self):
     # An anonymous user is correctly represented.
     user = auth.User()
     expected = '<User: anonymous (not authenticated)>'
     self.assertEqual(expected, repr(user))
Ejemplo n.º 12
0
class TestDeployer(helpers.BundlesTestMixin, LogTrapTestCase, AsyncTestCase):

    bundle = {'foo': 'bar'}
    user = auth.User(
        username='******', password='******', is_authenticated=True)
    version = 4

    def assert_change(
            self, changes, deployment_id, status, queue=None, error=None):
        """Ensure only one change is present in the given changes.

        Also check the change refers to the expected deployment id and status.
        Optionally also ensure the change includes the queue and error values.
        """
        self.assertEqual(1, len(changes))
        expected = {
            'DeploymentId': deployment_id,
            'Status': status,
            'Time': 42,
        }
        if queue is not None:
            expected['Queue'] = queue
        if error is not None:
            expected['Error'] = error
        self.assertEqual(expected, changes[0])

    @gen_test
    def test_validation_success(self):
        # None is returned if the validation succeeds.
        deployer = self.make_deployer()
        with self.patch_validate():
            result = yield deployer.validate(self.user, self.bundle)
        self.assertIsNone(result)

    @gen_test
    def test_validation_failure(self):
        # An error message is returned if the validation fails.
        deployer = self.make_deployer()
        error = ValueError('validation error')
        with self.patch_validate(side_effect=error):
            result = yield deployer.validate(self.user, self.bundle)
        self.assertEqual(str(error), result)

    @gen_test
    def test_validation_process(self):
        # The validation is executed in a separate process.
        deployer = self.make_deployer()
        with self.patch_validate() as mock_validate:
            yield deployer.validate(self.user, self.bundle)
        mock_validate.assert_called_once_with(
            self.apiurl, self.user.username, self.user.password, self.bundle)
        mock_validate.assert_called_in_a_separate_process()

    @gen_test
    def test_unsupported_api_version(self):
        # An error message is returned the API version is not supported.
        deployer = self.make_deployer(apiversion='not-supported')
        result = yield deployer.validate(self.user, self.bundle)
        self.assertEqual('unsupported API version: not-supported', result)

    def test_import_bundle_scheduling(self):
        # A deployment id is returned if the bundle import process is
        # successfully scheduled.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        self.assertIsInstance(deployment_id, int)
        # Wait for the deployment to be completed.
        self.wait()

    def test_import_bundle_process(self):
        # The deployment is executed in a separate process.
        deployer = self.make_deployer()
        with self.patch_import_bundle() as mock_import_bundle:
            deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        # Wait for the deployment to be completed.
        self.wait()
        mock_import_bundle.assert_called_once_with(
            self.apiurl, self.user.username, self.user.password, 'bundle',
            self.bundle, self.version, deployer.importer_options)
        mock_import_bundle.assert_called_in_a_separate_process()

    def test_options_are_fully_populated(self):
        # The options passed to the deployer match what it expects and are not
        # missing any entries.
        deployer = self.make_deployer()
        default_options = deployer_cli.setup_parser().parse_args([]).__dict__
        expected_options = set(default_options.keys())
        passed_options = set(deployer.importer_options.__dict__.keys())
        self.assertEqual(expected_options, passed_options)

    def test_watch(self):
        # To start observing a deployment progress, a client can obtain a
        # watcher id for the given deployment job.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher_id = deployer.watch(deployment_id)
        self.assertIsInstance(watcher_id, int)
        # Wait for the deployment to be completed.
        self.wait()

    def test_watch_unknown_deployment(self):
        # None is returned if a client tries to observe an invalid deployment.
        deployer = self.make_deployer()
        self.assertIsNone(deployer.watch(42))

    @gen_test
    def test_next(self):
        # A client can be asynchronously notified of deployment changes.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher_id = deployer.watch(deployment_id)
        # A first change is received notifying that the deployment is started.
        changes = yield deployer.next(watcher_id)
        self.assert_change(changes, deployment_id, utils.STARTED, queue=0)
        # A second change is received notifying a completed deployment.
        changes = yield deployer.next(watcher_id)
        self.assert_change(changes, deployment_id, utils.COMPLETED)
        # Only the last change is notified to new subscribers.
        watcher_id = deployer.watch(deployment_id)
        changes = yield deployer.next(watcher_id)
        self.assert_change(changes, deployment_id, utils.COMPLETED)
        # Wait for the deployment to be completed.
        self.wait()

    @gen_test
    def test_multiple_deployments(self):
        # Multiple deployments can be scheduled and observed.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment1 = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None)
            deployment2 = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher1 = deployer.watch(deployment1)
        watcher2 = deployer.watch(deployment2)
        # The first deployment is started.
        changes = yield deployer.next(watcher1)
        self.assert_change(changes, deployment1, utils.STARTED, queue=0)
        # The second deployment is scheduled and will only start after the
        # first one is done.
        changes = yield deployer.next(watcher2)
        self.assert_change(changes, deployment2, utils.SCHEDULED, queue=1)
        # The first deployment completes.
        changes = yield deployer.next(watcher1)
        self.assert_change(changes, deployment1, utils.COMPLETED)
        # The second one is started.
        changes = yield deployer.next(watcher2)
        self.assert_change(changes, deployment2, utils.STARTED, queue=0)
        # Wait for the deployment to be completed.
        self.wait()

    @gen_test
    def test_deployment_failure(self):
        # An error change is notified if the deployment process fails.
        deployer = self.make_deployer()
        with self.patch_import_bundle(side_effect=RuntimeError('bad wolf')):
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher_id = deployer.watch(deployment_id)
        # We expect two changes: the second one should include the error.
        yield deployer.next(watcher_id)
        changes = yield deployer.next(watcher_id)
        self.assert_change(
            changes, deployment_id, utils.COMPLETED, error='bad wolf')
        # Wait for the deployment to be completed.
        self.wait()

    def test_import_bundle_exception_propagation(self):
        # An EnvError is correctly propagated from the separate process to the
        # main thread.
        deployer = self.make_deployer()
        import_bundle_path = 'guiserver.bundles.base.blocking.import_bundle'
        with mock.patch(import_bundle_path, import_bundle_mock):
            deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        # Wait for the deployment to be completed.
        self.wait()
        status = deployer.status()
        self.assertEqual(1, len(status))
        expected = {
            'DeploymentId': 0,
            'Status': utils.COMPLETED,
            'Error': "bad wolf",
            'Time': 42,
        }
        self.assertEqual(expected, status[0])

    def test_invalid_watcher(self):
        # None is returned if the watcher id is not valid.
        deployer = self.make_deployer()
        changes = deployer.next(42)
        self.assertIsNone(changes)

    @gen_test
    def test_cancel(self):
        # It is possible to cancel the execution of a pending deployment.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            # The test callback is passed to the first deployment because we
            # expect the second one to be immediately cancelled.
            deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None)
        watcher_id = deployer.watch(deployment_id)
        self.assertIsNone(deployer.cancel(deployment_id))
        # We expect two changes: the second one should notify the deployment
        # has been cancelled.
        yield deployer.next(watcher_id)
        changes = yield deployer.next(watcher_id)
        self.assert_change(changes, deployment_id, utils.CANCELLED)
        # Wait for the deployment to be completed.
        self.wait()

    def test_cancel_unknown_deployment(self):
        # An error is returned when trying to cancel an invalid deployment.
        deployer = self.make_deployer()
        error = deployer.cancel(42)
        self.assertEqual('deployment not found or already completed', error)

    @gen_test
    def test_cancel_completed_deployment(self):
        # An error is returned when trying to cancel a completed deployment.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher_id = deployer.watch(deployment_id)
        # Assume the deployment is completed after two changes.
        yield deployer.next(watcher_id)
        yield deployer.next(watcher_id)
        error = deployer.cancel(deployment_id)
        self.assertEqual('deployment not found or already completed', error)
        # Wait for the deployment to be completed.
        self.wait()

    @gen_test
    def test_cancel_started_deployment(self):
        # An error is returned when trying to cancel a deployment already
        # started.
        deployer = self.make_deployer()
        with self.patch_import_bundle() as mock_import_bundle:
            deployment_id = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        watcher_id = deployer.watch(deployment_id)
        # Wait until the deployment is started.
        yield deployer.next(watcher_id)
        while True:
            if mock_import_bundle.call_count:
                break
        error = deployer.cancel(deployment_id)
        self.assertEqual('unable to cancel the deployment', error)
        # Wait for the deployment to be completed.
        yield deployer.next(watcher_id)
        self.wait()

    def test_initial_status(self):
        # The initial deployer status is an empty list.
        deployer = self.make_deployer()
        self.assertEqual([], deployer.status())

    def test_status(self):
        # The status contains the last known change for each deployment.
        deployer = self.make_deployer()
        with self.patch_import_bundle():
            deployment1 = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None)
            deployment2 = deployer.import_bundle(
                self.user, 'bundle', self.bundle, self.version, bundle_id=None,
                test_callback=self.stop)
        # Wait for the deployment to be completed.
        self.wait()
        # At this point we expect two completed deployments.
        change1, change2 = deployer.status()
        self.assertEqual(utils.COMPLETED, change1['Status'])
        self.assertEqual(utils.COMPLETED, change2['Status'])
        self.assertEqual(deployment1, change1['DeploymentId'])
        self.assertEqual(deployment2, change2['DeploymentId'])

    def test_import_callback_cancelled(self):
        deployer = self.make_deployer()
        deployer_id = 123
        deployer._queue.append(deployer_id)
        deployer._futures[deployer_id] = None
        mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
        future = FakeFuture(True)
        with mock.patch.object(
                deployer._observer, 'notify_cancelled') as mock_notify:
            with mock.patch(mock_path) as mock_incrementer:
                deployer._import_callback(deployer_id, None, future)
        mock_notify.assert_called_with(deployer_id)
        self.assertFalse(mock_incrementer.called)

    def test_import_callback_error(self):
        deployer = self.make_deployer()
        deployer_id = 123
        deployer._queue.append(deployer_id)
        deployer._futures[deployer_id] = None
        mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
        future = FakeFuture(exception='aiiee')
        with mock.patch.object(
                deployer._observer, 'notify_completed') as mock_notify:
            with mock.patch(mock_path) as mock_incrementer:
                deployer._import_callback(deployer_id, None, future)
        mock_notify.assert_called_with(deployer_id, error='aiiee')
        self.assertFalse(mock_incrementer.called)

    def test_import_callback_no_bundleid(self):
        deployer = self.make_deployer()
        deployer_id = 123
        deployer._queue.append(deployer_id)
        deployer._futures[deployer_id] = None
        mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
        future = FakeFuture()
        with mock.patch.object(
                deployer._observer, 'notify_completed') as mock_notify:
            with mock.patch(mock_path) as mock_incrementer:
                deployer._import_callback(deployer_id, None, future)
        mock_notify.assert_called_with(deployer_id, error=None)
        self.assertFalse(mock_incrementer.called)

    def test_import_callback_success(self):
        deployer = self.make_deployer()
        deployer_id = 123
        bundle_id = '~jorge/basket/bundle'
        deployer._charmworldurl = 'http://cw.example.com'
        deployer._queue.append(deployer_id)
        deployer._futures[deployer_id] = None
        mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
        future = FakeFuture()
        with mock.patch.object(
                deployer._observer, 'notify_completed') as mock_notify:
            with mock.patch(mock_path) as mock_incrementer:
                deployer._import_callback(deployer_id, bundle_id, future)
        mock_notify.assert_called_with(deployer_id, error=None)
        mock_incrementer.assert_called_with(bundle_id, deployer._charmworldurl)