diff --git a/containers/base/Dockerfile b/containers/base/Dockerfile index d3fce14a0..291e3db62 100644 --- a/containers/base/Dockerfile +++ b/containers/base/Dockerfile @@ -12,7 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM debian:jessie +# We use different base images for GPU vs CPU Dockerfiles, so we expect +# that the appropriate image is pulled and tagged locally. +# CPU should use ubuntu:16.04 +# and GPU uses nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 +FROM datalab-external-base-image MAINTAINER Google Cloud DataLab # Container configuration @@ -34,7 +38,10 @@ RUN echo "deb-src http://ftp.us.debian.org/debian testing main" >> /etc/apt/sour # Save GPL source packages mkdir -p /srcs && \ cd /srcs && \ - apt-get source -d wget git python-zmq ca-certificates pkg-config libpng-dev && \ + # We have to use allow-unauthenticated since Ubuntu can't verify the keys for the source + # repos. We don't care since we only download these sources for licensing reasons and + # don't actually use them. + apt-get source --allow-unauthenticated -d wget git python-zmq ca-certificates pkg-config libpng-dev && \ wget --progress=dot:mega https://mirrors.kernel.org/gnu/gcc/gcc-4.9.2/gcc-4.9.2.tar.bz2 && \ cd / && \ @@ -168,11 +175,9 @@ RUN echo "deb-src http://ftp.us.debian.org/debian testing main" >> /etc/apt/sour tools/google-cloud-sdk/bin/gcloud config set component_manager/disable_update_check true && \ touch /tools/google-cloud-sdk/lib/third_party/google.py && \ -# locale - DEBIAN_FRONTEND=noninteractive apt-get install -y locales && \ - sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ - echo 'LANG="en_US.UTF-8"'>/etc/default/locale && \ - dpkg-reconfigure --frontend=noninteractive locales && \ +# Set our locale to en_US.UTF-8. + apt-get install -y locales && \ + locale-gen en_US.UTF-8 && \ update-locale LANG=en_US.UTF-8 && \ # Add some unchanging bits - specifically node modules (that need to be kept in sync @@ -191,7 +196,7 @@ RUN echo "deb-src http://ftp.us.debian.org/debian testing main" >> /etc/apt/sour /tools/node/bin/npm install -g forever && \ # Clean up - apt-get purge -y build-essential bzip2 cpp cpp-4.9 python-setuptools pkg-config libfreetype6-dev && \ + apt-get purge -y build-essential bzip2 cpp pkg-config libfreetype6-dev && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/dpkg/info/* && \ diff --git a/containers/base/Dockerfile.gpu b/containers/base/Dockerfile.gpu new file mode 100644 index 000000000..5fb55d02f --- /dev/null +++ b/containers/base/Dockerfile.gpu @@ -0,0 +1,22 @@ +# Copyright 2017 Google Inc. 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. + +FROM datalab-core-gpu +MAINTAINER Google Cloud DataLab + +# Download and Install GPU specific packages +RUN pip install -U --upgrade-strategy only-if-needed --no-cache-dir tensorflow-gpu==1.0.1 && \ + pip3 install -U --upgrade-strategy only-if-needed --no-cache-dir tensorflow-gpu==1.0.1 + + diff --git a/containers/base/build.gpu.sh b/containers/base/build.gpu.sh new file mode 100755 index 000000000..21e597278 --- /dev/null +++ b/containers/base/build.gpu.sh @@ -0,0 +1,42 @@ +#!/bin/bash -e +# Copyright 2015 Google Inc. 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. + +# Builds the Google Cloud DataLab base docker image. Usage: +# build.sh [path_of_pydatalab_dir] +# If [path_of_pydatalab_dir] is provided, it will copy the content of that dir into image. +# Otherwise, it will get the pydatalab by "git clone" from pydatalab repo. + +# Build the docker image +if [ -n "$1" ]; then + src_pydatalab=$(realpath "$1") + + cd $(dirname $0) + rsync -avp "$src_pydatalab"/ pydatalab +else + # Create empty dir to make docker build happy. + cd $(dirname $0) + mkdir -p pydatalab +fi + +trap 'rm -rf pydatalab' exit + +docker pull nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 +# Docker tag flags changed in an incompatible way between versions. +# The Datalab Jenkins build still uses the old one, so try it both ways. +if ! $(docker tag -f nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 datalab-external-base-image); then + docker tag nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04 datalab-external-base-image +fi +docker build ${DOCKER_BUILD_ARGS} -t datalab-core-gpu . +docker build ${DOCKER_BUILD_ARGS} -f Dockerfile.gpu -t datalab-base-gpu . diff --git a/containers/base/build.sh b/containers/base/build.sh index 5305b7fb2..fb2270ca5 100755 --- a/containers/base/build.sh +++ b/containers/base/build.sh @@ -31,4 +31,11 @@ else fi trap 'rm -rf pydatalab' exit + +docker pull ubuntu:16.04 +# Docker tag flags changed in an incompatible way between versions. +# The Datalab Jenkins build still uses the old one, so try it both ways. +if ! $(docker tag -f ubuntu:16.04 datalab-external-base-image); then + docker tag ubuntu:16.04 datalab-external-base-image +fi docker build ${DOCKER_BUILD_ARGS} -t datalab-base . diff --git a/containers/datalab/Dockerfile.in b/containers/datalab/Dockerfile.in index 1b8f39dc1..26b3b534d 100644 --- a/containers/datalab/Dockerfile.in +++ b/containers/datalab/Dockerfile.in @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -FROM datalab-base +FROM _base_image_ MAINTAINER Google Cloud DataLab # Add build artifacts diff --git a/containers/datalab/build.gpu.sh b/containers/datalab/build.gpu.sh new file mode 100755 index 000000000..170a72e19 --- /dev/null +++ b/containers/datalab/build.gpu.sh @@ -0,0 +1,61 @@ +#!/bin/bash -e +# Copyright 2015 Google Inc. 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. + +# Builds the Google Cloud DataLab docker image. Usage: +# build.sh [path_of_pydatalab_dir] +# If [path_of_pydatalab_dir] is provided, it will copy the content of that dir into image. +# Otherwise, it will get the pydatalab by "git clone" from pydatalab repo. + +# Create a versioned Dockerfile based on current date and git commit hash +VERSION=`date +%Y%m%d` +VERSION_SUBSTITUTION="s/_version_/0.5.$VERSION/" + +COMMIT=`git log --pretty=format:'%H' -n 1` +COMMIT_SUBSTITUTION="s/_commit_/$COMMIT/" + +BASE_IMAGE_SUBSTITUTION="s/_base_image_/datalab-base-gpu/" + +if [ -z "$1" ]; then + pydatalabPath='' +else + pydatalabPath=$(realpath "$1") +fi + +cd $(dirname $0) + +cat Dockerfile.in | sed $VERSION_SUBSTITUTION | sed $COMMIT_SUBSTITUTION | sed $BASE_IMAGE_SUBSTITUTION > Dockerfile + +# Set up our required environment +source ../../tools/initenv.sh + +# Build the datalab frontend +../../sources/web/build.sh + +# Copy build outputs as a dependency of the Dockerfile +rsync -avp ../../build/ build + +# Copy the license file into the container +cp ../../third_party/license.txt content/license.txt + +# Build the base docker image +../base/build.gpu.sh "$pydatalabPath" + +# Build the docker image +docker build ${DOCKER_BUILD_ARGS} -t datalab-gpu . + +# Finally cleanup +rm -rf build +rm content/license.txt +rm Dockerfile diff --git a/containers/datalab/build.sh b/containers/datalab/build.sh index 68b2469da..92816bfb0 100755 --- a/containers/datalab/build.sh +++ b/containers/datalab/build.sh @@ -26,6 +26,8 @@ source $HERE/../../tools/release/version.sh VERSION_SUBSTITUTION="s/_version_/$DATALAB_VERSION/" COMMIT_SUBSTITUTION="s/_commit_/$DATALAB_COMMIT/" +BASE_IMAGE_SUBSTITUTION="s/_base_image_/datalab-base/" + if [ -z "$1" ]; then pydatalabPath='' else @@ -34,7 +36,7 @@ fi cd $(dirname $0) -cat Dockerfile.in | sed $VERSION_SUBSTITUTION | sed $COMMIT_SUBSTITUTION > Dockerfile +cat Dockerfile.in | sed $VERSION_SUBSTITUTION | sed $COMMIT_SUBSTITUTION | sed $BASE_IMAGE_SUBSTITUTION > Dockerfile # Set up our required environment source ../../tools/initenv.sh diff --git a/tools/cli/commands/create.py b/tools/cli/commands/create.py index 2d4744dc3..647e5027b 100644 --- a/tools/cli/commands/create.py +++ b/tools/cli/commands/create.py @@ -44,7 +44,7 @@ _DATALAB_NOTEBOOKS_REPOSITORY = 'datalab-notebooks' -_DATALAB_STARTUP_SCRIPT = """#!/bin/bash +_DATALAB_BASE_STARTUP_SCRIPT = """#!/bin/bash PERSISTENT_DISK_DEV="/dev/disk/by-id/google-datalab-pd" MOUNT_DIR="/mnt/disks/datalab-pd" @@ -148,6 +148,9 @@ find "${{tmpdir}}/" -mindepth 1 -delete }} +""" + +_DATALAB_STARTUP_SCRIPT = _DATALAB_BASE_STARTUP_SCRIPT + """ mount_and_prepare_disk configure_swap cleanup_tmp @@ -524,38 +527,56 @@ def ensure_repo_exists(args, gcloud_repos, repo_name): raise RepositoryException(repo_name) -def run(args, gcloud_compute, gcloud_repos, - email='', in_cloud_shell=False, **kwargs): - """Implementation of the `datalab create` subcommand. +def prepare(args, gcloud_compute, gcloud_repos): + """Run preparation steps for VM creation. Args: args: The Namespace instance returned by argparse gcloud_compute: Function that can be used to invoke `gcloud compute` gcloud_repos: Function that can be used to invoke `gcloud source repos` - email: The user's email address - in_cloud_shell: Whether or not the command is being run in the - Google Cloud Shell + Returns: + The disk config Raises: subprocess.CalledProcessError: If a nested `gcloud` calls fails """ ensure_network_exists(args, gcloud_compute) ensure_firewall_rule_exists(args, gcloud_compute) - instance = args.instance - disk_name = args.disk_name or '{0}-pd'.format(instance) + disk_name = args.disk_name or '{0}-pd'.format(args.instance) ensure_disk_exists(args, gcloud_compute, disk_name) + disk_cfg = ( + 'auto-delete=no,boot=no,device-name=datalab-pd,mode=rw,name=' + + disk_name) if not args.no_create_repository: ensure_repo_exists(args, gcloud_repos, _DATALAB_NOTEBOOKS_REPOSITORY) - print('Creating the instance {0}'.format(instance)) + return disk_cfg + + +def run(args, gcloud_compute, gcloud_repos, + email='', in_cloud_shell=False, **kwargs): + """Implementation of the `datalab create` subcommand. + + Args: + args: The Namespace instance returned by argparse + gcloud_compute: Function that can be used to invoke `gcloud compute` + gcloud_repos: Function that can be used to invoke + `gcloud source repos` + email: The user's email address + in_cloud_shell: Whether or not the command is being run in the + Google Cloud Shell + Raises: + subprocess.CalledProcessError: If a nested `gcloud` calls fails + """ + disk_cfg = prepare(args, gcloud_compute, gcloud_repos) + + print('Creating the instance {0}'.format(args.instance)) cmd = ['instances', 'create'] if args.zone: cmd.extend(['--zone', args.zone]) - disk_cfg = ( - 'auto-delete=no,boot=no,device-name=datalab-pd,mode=rw,name=' + - disk_name) + enable_backups = "false" if args.no_backups else "true" console_log_level = args.log_level or "warn" user_email = args.for_user or email @@ -602,7 +623,7 @@ def run(args, gcloud_compute, gcloud_repos, '--disk', disk_cfg, '--service-account', service_account, '--scopes', 'cloud-platform', - instance]) + args.instance]) gcloud_compute(args, cmd) finally: os.remove(startup_script_file.name) diff --git a/tools/cli/commands/creategpu.py b/tools/cli/commands/creategpu.py new file mode 100644 index 000000000..983e15af9 --- /dev/null +++ b/tools/cli/commands/creategpu.py @@ -0,0 +1,209 @@ +# Copyright 2016 Google Inc. 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. + +"""Methods for implementing the `datalab beta creategpu` command.""" + +import os +from builtins import input +import tempfile + +import create +import connect + + +description = ("""`{0} {1}` creates a new Datalab instance running in a Google +Compute Engine VM with a GPU. + +This command also creates the 'datalab-network' network if necessary. + +By default, the command creates a persistent connection to the newly +created instance. You can disable that behavior by passing in the +'--no-connect' flag.""") + +_NVIDIA_PACKAGE = 'cuda-repo-ubuntu1604_8.0.61-1_amd64.deb' + +_DATALAB_STARTUP_SCRIPT = create._DATALAB_BASE_STARTUP_SCRIPT + """ +install_cuda() {{ + # Check for CUDA and try to install. + if ! dpkg-query -W cuda; then + curl -O http://developer.download.nvidia.com/\ +compute/cuda/repos/ubuntu1604/x86_64/{5} + dpkg -i ./{5} + apt-get update + apt-get install cuda -y + fi +}} + +install_nvidia_docker() {{ + # Install normal docker then install nvidia-docker + if ! dpkg-query -W docker; then + curl -sSL https://get.docker.com/ | sh + curl -L -O https://github.com/NVIDIA/nvidia-docker/releases/\ +download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb + dpkg -i ./nvidia-docker_1.0.1-1_amd64.deb + apt-get update + apt-get install nvidia-docker -y + fi +}} + +pull_datalab_image() {{ + if [[ "$(docker images -q {0})" == "" ]]; then + gcloud docker -- pull {0} ; + fi +}} + +start_datalab_docker() {{ + nvidia-docker run --restart always -p '127.0.0.1:8080:8080' \ + -e DATALAB_ENV='GCE' -e DATALAB_DEBUG='true' \ + -e DATALAB_SETTINGS_OVERRIDES=\ +'{{"enableAutoGCSBackups": {2}, "consoleLogLevel": "{3}" }}' \ + -e DATALAB_GIT_AUTHOR='{4}' \ + -v "${{MOUNT_DIR}}/content:/content" \ + -v "${{MOUNT_DIR}}/tmp:/tmp" \ + {0} -c /datalab/run.sh +}} + +start_fluentd_docker() {{ + docker run --restart always \ + -e FLUENTD_ARGS='-q' \ + -v /var/log:/var/log \ + -v /var/lib/docker/containers:/var/lib/docker/containers:ro \ + gcr.io/google_containers/fluentd-gcp:1.18 +}} + +install_cuda +install_nvidia_docker +pull_datalab_image +mount_and_prepare_disk +configure_swap +cleanup_tmp +start_datalab_docker +start_fluentd_docker + +journalctl -u google-startup-scripts --no-pager > /var/log/startupscript.log +""" + + +def flags(parser): + """Add command line flags for the `create` subcommand. + + Args: + parser: The argparse parser to which to add the flags. + """ + create.flags(parser) + parser.set_defaults(image_name='gcr.io/cloud-datalab/datalab-gpu:latest') + + parser.add_argument( + '--accelerator-type', + dest='accelerator_type', + default='nvidia-tesla-k80', + help=( + 'the accelerator type of the instance.' + '\n\n' + 'Datalab currently only supports nvidia-tesla-k80.' + '\n\n' + 'If not specified, the default type is none.')) + + parser.add_argument( + '--accelerator-count', + dest='accelerator_count', + type=int, + default=1, + help=( + 'the accelerator count of the instance, used if ' + 'accelerator-type is specified.' + '\n\n' + 'If not specified, the default type is 1.')) + return + + +def run(args, gcloud_beta_compute, gcloud_repos, + email='', in_cloud_shell=False, **kwargs): + """Implementation of the `datalab create` subcommand. + + Args: + args: The Namespace instance returned by argparse + gcloud_beta_compute: Function that can be used to invoke `gcloud compute` + gcloud_repos: Function that can be used to invoke + `gcloud source repos` + email: The user's email address + in_cloud_shell: Whether or not the command is being run in the + Google Cloud Shell + Raises: + subprocess.CalledProcessError: If a nested `gcloud` calls fails + """ + print('By accepting below, you will download and install the ' + 'following third-party software onto your managed GCE instances: ' + 'NVidia GPU CUDA Toolkit Drivers: ' + _NVIDIA_PACKAGE) + resp = input('Do you accept? (y/[n]): ') + if len(resp) < 1 or (resp[0] != 'y' and resp[0] != 'Y'): + print('Installation not accepted; Exiting.') + return + + disk_cfg = create.prepare(args, gcloud_beta_compute, gcloud_repos) + print('Creating the instance {0}'.format(args.instance)) + print('\n\nDue to GPU Driver installation, please note that ' + 'Datalab GPU instances take significantly longer to ' + 'startup compared to non-GPU instances.') + cmd = ['instances', 'create'] + if args.zone: + cmd.extend(['--zone', args.zone]) + enable_backups = "false" if args.no_backups else "true" + console_log_level = args.log_level or "warn" + user_email = args.for_user or email + service_account = args.service_account or "default" + # We have to escape the user's email before using it in the YAML template. + escaped_email = user_email.replace("'", "''") + with tempfile.NamedTemporaryFile(delete=False) as startup_script_file, \ + tempfile.NamedTemporaryFile(delete=False) as for_user_file: + try: + startup_script_file.write(_DATALAB_STARTUP_SCRIPT.format( + args.image_name, create._DATALAB_NOTEBOOKS_REPOSITORY, + enable_backups, console_log_level, escaped_email, + _NVIDIA_PACKAGE)) + startup_script_file.close() + for_user_file.write(user_email) + for_user_file.close() + metadata_template = ( + 'startup-script={0},' + + 'for-user={1}') + metadata_from_file = ( + metadata_template.format( + startup_script_file.name, + for_user_file.name)) + cmd.extend([ + '--format=none', + '--boot-disk-size=20GB', + '--network', create._DATALAB_NETWORK, + '--image-family', 'ubuntu-1604-lts', + '--image-project', 'ubuntu-os-cloud', + '--machine-type', args.machine_type, + '--accelerator', + 'type=' + args.accelerator_type + ',count=' + + str(args.accelerator_count), + '--maintenance-policy', 'TERMINATE', '--restart-on-failure', + '--metadata-from-file', metadata_from_file, + '--tags', 'datalab', + '--disk', disk_cfg, + '--service-account', service_account, + '--scopes', 'cloud-platform', + args.instance]) + gcloud_beta_compute(args, cmd) + finally: + os.remove(startup_script_file.name) + os.remove(for_user_file.name) + + if (not args.no_connect) and (not args.for_user): + connect.connect(args, gcloud_beta_compute, email, in_cloud_shell) + return diff --git a/tools/cli/datalab.py b/tools/cli/datalab.py index d1c677bde..6ffeca540 100755 --- a/tools/cli/datalab.py +++ b/tools/cli/datalab.py @@ -19,7 +19,7 @@ This tool is specific to the use case of running in the Google Cloud Platform. """ -from commands import create, connect, list, stop, delete, utils +from commands import create, creategpu, connect, list, stop, delete, utils import argparse import os @@ -66,6 +66,16 @@ }, } +_BETA_SUBCOMMANDS = { + 'create-gpu': { + 'help': 'Create and connect to a new Datalab GPU instance', + 'description': creategpu.description, + 'flags': creategpu.flags, + 'run': creategpu.run, + 'require-zone': True, + } +} + _PROJECT_HELP = ("""The Google Cloud Platform project name to use for this invocation. @@ -128,6 +138,31 @@ def gcloud_compute( cmd, stdin=stdin, stdout=stdout, stderr=stderr) +def gcloud_beta_compute( + args, compute_cmd, stdin=None, stdout=None, stderr=None): + """Run the given subcommand of `gcloud beta compute` + + Args: + args: The Namespace instance returned by argparse + compute_cmd: The subcommand of `gcloud compute` to run + stdin: The 'stdin' argument for the subprocess call + stdout: The 'stdout' argument for the subprocess call + stderr: The 'stderr' argument for the subprocess call + Raises: + KeyboardInterrupt: If the user kills the command + subprocess.CalledProcessError: If the command dies on its own + """ + base_cmd = [gcloud_cmd, 'beta', 'compute'] + if args.project: + base_cmd.extend(['--project', args.project]) + if args.quiet: + base_cmd.append('--quiet') + base_cmd.append('--verbosity={}'.format(args.verbosity)) + cmd = base_cmd + compute_cmd + return subprocess.check_call( + cmd, stdin=stdin, stdout=stdout, stderr=stderr) + + def gcloud_repos( args, repos_cmd, stdin=None, stdout=None, stderr=None): """Run the given subcommand of `gcloud source repos` @@ -165,6 +200,51 @@ def get_email_address(): 'value(account)', '--filter', 'status:ACTIVE']).strip() +def add_sub_parser(subcommand, command_config, subparsers, prog): + """Adds a subparser. + + Args: + subcommand: The subcommand to add. + command_config: The subcommand's config. + subparsers: The list of subparsers to add to. + prog: The program name. + """ + description_template = command_config.get('description') + command_description = ( + description_template.format(prog, subcommand)) + examples = command_config.get('examples', '').format(prog, subcommand) + epilog = 'examples:{0}'.format(examples) if examples else '' + subcommand_parser = subparsers.add_parser( + subcommand, + formatter_class=argparse.RawTextHelpFormatter, + description=command_description, + epilog=epilog, + help=command_config['help']) + command_config['flags'](subcommand_parser) + subcommand_parser.add_argument( + '--project', + dest='project', + default=None, + help=_PROJECT_HELP) + subcommand_parser.add_argument( + '--quiet', + dest='quiet', + action='store_true', + help='do not issue any interactive prompts') + subcommand_parser.add_argument( + '--verbosity', + dest='verbosity', + choices=['debug', 'info', 'warning', 'error', 'critical', 'none'], + default='error', + help='Override the default output verbosity for this command.') + if command_config['require-zone']: + subcommand_parser.add_argument( + '--zone', + dest='zone', + default=None, + help=_ZONE_HELP) + + def run(): """Run the command line tool.""" prog = 'datalab' @@ -194,49 +274,31 @@ def run(): subparsers = parser.add_subparsers(dest='subcommand') for subcommand in _SUBCOMMANDS: - command_config = _SUBCOMMANDS[subcommand] - description_template = command_config.get('description') - command_description = ( - description_template.format(prog, subcommand)) - examples = command_config.get('examples', '').format(prog, subcommand) - epilog = 'examples:{0}'.format(examples) if examples else '' - subcommand_parser = subparsers.add_parser( - subcommand, - formatter_class=argparse.RawTextHelpFormatter, - description=command_description, - epilog=epilog, - help=command_config['help']) - command_config['flags'](subcommand_parser) - subcommand_parser.add_argument( - '--project', - dest='project', - default=None, - help=_PROJECT_HELP) - subcommand_parser.add_argument( - '--quiet', - dest='quiet', - action='store_true', - help='do not issue any interactive prompts') - subcommand_parser.add_argument( - '--verbosity', - dest='verbosity', - choices=['debug', 'info', 'warning', 'error', 'critical', 'none'], - default='error', - help='Override the default output verbosity for this command.') - if command_config['require-zone']: - subcommand_parser.add_argument( - '--zone', - dest='zone', - default=None, - help=_ZONE_HELP) + add_sub_parser(subcommand, _SUBCOMMANDS[subcommand], subparsers, prog) + + beta_parser = subparsers.add_parser( + 'beta', + formatter_class=argparse.RawTextHelpFormatter, + description='Beta commands for datalab.') + beta_subparsers = beta_parser.add_subparsers(dest='beta_subcommand') + for subcommand in _BETA_SUBCOMMANDS: + add_sub_parser(subcommand, _BETA_SUBCOMMANDS[subcommand], + beta_subparsers, prog) args = parser.parse_args() try: - _SUBCOMMANDS[args.subcommand]['run']( - args, gcloud_compute, - gcloud_repos=gcloud_repos, - email=get_email_address(), - in_cloud_shell=('DEVSHELL_CLIENT_PORT' in os.environ)) + if args.subcommand == 'beta': + _BETA_SUBCOMMANDS[args.beta_subcommand]['run']( + args, gcloud_beta_compute, + gcloud_repos=gcloud_repos, + email=get_email_address(), + in_cloud_shell=('DEVSHELL_CLIENT_PORT' in os.environ)) + else: + _SUBCOMMANDS[args.subcommand]['run']( + args, gcloud_compute, + gcloud_repos=gcloud_repos, + email=get_email_address(), + in_cloud_shell=('DEVSHELL_CLIENT_PORT' in os.environ)) except subprocess.CalledProcessError: print('A nested call to gcloud failed.') except Exception as e: diff --git a/tools/release/build.sh b/tools/release/build.sh index e3d9bef66..0720c5e81 100755 --- a/tools/release/build.sh +++ b/tools/release/build.sh @@ -34,6 +34,7 @@ TIMESTAMP=$(date +%Y%m%d) LABEL="${LABEL_PREFIX:-}${TIMESTAMP}" GATEWAY_IMAGE="gcr.io/${PROJECT_ID}/datalab-gateway:${LABEL}" DATALAB_IMAGE="gcr.io/${PROJECT_ID}/datalab:local-${LABEL}" +DATALAB_GPU_IMAGE="gcr.io/${PROJECT_ID}/datalab-gpu:local-${LABEL}" CLI_TARBALL="datalab-cli-${LABEL}.tgz" function install_node() { @@ -69,11 +70,10 @@ echo "Building the Datalab server" echo "Building the base image" cd containers/base -# We do not use the base image's `build.sh` script because we -# want to make sure that we are not using any cached layers. -mkdir -p pydatalab -docker build --no-cache -t datalab-base . -rm -rf pydatalab +DOCKER_BUILD_ARGS="--no-cache" +./build.sh +echo "Building the base GPU image" +./build.gpu.sh echo "Building the gateway image ${GATEWAY_IMAGE}" cd ../../containers/gateway @@ -87,6 +87,12 @@ cd ../../containers/datalab docker tag -f datalab ${DATALAB_IMAGE} gcloud docker -- push ${DATALAB_IMAGE} +echo "Building the Datalab GPU image ${DATALAB_GPU_IMAGE}" +cd ../../containers/datalab +./build.gpu.sh +docker tag -f datalab-gpu ${DATALAB_GPU_IMAGE} +gcloud docker -- push ${DATALAB_GPU_IMAGE} + cd ../../ tar -cvzf "/tmp/${CLI_TARBALL}" --transform 's,^tools/cli,datalab,' tools/cli gsutil cp "/tmp/${CLI_TARBALL}" "gs://${PROJECT_ID}/${CLI_TARBALL}" diff --git a/tools/release/publish.sh b/tools/release/publish.sh index 588cf067f..d648c80c0 100755 --- a/tools/release/publish.sh +++ b/tools/release/publish.sh @@ -38,10 +38,12 @@ TEST_PROJECT_ID="${TEST_PROJECT_ID:-`gcloud config list -q --format 'value(core. BUILD="${BUILD:-$(date +%Y%m%d)}" GATEWAY_IMAGE="gcr.io/${PROJECT_ID}/datalab-gateway:${BUILD}" DATALAB_IMAGE="gcr.io/${PROJECT_ID}/datalab:local-${BUILD}" +DATALAB_GPU_IMAGE="gcr.io/${PROJECT_ID}/datalab-gpu:local-${BUILD}" -echo "Pulling the daily gateway and Datalab images: ${GATEWAY_IMAGE}, ${DATALAB_IMAGE}" +echo "Pulling the daily Datalab images: ${GATEWAY_IMAGE}, ${DATALAB_IMAGE}, ${DATALAB_GPU_IMAGE}" gcloud docker -- pull ${GATEWAY_IMAGE} gcloud docker -- pull ${DATALAB_IMAGE} +gcloud docker -- pull ${DATALAB_GPU_IMAGE} echo "Running the Notebook tests..." mkdir -p tests @@ -65,6 +67,12 @@ gcloud docker -- push gcr.io/${PROJECT_ID}/datalab:local docker tag -f ${DATALAB_IMAGE} gcr.io/${PROJECT_ID}/datalab:latest gcloud docker -- push gcr.io/${PROJECT_ID}/datalab:latest +echo "Releasing the Datalab GPU image: ${DATALAB_GPU_IMAGE}" +docker tag -f ${DATALAB_GPU_IMAGE} gcr.io/${PROJECT_ID}/datalab-gpu:local +gcloud docker -- push gcr.io/${PROJECT_ID}/datalab-gpu:local +docker tag -f ${DATALAB_GPU_IMAGE} gcr.io/${PROJECT_ID}/datalab-gpu:latest +gcloud docker -- push gcr.io/${PROJECT_ID}/datalab-gpu:latest + gsutil cp gs://${PROJECT_ID}/deploy/config_local.js ./config_local.js # Get the latest and previous versions from the config_local. Note that older # config files don't have the full semantic version specified, so cannot extract using diff --git a/tools/release/rollback.sh b/tools/release/rollback.sh index 29429ccce..410bcd5b0 100755 --- a/tools/release/rollback.sh +++ b/tools/release/rollback.sh @@ -47,8 +47,9 @@ fi GATEWAY_IMAGE="gcr.io/${PROJECT_ID}/datalab-gateway:${PREVIOUS_BUILD}" DATALAB_IMAGE="gcr.io/${PROJECT_ID}/datalab:local-${PREVIOUS_BUILD}" +DATALAB_GPU_IMAGE="gcr.io/${PROJECT_ID}/datalab-gpu:local-${PREVIOUS_BUILD}" -read -p "Proceed to release ${GATEWAY_IMAGE} and ${DATALAB_IMAGE} as latest? [Y/n]: " answer +read -p "Proceed to release ${GATEWAY_IMAGE}, ${DATALAB_GPU_IMAGE}, and ${DATALAB_IMAGE} as latest? [Y/n]: " answer if echo $answer | grep -iq -v '^y'; then exit 1 fi @@ -67,3 +68,12 @@ gcloud docker -- push gcr.io/${PROJECT_ID}/datalab-gateway:latest echo "Releasing the Datalab image: ${DATALAB_IMAGE}" docker tag -f ${DATALAB_IMAGE} gcr.io/${PROJECT_ID}/datalab:local gcloud docker -- push gcr.io/${PROJECT_ID}/datalab:local + +echo "Pulling the rollback GPU images: ${DATALAB_GPU_IMAGE}" +# This will fail and exit if the previous GPU image doesn't exist. +# This will happen if we try to rollback the first GPU release, and +# that is fine since there is nothing to rollback to. +gcloud docker -- pull ${DATALAB_GPU_IMAGE} || exit 0 +echo "Releasing the Datalab GPU image: ${DATALAB_GPU_IMAGE}" +docker tag -f ${DATALAB_GPU_IMAGE} gcr.io/${PROJECT_ID}/datalab-gpu:local +gcloud docker -- push gcr.io/${PROJECT_ID}/datalab-gpu:local