diff --git a/tutor/commands/k8s.py b/tutor/commands/k8s.py index d01b169..3e55200 100644 --- a/tutor/commands/k8s.py +++ b/tutor/commands/k8s.py @@ -4,12 +4,13 @@ from typing import Any, List, Optional, Type import click -from .. import config as tutor_config -from .. import env as tutor_env -from .. import exceptions, fmt, jobs, serialize, utils -from ..types import Config, get_typed -from .config import save as config_save_command -from .context import Context +from tutor import config as tutor_config +from tutor import env as tutor_env +from tutor import exceptions, fmt, jobs, serialize, utils +from tutor.commands.config import save as config_save_command +from tutor.commands.context import Context +from tutor.commands.upgrade.k8s import upgrade_from +from tutor.types import Config, get_typed class K8sClients: @@ -452,116 +453,7 @@ def wait(context: Context, name: str) -> None: @click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively") @click.pass_obj def upgrade(context: Context, from_version: str, non_interactive: bool) -> None: - config = tutor_config.load(context.root) - - running_version = from_version - if running_version == "ironwood": - upgrade_from_ironwood(config) - running_version = "juniper" - - if running_version == "juniper": - upgrade_from_juniper(config) - running_version = "koa" - - if running_version == "koa": - upgrade_from_koa(config) - running_version = "lilac" - - if running_version == "lilac": - upgrade_from_lilac(config) - running_version = "maple" - - # Update env such that the build environment is up-to-date - tutor_env.save(context.root, config) - if not non_interactive: - question = f""" -Your platform was successfuly upgraded from {from_version} to {running_version}. -Depending on your setup, you might have to rebuild some of your Docker images -and push them to your private registry (if any). You can do this now by running -the following command in a different shell: - - tutor images build openedx # add your custom images here - tutor images push openedx - -Press enter when you are ready to continue""" - click.confirm( - fmt.question(question), default=True, abort=True, prompt_suffix=" " - ) - - -def upgrade_from_ironwood(config: Config) -> None: - if not config["RUN_MONGODB"]: - fmt.echo_info( - "You are not running MongDB (RUN_MONGODB=false). It is your " - "responsibility to upgrade your MongoDb instance to v3.6. There is " - "nothing left to do to upgrade from Ironwood." - ) - return - message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Ironwood, you should upgrade -your MongoDb cluster from v3.2 to v3.6. You should run something similar to: - - # Upgrade from v3.2 to v3.4 - tutor k8s stop - tutor config save --set DOCKER_IMAGE_MONGODB=mongo:3.4.24 - tutor k8s start - tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "3.4" })' - - # Upgrade from v3.4 to v3.6 - tutor k8s stop - tutor config save --set DOCKER_IMAGE_MONGODB=mongo:3.6.18 - tutor k8s start - tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "3.6" })' - - tutor config save --unset DOCKER_IMAGE_MONGODB""" - fmt.echo_info(message) - - -def upgrade_from_juniper(config: Config) -> None: - if not config["RUN_MYSQL"]: - fmt.echo_info( - "You are not running MySQL (RUN_MYSQL=false). It is your " - "responsibility to upgrade your MySQL instance to v5.7. There is " - "nothing left to do to upgrade from Juniper." - ) - return - - message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Juniper, you should upgrade -your MySQL database from v5.6 to v5.7. You should run something similar to: - - tutor k8s start - tutor k8s exec mysql bash -e -c "mysql_upgrade \ - -u $(tutor config printvalue MYSQL_ROOT_USERNAME) \ - --password='$(tutor config printvalue MYSQL_ROOT_PASSWORD)' -""" - fmt.echo_info(message) - - -def upgrade_from_koa(config: Config) -> None: - if not config["RUN_MONGODB"]: - fmt.echo_info( - "You are not running MongDB (RUN_MONGODB=false). It is your " - "responsibility to upgrade your MongoDb instance to v4.0. There is " - "nothing left to do to upgrade to Lilac from Koa." - ) - return - message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Koa to Lilac, you should upgrade -your MongoDb cluster from v3.6 to v4.0. You should run something similar to: - - tutor k8s stop - tutor config save --set DOCKER_IMAGE_MONGODB=mongo:4.0.25 - tutor k8s start - tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.0" })' - tutor config save --unset DOCKER_IMAGE_MONGODB - """ - fmt.echo_info(message) - - -def upgrade_from_lilac(config: Config) -> None: - fmt.echo_info( - "All Kubernetes services and deployments need to be deleted during " - "upgrade from Lilac to Maple" - ) - delete_resources(config, resources=["deployments", "services"]) + upgrade_from(context, from_version, interactive=not non_interactive) def kubectl_exec( diff --git a/tutor/commands/local.py b/tutor/commands/local.py index 0b30634..3c78353 100644 --- a/tutor/commands/local.py +++ b/tutor/commands/local.py @@ -1,13 +1,12 @@ -from time import sleep - import click -from .. import config as tutor_config -from .. import env as tutor_env -from .. import exceptions, fmt, utils -from ..types import Config, get_typed -from . import compose -from .config import save as config_save_command +from tutor import config as tutor_config +from tutor import env as tutor_env +from tutor import exceptions, fmt, utils +from tutor.commands import compose +from tutor.commands.config import save as config_save_command +from tutor.commands.upgrade.local import upgrade_from +from tutor.types import Config, get_typed class LocalJobRunner(compose.ComposeJobRunner): @@ -49,10 +48,12 @@ def quickstart(context: click.Context, non_interactive: bool, pullimages: bool) except exceptions.TutorError as e: fmt.echo_alert( f"""Could not verify sufficient RAM allocation in Docker: + {e} -Tutor may not work if Docker is configured with < 4 GB RAM. Please follow the instructions from: - https://docs.tutor.overhang.io/install.html - """ + +Tutor may not work if Docker is configured with < 4 GB RAM. Please follow instructions from: + + https://docs.tutor.overhang.io/install.html""" ) if tutor_env.needs_major_upgrade(context.obj.root): @@ -64,12 +65,7 @@ Tutor may not work if Docker is configured with < 4 GB RAM. Please follow the in ) click.echo(fmt.title("Interactive platform configuration")) - context.invoke( - config_save_command, - interactive=(not non_interactive), - set_vars=[], - unset_vars=[], - ) + context.invoke(config_save_command, interactive=(not non_interactive)) click.echo(fmt.title("Stopping any existing platform")) context.invoke(compose.stop) if pullimages: @@ -105,143 +101,7 @@ Your Open edX platform is ready and can be accessed at the following urls: @click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively") @click.pass_context def upgrade(context: click.Context, from_version: str, non_interactive: bool) -> None: - config = tutor_config.load(context.obj.root) - - if not non_interactive: - question = """You are about to upgrade your Open edX platform. It is strongly recommended to make a backup before upgrading. To do so, run: - - tutor local stop - sudo rsync -avr "$(tutor config printroot)"/ /tmp/tutor-backup/ - -In case of problem, to restore your backup you will then have to run: sudo rsync -avr /tmp/tutor-backup/ "$(tutor config printroot)"/ - -Are you sure you want to continue?""" - click.confirm( - fmt.question(question), default=True, abort=True, prompt_suffix=" " - ) - - running_version = from_version - if running_version == "ironwood": - upgrade_from_ironwood(context, config) - running_version = "juniper" - - if running_version == "juniper": - upgrade_from_juniper(context, config) - running_version = "koa" - - if running_version == "koa": - upgrade_from_koa(context, config) - running_version = "lilac" - - if running_version == "lilac": - # Nothing to do here - running_version = "maple" - - # Update env such that the build environment is up-to-date - tutor_env.save(context.obj.root, config) - if not non_interactive: - question = f""" -Your platform was successfuly upgraded from {from_version} to {running_version}. -Depending on your setup, you might have to rebuild some of your Docker images. -You can do this now by running the following command in a different shell: - - tutor images build openedx # add your custom images here - -Press enter when you are ready to continue""" - click.confirm( - fmt.question(question), default=True, abort=True, prompt_suffix=" " - ) - - -def upgrade_from_ironwood(context: click.Context, config: Config) -> None: - click.echo(fmt.title("Upgrading from Ironwood")) - tutor_env.save(context.obj.root, config) - - click.echo(fmt.title("Stopping any existing platform")) - context.invoke(compose.stop) - - if not config["RUN_MONGODB"]: - fmt.echo_info( - "You are not running MongDB (RUN_MONGODB=false). It is your " - "responsibility to upgrade your MongoDb instance to v3.6. There is " - "nothing left to do to upgrade from Ironwood to Juniper." - ) - return - - upgrade_mongodb(context, config, "3.4", "3.4") - context.invoke(compose.stop) - upgrade_mongodb(context, config, "3.6", "3.6") - context.invoke(compose.stop) - - -def upgrade_from_juniper(context: click.Context, config: Config) -> None: - click.echo(fmt.title("Upgrading from Juniper")) - tutor_env.save(context.obj.root, config) - - click.echo(fmt.title("Stopping any existing platform")) - context.invoke(compose.stop) - - if not config["RUN_MYSQL"]: - fmt.echo_info( - "You are not running MySQL (RUN_MYSQL=false). It is your " - "responsibility to upgrade your MySQL instance to v5.7. There is " - "nothing left to do to upgrade from Juniper." - ) - return - - click.echo(fmt.title("Upgrading MySQL from v5.6 to v5.7")) - context.invoke(compose.start, detach=True, services=["mysql"]) - context.invoke( - compose.execute, - args=[ - "mysql", - "bash", - "-e", - "-c", - "mysql_upgrade -u {} --password='{}'".format( - config["MYSQL_ROOT_USERNAME"], config["MYSQL_ROOT_PASSWORD"] - ), - ], - ) - context.invoke(compose.stop) - - -def upgrade_from_koa(context: click.Context, config: Config) -> None: - if not config["RUN_MONGODB"]: - fmt.echo_info( - "You are not running MongDB (RUN_MONGODB=false). It is your " - "responsibility to upgrade your MongoDb instance to v4.0. There is " - "nothing left to do to upgrade from Koa to Lilac." - ) - return - upgrade_mongodb(context, config, "4.0.25", "4.0") - - -def upgrade_mongodb( - context: click.Context, - config: Config, - to_docker_version: str, - to_compatibility_version: str, -) -> None: - click.echo(fmt.title("Upgrading MongoDb to v{}".format(to_docker_version))) - # Note that the DOCKER_IMAGE_MONGODB value is never saved, because we only save the - # environment, not the configuration. - config["DOCKER_IMAGE_MONGODB"] = "mongo:{}".format(to_docker_version) - tutor_env.save(context.obj.root, config) - context.invoke(compose.start, detach=True, services=["mongodb"]) - fmt.echo_info("Waiting for mongodb to boot...") - sleep(10) - context.invoke( - compose.execute, - args=[ - "mongodb", - "mongo", - "--eval", - 'db.adminCommand({ setFeatureCompatibilityVersion: "%s" })' - % to_compatibility_version, - ], - ) - context.invoke(compose.stop) + upgrade_from(context, from_version, not non_interactive) local.add_command(quickstart) diff --git a/tutor/commands/upgrade/__init__.py b/tutor/commands/upgrade/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutor/commands/upgrade/k8s.py b/tutor/commands/upgrade/k8s.py new file mode 100644 index 0000000..8edd6cf --- /dev/null +++ b/tutor/commands/upgrade/k8s.py @@ -0,0 +1,123 @@ +import click + +from tutor import config as tutor_config +from tutor import env as tutor_env +from tutor import fmt +from tutor.commands import k8s +from tutor.commands.context import Context +from tutor.types import Config + + +def upgrade_from( + context: Context, from_version: str, interactive: bool = False +) -> None: + config = tutor_config.load(context.root) + + running_version = from_version + if running_version == "ironwood": + upgrade_from_ironwood(config) + running_version = "juniper" + + if running_version == "juniper": + upgrade_from_juniper(config) + running_version = "koa" + + if running_version == "koa": + upgrade_from_koa(config) + running_version = "lilac" + + if running_version == "lilac": + upgrade_from_lilac(config) + running_version = "maple" + + # Update env such that the build environment is up-to-date + tutor_env.save(context.root, config) + if interactive: + question = f"""Your platform was successfuly upgraded from {from_version} to {running_version}. + +Depending on your setup, you might have to rebuild some of your Docker images +and push them to your private registry (if any). You can do this now by running +the following command in a different shell: + + tutor images build openedx # add your custom images here + tutor images push openedx + +Press enter when you are ready to continue""" + click.confirm( + fmt.question(question), default=True, abort=True, prompt_suffix=" " + ) + + +def upgrade_from_ironwood(config: Config) -> None: + if not config["RUN_MONGODB"]: + fmt.echo_info( + "You are not running MongDB (RUN_MONGODB=false). It is your " + "responsibility to upgrade your MongoDb instance to v3.6. There is " + "nothing left to do to upgrade from Ironwood." + ) + return + message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Ironwood, you should upgrade +your MongoDb cluster from v3.2 to v3.6. You should run something similar to: + + # Upgrade from v3.2 to v3.4 + tutor k8s stop + tutor config save --set DOCKER_IMAGE_MONGODB=mongo:3.4.24 + tutor k8s start + tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "3.4" })' + + # Upgrade from v3.4 to v3.6 + tutor k8s stop + tutor config save --set DOCKER_IMAGE_MONGODB=mongo:3.6.18 + tutor k8s start + tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "3.6" })' + + tutor config save --unset DOCKER_IMAGE_MONGODB""" + fmt.echo_info(message) + + +def upgrade_from_juniper(config: Config) -> None: + if not config["RUN_MYSQL"]: + fmt.echo_info( + "You are not running MySQL (RUN_MYSQL=false). It is your " + "responsibility to upgrade your MySQL instance to v5.7. There is " + "nothing left to do to upgrade from Juniper." + ) + return + + message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Juniper, you should upgrade +your MySQL database from v5.6 to v5.7. You should run something similar to: + + tutor k8s start + tutor k8s exec mysql bash -e -c "mysql_upgrade \ + -u $(tutor config printvalue MYSQL_ROOT_USERNAME) \ + --password='$(tutor config printvalue MYSQL_ROOT_PASSWORD)' +""" + fmt.echo_info(message) + + +def upgrade_from_koa(config: Config) -> None: + if not config["RUN_MONGODB"]: + fmt.echo_info( + "You are not running MongDB (RUN_MONGODB=false). It is your " + "responsibility to upgrade your MongoDb instance to v4.0. There is " + "nothing left to do to upgrade to Lilac from Koa." + ) + return + message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Koa to Lilac, you should upgrade +your MongoDb cluster from v3.6 to v4.0. You should run something similar to: + + tutor k8s stop + tutor config save --set DOCKER_IMAGE_MONGODB=mongo:4.0.25 + tutor k8s start + tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.0" })' + tutor config save --unset DOCKER_IMAGE_MONGODB + """ + fmt.echo_info(message) + + +def upgrade_from_lilac(config: Config) -> None: + fmt.echo_info( + "All Kubernetes services and deployments need to be deleted during " + "upgrade from Lilac to Maple" + ) + k8s.delete_resources(config, resources=["deployments", "services"]) diff --git a/tutor/commands/upgrade/local.py b/tutor/commands/upgrade/local.py new file mode 100644 index 0000000..aa21e5d --- /dev/null +++ b/tutor/commands/upgrade/local.py @@ -0,0 +1,150 @@ +from time import sleep + +import click + +from tutor import config as tutor_config +from tutor import env as tutor_env +from tutor import fmt +from tutor.commands import compose +from tutor.types import Config + + +def upgrade_from( + context: click.Context, from_version: str, interactive: bool = False +) -> None: + config = tutor_config.load(context.obj.root) + + if interactive: + question = """You are about to upgrade your Open edX platform. + +It is strongly recommended to make a backup before upgrading. To do so, run: + + tutor local stop + sudo rsync -avr "$(tutor config printroot)"/ /tmp/tutor-backup/ + +In case of problem, to restore your backup you will then have to run: sudo rsync -avr /tmp/tutor-backup/ "$(tutor config printroot)"/ + +Are you sure you want to continue?""" + click.confirm( + fmt.question(question), default=True, abort=True, prompt_suffix=" " + ) + + running_version = from_version + if running_version == "ironwood": + upgrade_from_ironwood(context, config) + running_version = "juniper" + + if running_version == "juniper": + upgrade_from_juniper(context, config) + running_version = "koa" + + if running_version == "koa": + upgrade_from_koa(context, config) + running_version = "lilac" + + if running_version == "lilac": + # Nothing to do here + running_version = "maple" + + # Update env such that the build environment is up-to-date + tutor_env.save(context.obj.root, config) + if interactive: + question = f"""Your platform was successfuly upgraded from {from_version} to {running_version}. + +Depending on your setup, you might have to rebuild some of your Docker images. +You can do this now by running the following command in a different shell: + + tutor images build openedx # add your custom images here + +Press enter when you are ready to continue""" + click.confirm( + fmt.question(question), default=True, abort=True, prompt_suffix=" " + ) + + +def upgrade_from_ironwood(context: click.Context, config: Config) -> None: + click.echo(fmt.title("Upgrading from Ironwood")) + tutor_env.save(context.obj.root, config) + + click.echo(fmt.title("Stopping any existing platform")) + context.invoke(compose.stop) + + if not config["RUN_MONGODB"]: + fmt.echo_info( + "You are not running MongDB (RUN_MONGODB=false). It is your " + "responsibility to upgrade your MongoDb instance to v3.6. There is " + "nothing left to do to upgrade from Ironwood to Juniper." + ) + return + + upgrade_mongodb(context, config, "3.4", "3.4") + context.invoke(compose.stop) + upgrade_mongodb(context, config, "3.6", "3.6") + context.invoke(compose.stop) + + +def upgrade_from_juniper(context: click.Context, config: Config) -> None: + click.echo(fmt.title("Upgrading from Juniper")) + tutor_env.save(context.obj.root, config) + + click.echo(fmt.title("Stopping any existing platform")) + context.invoke(compose.stop) + + if not config["RUN_MYSQL"]: + fmt.echo_info( + "You are not running MySQL (RUN_MYSQL=false). It is your " + "responsibility to upgrade your MySQL instance to v5.7. There is " + "nothing left to do to upgrade from Juniper." + ) + return + + click.echo(fmt.title("Upgrading MySQL from v5.6 to v5.7")) + context.invoke(compose.start, detach=True, services=["mysql"]) + context.invoke( + compose.execute, + args=[ + "mysql", + "bash", + "-e", + "-c", + f"mysql_upgrade -u {config['MYSQL_ROOT_USERNAME']} --password='{config['MYSQL_ROOT_PASSWORD']}'", + ], + ) + context.invoke(compose.stop) + + +def upgrade_from_koa(context: click.Context, config: Config) -> None: + if not config["RUN_MONGODB"]: + fmt.echo_info( + "You are not running MongDB (RUN_MONGODB=false). It is your " + "responsibility to upgrade your MongoDb instance to v4.0. There is " + "nothing left to do to upgrade from Koa to Lilac." + ) + return + upgrade_mongodb(context, config, "4.0.25", "4.0") + + +def upgrade_mongodb( + context: click.Context, + config: Config, + to_docker_version: str, + to_compatibility_version: str, +) -> None: + click.echo(fmt.title(f"Upgrading MongoDb to v{to_docker_version}")) + # Note that the DOCKER_IMAGE_MONGODB value is never saved, because we only save the + # environment, not the configuration. + config["DOCKER_IMAGE_MONGODB"] = f"mongo:{to_docker_version}" + tutor_env.save(context.obj.root, config) + context.invoke(compose.start, detach=True, services=["mongodb"]) + fmt.echo_info("Waiting for mongodb to boot...") + sleep(10) + context.invoke( + compose.execute, + args=[ + "mongodb", + "mongo", + "--eval", + f'db.adminCommand({{ setFeatureCompatibilityVersion: "{to_compatibility_version}" }})', + ], + ) + context.invoke(compose.stop)