Example #1
0
 def test_get_recent_deployments(self):
     file_content = textwrap.dedent("""\
         nomulus-20200925-RC02,backend,nomulus-backend-v008
         nomulus-20200925-RC02,default,nomulus-default-v008
         """)
     self._version_map_blob.download_as_text.return_value = file_content
     self.assertEqual(
         self._client.get_recent_deployments(self._ENV, 2), {
             common.VersionKey('default', 'nomulus-default-v008'):
             'nomulus-20200925-RC02',
             common.VersionKey('backend', 'nomulus-backend-v008'):
             'nomulus-20200925-RC02'
         })
Example #2
0
 def test_get_versions_by_release_multiple_deployment(self):
     self._version_map_blob.download_as_text.return_value = textwrap.dedent(
         """\
         nomulus-20200925-RC02,backend,nomulus-backend-v008
         nomulus-20200925-RC02,backend,nomulus-backend-v018
         """)
     self.assertEqual(
         self._client.get_versions_by_release(self._ENV,
                                              'nomulus-20200925-RC02'),
         frozenset([
             common.VersionKey('backend', 'nomulus-backend-v008'),
             common.VersionKey('backend', 'nomulus-backend-v018')
         ]))
Example #3
0
 def test_get_versions_by_release(self):
     self._version_map_blob.download_as_text.return_value = \
         'nomulus-20200925-RC02,backend,nomulus-backend-v008'
     self.assertEqual(
         self._client.get_versions_by_release(self._ENV,
                                              'nomulus-20200925-RC02'),
         frozenset([common.VersionKey('backend', 'nomulus-backend-v008')]))
Example #4
0
    def get_serving_versions(self) -> FrozenSet[common.VersionKey]:
        """Returns the serving versions of every Nomulus service.

        For each service in appengine.SERVICES, gets the version(s) actually
        serving traffic. Services with the 'SERVING' status but no allocated
        traffic are not included. Services not included in appengine.SERVICES
        are also ignored.

        Returns: An immutable collection of the serving versions grouped by
            service.
        """
        services = common.list_all_pages(self._services.list,
                                         'services',
                                         appsId=self._project)

        # Response format is specified at
        # http://googleapis.github.io/google-api-python-client/docs/dyn/appengine_v1beta.apps.services.html#list.

        versions = []
        for service in services:
            if service['id'] in SERVICES:
                # yapf: disable
                versions_with_traffic = (
                    service.get('split', {}).get('allocations', {}).keys())
                # yapf: enable
                for version in versions_with_traffic:
                    versions.append(common.VersionKey(service['id'], version))

        return frozenset(versions)
Example #5
0
 def test_get_releases_by_versions(self):
     self._version_map_blob.download_as_text.return_value = textwrap.dedent(
         """\
         nomulus-20200925-RC02,backend,nomulus-backend-v008
         nomulus-20200925-RC02,default,nomulus-default-v008
         """)
     self.assertEqual(
         self._client.get_releases_by_versions(
             self._ENV, {
                 common.VersionKey('backend', 'nomulus-backend-v008'),
                 common.VersionKey('default', 'nomulus-default-v008')
             }), {
                 common.VersionKey('backend', 'nomulus-backend-v008'):
                 'nomulus-20200925-RC02',
                 common.VersionKey('default', 'nomulus-default-v008'):
                 'nomulus-20200925-RC02',
             })
Example #6
0
 def test_kill_vm_command(self) -> None:
     cmd = steps.kill_nomulus_instance(
         'my_project', common.VersionKey('my_service', 'my_version'),
         'my_inst')
     self.assertEqual(cmd.instance_name, 'my_inst')
     self.assertIn(('gcloud app instances delete my_inst --quiet '
                    '--user-output-enabled=false --service my_service '
                    '--version my_version --project my_project'),
                   cmd.info())
Example #7
0
 def test_generate_commands_older_vm(self):
     self._setup_generate_steps_tests()
     version = common.VersionKey('my_service', 'my_version')
     # yapf: disable
     commands = rolling_restart.generate_steps(
         self._appengine_admin,
         version,
         common.parse_gcp_timestamp('2019-12-01T00:00:00Z'))
     # yapf: enable
     self.assertSequenceEqual(
         commands, [self._generate_kill_vm_command(version, 'vm_2019')])
Example #8
0
    def get_version_configs(
            self, versions: Set[common.VersionKey]
    ) -> FrozenSet[common.VersionConfig]:
        # yapf: enable
        """Returns the configuration of requested versions.

        For each version in the request, gets the rollback-related data from
        its static configuration (found in appengine-web.xml).

        Args:
            versions: A set of the VersionKey objects, each containing the
                versions being queried in that service.

        Returns:
            The version configurations in an immutable set.
        """
        requested_services = {version.service_id for version in versions}

        version_configs = []
        # Sort the requested services for ease of testing. For now the mocked
        # AppEngine admin in appengine_test can only respond in a fixed order.
        for service_id in sorted(requested_services):
            response = common.list_all_pages(self._versions.list,
                                             'versions',
                                             appsId=self._project,
                                             servicesId=service_id)

            # Format of version_list is defined at
            # https://googleapis.github.io/google-api-python-client/docs/dyn/appengine_v1beta.apps.services.versions.html#list.

            for version in response:
                if common.VersionKey(service_id, version['id']) in versions:
                    scalings = [
                        s for s in list(common.AppEngineScaling)
                        if s.value in version
                    ]
                    if len(scalings) != 1:
                        raise common.CannotRollbackError(
                            f'Expecting exactly one scaling, found {scalings}')

                    scaling = common.AppEngineScaling(list(scalings)[0])
                    if scaling == common.AppEngineScaling.MANUAL:
                        manual_instances = version.get(
                            scaling.value).get('instances')
                    else:
                        manual_instances = None

                    version_configs.append(
                        common.VersionConfig(service_id, version['id'],
                                             scaling, manual_instances))

        return frozenset(version_configs)
Example #9
0
 def test_get_version_configs(self):
     self._set_mocked_response({
         'versions': [{
             'basicScaling': {
                 'maxInstances': 10
             },
             'id': 'version'
         }]
     })
     self.assertEqual(
         self._client.get_version_configs(
             frozenset([common.VersionKey('default', 'version')])),
         frozenset([
             common.VersionConfig('default', 'version',
                                  common.AppEngineScaling.BASIC)
         ]))
Example #10
0
    def _get_release_to_version_mapping(
            self, env: str) -> Dict[common.VersionKey, str]:
        """Returns the content of the release to version mapping file.

        File content is returned in utf-8 encoding. Each line in the file is
        in this format:
        '{RELEASE_TAG},{APP_ENGINE_SERVICE_ID},{APP_ENGINE_VERSION}'.
        """
        file_content = self._client.get_bucket(
            self._get_deploy_bucket_name()).get_blob(
                _get_version_map_name(env)).download_as_text()

        mapping = {}
        for line in file_content.splitlines(False):
            tag, service_id, version_id = line.split(',')
            mapping[common.VersionKey(service_id, version_id)] = tag

        return mapping
Example #11
0
 def test_get_serving_versions(self) -> None:
     self._set_mocked_response({
         'services': [{
             'split': {
                 'allocations': {
                     'my_version': 3.14,
                 }
             },
             'id': 'pubapi'
         }, {
             'split': {
                 'allocations': {
                     'another_version': 2.71,
                 }
             },
             'id': 'error_dashboard'
         }]
     })
     self.assertEqual(
         self._client.get_serving_versions(),
         frozenset([common.VersionKey('pubapi', 'my_version')]))
Example #12
0
    def get_recent_deployments(
            self, env: str, num_records: int) -> Dict[common.VersionKey, str]:
        """Gets the most recent deployment records.

        Deployment records are stored in a file, with one line per service.
        Caller should adjust num_records according to the number of services
        in AppEngine.

        Args:
            env: The environment of the deployed release, e.g., sandbox.
            num_records: the number of lines to go back.
        """
        file_content = self._client.get_bucket(
            self._get_deploy_bucket_name()).get_blob(
                _get_version_map_name(env)).download_as_text()

        mapping = {}
        for line in file_content.splitlines(False)[-num_records:]:
            tag, service_id, version_id = line.split(',')
            mapping[common.VersionKey(service_id, version_id)] = tag

        return mapping
Example #13
0
 def setUp(self) -> None:
     self._appengine_admin, self._appengine_request = (
         appengine_test.setup_appengine_admin())
     self._version = common.VersionKey('my_service', 'my_version')
     self.addCleanup(mock.patch.stopall)