mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
The only method that is called from this class is setNumInstances. However we don't current use `nomulus set_num_instances` anywhere. If we need to change the number of instances, it is either done by updating appengine-web.xml, which is deployed by Spinnaker, or doing it manually as a break-glass fix via gcloud or on Pantheon.
186 lines
6.8 KiB
Python
186 lines
6.8 KiB
Python
# Copyright 2020 The Nomulus Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Definition of rollback steps and factory methods to create them."""
|
|
|
|
import dataclasses
|
|
import subprocess
|
|
import textwrap
|
|
from typing import Tuple
|
|
|
|
import appengine
|
|
import common
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class RollbackStep:
|
|
"""One rollback step.
|
|
|
|
Most steps are implemented using commandline tools, e.g., gcloud and
|
|
gsutil, and execute their commands by forking a subprocess. Each step
|
|
also has a info method that returns its command with a description.
|
|
|
|
Two steps are handled differently. The _UpdateDeployTag step gets a piped
|
|
shell command, which needs to be handled differently. The
|
|
_SetManualScalingNumInstances step uses the AppEngine Admin API client in
|
|
this package to set the number of instances.
|
|
"""
|
|
|
|
description: str
|
|
command: Tuple[str, ...]
|
|
|
|
def info(self) -> str:
|
|
return f'# {self.description}\n' f'{" ".join(self.command)}'
|
|
|
|
def execute(self) -> None:
|
|
"""Executes the step.
|
|
|
|
Raises:
|
|
CannotRollbackError if command fails.
|
|
"""
|
|
if subprocess.call(self.command) != 0:
|
|
raise common.CannotRollbackError(f'Failed: {self.description}')
|
|
|
|
|
|
def check_schema_compatibility(dev_project: str, nom_tag: str,
|
|
sql_tag: str) -> RollbackStep:
|
|
|
|
return RollbackStep(description='Check compatibility with SQL schema.',
|
|
command=(f'{common.get_nomulus_root()}/nom_build',
|
|
':integration:sqlIntegrationTest',
|
|
f'--schema_version={sql_tag}',
|
|
f'--nomulus_version={nom_tag}',
|
|
'--publish_repo='
|
|
f'gcs://{dev_project}-deployed-tags/maven'))
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class _SetManualScalingNumInstances(RollbackStep):
|
|
"""Sets the number of instances for a manual scaling version.
|
|
|
|
The Nomulus set_num_instances command is currently broken. This step uses
|
|
the AppEngine REST API to update the version.
|
|
"""
|
|
|
|
appengine_admin: appengine.AppEngineAdmin
|
|
version: common.VersionKey
|
|
num_instance: int
|
|
|
|
def execute(self) -> None:
|
|
self.appengine_admin.set_manual_scaling_num_instance(
|
|
self.version.service_id, self.version.version_id,
|
|
self.num_instance)
|
|
|
|
|
|
def set_manual_scaling_instances(appengine_admin: appengine.AppEngineAdmin,
|
|
version: common.VersionConfig,
|
|
num_instances: int) -> RollbackStep:
|
|
|
|
cmd_description = textwrap.dedent("""\
|
|
Nomulus set_num_instances command is currently broken.
|
|
This script uses the AppEngine REST API to update the version.
|
|
To set this value without using this tool, you may use the REST API at
|
|
https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1beta/apps.services.versions/patch
|
|
""")
|
|
return _SetManualScalingNumInstances(
|
|
f'Set number of instance for manual-scaling version '
|
|
f'{version.version_id} in {version.service_id} to {num_instances}.',
|
|
(cmd_description, ''), appengine_admin, version, num_instances)
|
|
|
|
|
|
def start_or_stop_version(project: str, action: str,
|
|
version: common.VersionKey) -> RollbackStep:
|
|
"""Creates a rollback step that starts or stops an AppEngine version.
|
|
|
|
Args:
|
|
project: The GCP project of the AppEngine application.
|
|
action: Start or Stop.
|
|
version: The version being managed.
|
|
"""
|
|
return RollbackStep(
|
|
f'{action.title()} {version.version_id} in {version.service_id}',
|
|
('gcloud', 'app', 'versions', action, version.version_id, '--quiet',
|
|
'--service', version.service_id, '--project', project))
|
|
|
|
|
|
def direct_service_traffic_to_version(
|
|
project: str, version: common.VersionKey) -> RollbackStep:
|
|
return RollbackStep(
|
|
f'Direct all traffic to {version.version_id} in {version.service_id}.',
|
|
('gcloud', 'app', 'services', 'set-traffic', version.service_id,
|
|
'--quiet', f'--splits={version.version_id}=1', '--project', project))
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class KillNomulusInstance(RollbackStep):
|
|
"""Step that kills a Nomulus VM instance."""
|
|
instance_name: str
|
|
|
|
|
|
# yapf: disable
|
|
def kill_nomulus_instance(project: str,
|
|
version: common.VersionKey,
|
|
instance_name: str) -> KillNomulusInstance:
|
|
# yapf: enable
|
|
return KillNomulusInstance(
|
|
'Delete one VM instance.',
|
|
('gcloud', 'app', 'instances', 'delete', instance_name, '--quiet',
|
|
'--user-output-enabled=false', '--service', version.service_id,
|
|
'--version', version.version_id, '--project', project), instance_name)
|
|
|
|
|
|
@dataclasses.dataclass(frozen=True)
|
|
class _UpdateDeployTag(RollbackStep):
|
|
"""Updates the deployment tag on GCS."""
|
|
|
|
nom_tag: str
|
|
destination: str
|
|
|
|
def execute(self) -> None:
|
|
with subprocess.Popen(('gsutil', 'cp', '-', self.destination),
|
|
stdin=subprocess.PIPE) as p:
|
|
try:
|
|
p.communicate(self.nom_tag.encode('utf-8'))
|
|
if p.wait() != 0:
|
|
raise common.CannotRollbackError(
|
|
f'Failed: {self.description}')
|
|
except:
|
|
p.kill()
|
|
raise
|
|
|
|
|
|
def update_deploy_tags(dev_project: str, env: str,
|
|
nom_tag: str) -> RollbackStep:
|
|
destination = f'gs://{dev_project}-deployed-tags/nomulus.{env}.tag'
|
|
|
|
return _UpdateDeployTag(
|
|
f'Update Nomulus tag in {env}',
|
|
(f'echo {nom_tag} | gsutil cp - {destination}', ''), nom_tag,
|
|
destination)
|
|
|
|
|
|
def sync_live_release(dev_project: str, nom_tag: str) -> RollbackStep:
|
|
"""Syncs the target release artifacts to the live folder.
|
|
|
|
By convention the gs://{dev_project}-deploy/live folder should contain the
|
|
artifacts from the currently serving release.
|
|
|
|
For Domain Registry team members, this step updates the nomulus tool
|
|
installed on corp desktops.
|
|
"""
|
|
artifacts_folder = f'gs://{dev_project}-deploy/{nom_tag}'
|
|
live_folder = f'gs://{dev_project}-deploy/live'
|
|
|
|
return RollbackStep(
|
|
f'Syncing {artifacts_folder} to {live_folder}.',
|
|
('gsutil', '-m', 'rsync', '-d', artifacts_folder, live_folder))
|