Skip to content

Commit

Permalink
Merge pull request #524 from menivaitsi/version-check
Browse files Browse the repository at this point in the history
Change the version compatibility check between AppScale and AppScale Tools.
  • Loading branch information
cdonati authored Jul 29, 2016
2 parents d301aee + 545030f commit 2179463
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 130 deletions.
17 changes: 10 additions & 7 deletions appscale/tools/appscale_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,15 @@ def run_instances(cls, options):
LocalState.make_appscale_directory()
LocalState.ensure_appscale_isnt_running(options.keyname, options.force)

reduced_version = '.'.join(x for x in APPSCALE_VERSION.split('.')[:2])

if options.infrastructure:
if not options.disks and not options.test and not options.force:
LocalState.ensure_user_wants_to_run_without_disks()
AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION +
AppScaleLogger.log("Starting AppScale " + reduced_version +
" over the " + options.infrastructure + " cloud.")
else:
AppScaleLogger.log("Starting AppScale " + APPSCALE_VERSION +
AppScaleLogger.log("Starting AppScale " + reduced_version +
" over a virtualized cluster.")
my_id = str(uuid.uuid4())
AppScaleLogger.remote_log_tools_state(options, my_id, "started",
Expand Down Expand Up @@ -763,12 +765,13 @@ def upgrade(cls, options):
master_ip = node_layout.head_node().public_ip
upgrade_version_available = cls.get_upgrade_version_available()

remote_version = '{}/{}'.format(RemoteHelper.CONFIG_DIR, 'VERSION')
version_output = RemoteHelper.ssh(
master_ip, options.keyname, 'cat {}'.format(remote_version), False)
current_version = version_output.split('AppScale version')[1].strip()
current_version = RemoteHelper.get_host_appscale_version(
master_ip, options.keyname, options.verbose)

if current_version == upgrade_version_available:
# Don't run bootstrap if current version is later that the most recent
# public one. Covers cases of revoked versions/tags and ensures we won't
# try to downgrade the code.
if current_version >= upgrade_version_available:
AppScaleLogger.log(
'AppScale is already up to date. Skipping code upgrade.')
AppScaleLogger.log(
Expand Down
58 changes: 17 additions & 41 deletions appscale/tools/remote_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ def ensure_machine_is_compatible(cls, host, keyname, database, is_verbose):
"""Verifies that the specified host has AppScale installed on it.
This also validates that the host has the right version of AppScale
installed on it, as well as the database that the user has asked for.
installed on it.
Args:
host: A str representing the host that may or may not be
Expand All @@ -476,33 +476,21 @@ def ensure_machine_is_compatible(cls, host, keyname, database, is_verbose):
execute to validate the machine to stdout.
Raises:
AppScaleException: If the specified host does not have AppScale installed,
has the wrong version of AppScale installed, or does not have the
correct database installed.
or has the wrong version of AppScale installed.
"""
# first, make sure the image is an appscale image
if not cls.does_host_have_location(host, keyname, cls.CONFIG_DIR,
is_verbose):
# First, make sure the image is an AppScale image.
remote_version = cls.get_host_appscale_version(host, keyname, is_verbose)
if not remote_version:
raise AppScaleException("The machine at {0} does not have " \
"AppScale installed.".format(host))

# Make sure the remote version matches the tools version.
version_dir = '{}/{}'.format(cls.CONFIG_DIR, APPSCALE_VERSION)
if not cls.does_host_have_location(host, keyname, version_dir, is_verbose):
host_version = cls.get_host_appscale_version(host, keyname, is_verbose)
if host_version:
raise AppScaleException("The machine at {0} has AppScale {1} installed,"
" but you are trying to use it with version {2} of the AppScale "
"Tools. Please use the same version of the AppScale Tools that the "
"host machine runs.".format(host, host_version, APPSCALE_VERSION))
else:
raise AppScaleException("The machine at {0} does not have AppScale " \
"{1} installed.".format(host, APPSCALE_VERSION))

# Make sure we have the requested database installed.
db_file = '{}/{}/{}'.format(cls.CONFIG_DIR, APPSCALE_VERSION, database)
if not cls.does_host_have_location(host, keyname, db_file, is_verbose):
raise AppScaleException("The machine at {0} does not have support for"
" {1} installed.".format(host, database))
# Make sure the remote version is compatible with the tools version.
reduced_version = '.'.join(x for x in remote_version.split('.')[:2])
if not APPSCALE_VERSION.startswith(reduced_version):
raise AppScaleException("The machine at {0} has AppScale {1} installed,"
" but you are trying to use it with version {2} of the AppScale "
"Tools. Please use the same version of the AppScale Tools that the "
"host machine runs.".format(host, remote_version, APPSCALE_VERSION))


@classmethod
Expand Down Expand Up @@ -547,26 +535,14 @@ def get_host_appscale_version(cls, host, keyname, is_verbose):
(1) AppScale isn't installed on host, or (2) host has more than one
version of AppScale installed.
"""
remote_version_file = '{}/{}'.format(cls.CONFIG_DIR, 'VERSION')
try:
etc_appscale_files = cls.ssh(host, keyname,
'ls {}'.format(cls.CONFIG_DIR), is_verbose)

# If the host doesn't have an config directory, then the ls command
# will be empty, so catch that special case here.
if etc_appscale_files:
etc_appscale_files = etc_appscale_files.split()
else:
return None

# Only check version files.
versions_installed = [name for name in etc_appscale_files
if re.match(cls.VERSION_REGEX, name)]
if len(versions_installed) == 1:
return versions_installed[0]
else:
return None
version_output = cls.ssh(
host, keyname, 'cat {}'.format(remote_version_file), is_verbose)
except ShellException:
return None
version = version_output.split('AppScale version')[1].strip()
return version


@classmethod
Expand Down
8 changes: 3 additions & 5 deletions test/test_appscale_run_instances.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
# Programmer: Chris Bunch ([email protected])


# General-purpose Python library imports
Expand Down Expand Up @@ -195,10 +194,9 @@ def setup_appscale_compatibility_mocks(self):
self.local_state.should_receive('shell').with_args(re.compile('ssh'),
False, 5, stdin=re.compile(RemoteHelper.CONFIG_DIR)).and_return()

# Assume the verion file exists.
version_file = '{}/{}'.format(RemoteHelper.CONFIG_DIR, APPSCALE_VERSION)
self.local_state.should_receive('shell').with_args(re.compile('ssh'),
False, 5, stdin=re.compile(version_file))
flexmock(RemoteHelper)
RemoteHelper.should_receive('get_host_appscale_version').\
and_return(APPSCALE_VERSION)

# Assume we are using a supported database.
db_file = '{}/{}/{}'.\
Expand Down
83 changes: 6 additions & 77 deletions test/test_remote_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@
from appscale.tools.appscale_tools import AppScaleTools
from appscale.tools.custom_exceptions import AppScaleException
from appscale.tools.custom_exceptions import BadConfigurationException
from appscale.tools.custom_exceptions import ShellException
from appscale.tools.local_state import APPSCALE_VERSION
from appscale.tools.local_state import LocalState
from appscale.tools.node_layout import NodeLayout
from appscale.tools.remote_helper import RemoteHelper
Expand Down Expand Up @@ -221,10 +219,9 @@ def test_start_head_node_in_cloud_but_ami_not_appscale(self):
re.compile('ssh'), False, 5, stdin=re.compile('rm -f ')
).and_return()

local_state.should_receive('shell').with_args(
re.compile('^ssh'), False, 5,
stdin=re.compile('ls {}'.format(RemoteHelper.CONFIG_DIR))
).and_raise(ShellException).ordered()
# Assume AppScale is not installed.
flexmock(RemoteHelper).\
should_receive('get_host_appscale_version').and_return(None)

# Check that the cleanup routine is called on error.
flexmock(AppScaleTools).should_receive('terminate_instances')\
Expand Down Expand Up @@ -278,11 +275,9 @@ def test_start_head_node_in_cloud_but_ami_wrong_version(self):
stdin=re.compile('ls {}'.format(RemoteHelper.CONFIG_DIR))
).and_return()

# Assume the version file does not exist.
version_dir = '{}/{}'.format(RemoteHelper.CONFIG_DIR, APPSCALE_VERSION)
local_state.should_receive('shell').with_args(re.compile('^ssh'), False,
5, stdin=re.compile('ls {}'.format(version_dir))).\
and_raise(ShellException)
# Assume AppScale is not installed.
flexmock(RemoteHelper).\
should_receive('get_host_appscale_version').and_return('X.Y.Z')

# check that the cleanup routine is called on error
flexmock(AppScaleTools).should_receive('terminate_instances')\
Expand All @@ -292,72 +287,6 @@ def test_start_head_node_in_cloud_but_ami_wrong_version(self):
self.options, self.my_id, self.node_layout)


def test_start_head_node_in_cloud_but_using_unsupported_database(self):
local_state = flexmock(LocalState)

# Mock out our attempts to enable the root login.
local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5,
stdin='sudo touch /root/.ssh/authorized_keys').and_return()

local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5,
stdin='sudo chmod 600 /root/.ssh/authorized_keys').and_return()

local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5, stdin='mktemp').and_return()

local_state.should_receive('shell') \
.with_args(re.compile('^ssh'), False, 5,
stdin='ls') \
.and_return(RemoteHelper.LOGIN_AS_UBUNTU_USER)

local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5,
stdin=re.compile(
'sudo sort -u ~/.ssh/authorized_keys /root/.ssh/authorized_keys -o '
)
).and_return()

local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5,
stdin=re.compile(
'sudo sed -n '
'\'\/\.\*Please login\/d; w\/root\/\.ssh\/authorized_keys\' '
)
).and_return()

local_state.should_receive('shell').with_args(
re.compile('ssh'), False, 5, stdin=re.compile('rm -f ')
).and_return()

# Assume the configuration directory exists.
local_state.should_receive('shell').with_args(re.compile('^ssh'), False,
5, stdin=re.compile('ls {}'.format(RemoteHelper.CONFIG_DIR))).\
and_return().ordered()

# Assume the version directory exists.
version_dir = '{}/{}'.format(RemoteHelper.CONFIG_DIR, APPSCALE_VERSION)
local_state.should_receive('shell').with_args(re.compile('^ssh'), False,
5, stdin=re.compile('ls {}'.format(version_dir))).\
and_return().ordered()

# Assume the given database is not supported.
db_file = '{}/{}/{}'.\
format(RemoteHelper.CONFIG_DIR, APPSCALE_VERSION, 'cassandra')
local_state.should_receive('shell').with_args(
re.compile('^ssh'), False, 5,
stdin=re.compile('ls {}'.format(db_file))
).and_raise(ShellException).ordered()

# check that the cleanup routine is called on error
flexmock(AppScaleTools).should_receive('terminate_instances')\
.and_return().ordered()

self.assertRaises(AppScaleException, RemoteHelper.start_head_node,
self.options, self.my_id, self.node_layout)


def test_rsync_files_from_dir_that_doesnt_exist(self):
# if the user specifies that we should copy from a directory that doesn't
# exist, we should throw up and die
Expand Down

0 comments on commit 2179463

Please sign in to comment.