mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
* Script to rolling-start Nomulus Add a script to restart Nomulus non-disruptively. This can be used after a configuration change to external resources (e.g., Cloud SQL credential) to make Nomulus pick up the latest config. Also added proper support to paging based List api methods, replacing the current hack that forces the server to return everything in one response. The List method for instances has a lower limit on page size than others which is not sufficient for our project.
187 lines
6.9 KiB
Python
187 lines
6.9 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. The Nomulus set_num_instances
|
|
command is not working right now.
|
|
"""
|
|
|
|
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))
|