Skip to content

Commit 4c06084

Browse files
Eric Divencawallin
Eric Diven
authored andcommitted
SWARM-386
1 parent 76608ad commit 4c06084

15 files changed

+384
-42
lines changed

bin/install-docker.sh

100644100755
File mode changed.

prestoadmin/configure_cmds.py

+47-6
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@
1818
import logging
1919
import os
2020
from StringIO import StringIO
21+
from contextlib import closing
2122
from fabric.contrib import files
2223
from fabric.decorators import task, serial
2324
from fabric.operations import get
25+
from fabric.operations import put
2426
from fabric.state import env
2527
from fabric.utils import abort, warn
2628
import prestoadmin.deploy
2729
from prestoadmin.topology import requires_topology
2830
from prestoadmin.util import constants
31+
from prestoadmin.util.filesystem import ensure_parent_directories_exist
2932

3033

3134
CONFIG_PROPERTIES = "config.properties"
3235
LOG_PROPERTIES = "log.properties"
3336
JVM_CONFIG = "jvm.config"
3437
NODE_PROPERTIES = "node.properties"
3538

39+
ALL_CONFIG = [CONFIG_PROPERTIES, LOG_PROPERTIES, JVM_CONFIG, NODE_PROPERTIES]
40+
3641
_LOGGER = logging.getLogger(__name__)
3742

3843
__all__ = ['deploy', 'show']
@@ -69,15 +74,51 @@ def deploy(rolename=None):
6974
abort("Invalid Argument. Possible values: coordinator, workers")
7075

7176

72-
def configuration_show(file_name, should_warn=True):
73-
file_path = os.path.join(constants.REMOTE_CONF_DIR, file_name)
74-
if not files.exists(file_path):
77+
def gather_directory(target_directory, allow_overwrite=False):
78+
fetch_all(target_directory, allow_overwrite=allow_overwrite)
79+
80+
81+
def deploy_all(source_directory, should_warn=True):
82+
host_config_dir = os.path.join(source_directory, env.host)
83+
for file_name in ALL_CONFIG:
84+
local_config_file = os.path.join(host_config_dir, file_name)
85+
if not os.path.exists(local_config_file):
86+
if should_warn:
87+
warn("No configuration file found for %s at %s"
88+
% (env.host, local_config_file))
89+
continue
90+
remote_config_file = os.path.join(constants.REMOTE_CONF_DIR, file_name)
91+
put(local_config_file, remote_config_file)
92+
93+
94+
def fetch_all(target_directory, allow_overwrite=False):
95+
host_config_dir = os.path.join(target_directory, env.host)
96+
for file_name in ALL_CONFIG:
97+
local_config_file = os.path.join(host_config_dir, file_name)
98+
ensure_parent_directories_exist(local_config_file)
99+
if not allow_overwrite and os.path.exists(local_config_file):
100+
abort("Refusing to overwrite %s" % (local_config_file))
101+
configuration_fetch(file_name, local_config_file)
102+
103+
104+
def configuration_fetch(file_name, config_destination, should_warn=True):
105+
remote_file_path = os.path.join(constants.REMOTE_CONF_DIR, file_name)
106+
if not files.exists(remote_file_path):
75107
if should_warn:
76108
warn("No configuration file found for %s at %s"
77-
% (env.host, file_path))
109+
% (env.host, remote_file_path))
110+
return None
78111
else:
79-
file_content_buffer = StringIO()
80-
get(file_path, file_content_buffer)
112+
get(remote_file_path, config_destination)
113+
return remote_file_path
114+
115+
116+
def configuration_show(file_name, should_warn=True):
117+
with closing(StringIO()) as file_content_buffer:
118+
file_path = configuration_fetch(file_name, file_content_buffer,
119+
should_warn)
120+
if file_path is None:
121+
return
81122
config_values = file_content_buffer.getvalue()
82123
file_content_buffer.close()
83124
print ("\n%s: Configuration file at %s:" % (env.host, file_path))

prestoadmin/connector.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
import logging
1919
import errno
2020

21-
from fabric.api import task, env
21+
from fabric.api import task, env, abort
2222
from fabric.context_managers import hide
23-
from fabric.operations import sudo, os, put
23+
from fabric.contrib import files
24+
from fabric.operations import sudo, os, put, get
2425
import fabric.utils
2526

2627
from prestoadmin.util import constants
2728
from prestoadmin.util.exception import ConfigFileNotFoundError, \
2829
ConfigurationError
30+
from prestoadmin.util.filesystem import ensure_directory_exists
2931

3032
_LOGGER = logging.getLogger(__name__)
3133

@@ -40,6 +42,17 @@ def deploy_files(filenames, local_dir, remote_dir):
4042
put(os.path.join(local_dir, name), remote_dir, use_sudo=True)
4143

4244

45+
def gather_connectors(local_config_dir, allow_overwrite=False):
46+
local_catalog_dir = os.path.join(local_config_dir, env.host, 'catalog')
47+
if not allow_overwrite and os.path.exists(local_catalog_dir):
48+
abort("Refusing to overwrite %s" % local_catalog_dir)
49+
ensure_directory_exists(local_catalog_dir)
50+
if files.exists(constants.REMOTE_CATALOG_DIR):
51+
return get(constants.REMOTE_CATALOG_DIR, local_catalog_dir)
52+
else:
53+
return []
54+
55+
4356
def validate(filenames):
4457
for name in filenames:
4558
file_path = os.path.join(constants.CONNECTORS_DIR, name)

prestoadmin/package.py

+27-2
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,17 @@ def check_if_valid_rpm(local_path):
6161

6262

6363
def deploy_install(local_path):
64+
deploy_action(local_path, rpm_install)
65+
66+
67+
def deploy_upgrade(local_path):
68+
deploy_action(local_path, rpm_upgrade)
69+
70+
71+
def deploy_action(local_path, rpm_action):
6472
check_if_valid_rpm(local_path)
6573
deploy(local_path)
66-
rpm_install(os.path.basename(local_path))
74+
rpm_action(os.path.basename(local_path))
6775

6876

6977
def deploy(local_path=None):
@@ -86,6 +94,23 @@ def rpm_install(rpm_name):
8694
nodeps = '--nodeps '
8795

8896
ret = sudo('rpm -i %s%s' %
89-
(nodeps, constants.REMOTE_PACKAGES_PATH + "/" + rpm_name))
97+
(nodeps, os.path.join(constants.REMOTE_PACKAGES_PATH,
98+
rpm_name)))
9099
if ret.succeeded:
91100
print("Package installed successfully on: " + env.host)
101+
102+
103+
def rpm_upgrade(rpm_name):
104+
_LOGGER.info("Upgrading the rpm")
105+
nodeps = ''
106+
if env.nodeps:
107+
nodeps = '--nodeps '
108+
109+
package_path = os.path.join(constants.REMOTE_PACKAGES_PATH, rpm_name)
110+
package_name = sudo('rpm -qp --queryformat \'%%{NAME}\' %s'
111+
% package_path, quiet=True)
112+
113+
ret_uninstall = sudo('rpm -e %s%s' % (nodeps, package_name))
114+
ret_install = sudo('rpm -i %s%s' % (nodeps, package_path))
115+
if ret_uninstall.succeeded and ret_install.succeeded:
116+
print("Package upgraded successfully on: " + env.host)

prestoadmin/server.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,12 @@
3737
from prestoadmin.util.exception import ConfigFileNotFoundError
3838
from prestoadmin.util.fabricapi import get_host_list, get_coordinator_role
3939
from prestoadmin.util.service_util import lookup_port
40+
41+
from tempfile import mkdtemp
4042
import util.filesystem
4143

42-
__all__ = ['install', 'uninstall', 'start', 'stop', 'restart', 'status']
44+
__all__ = ['install', 'uninstall', 'upgrade', 'start', 'stop', 'restart',
45+
'status']
4346

4447
INIT_SCRIPTS = '/etc/init.d/presto'
4548
RETRY_TIMEOUT = 120
@@ -125,6 +128,50 @@ def uninstall():
125128
print('Package uninstalled successfully on: ' + env.host)
126129

127130

131+
@task
132+
@requires_topology
133+
def upgrade(local_package_path, local_config_dir=None):
134+
"""
135+
Copy and upgrade a new presto-server rpm to all of the nodes in the
136+
cluster. Retains existing node configuration.
137+
138+
The existing topology information is read from the config.json file.
139+
Unlike install, there is no provision to supply topology information
140+
interactively.
141+
142+
The existing cluster configuration is collected from the nodes on the
143+
cluster and stored on the host running presto-admin. After the
144+
presto-server packages have been upgraded, presto-admin pushes the
145+
collected configuration back out to the hosts on the cluster.
146+
147+
Note that the configuration files in /etc/opt/prestoadmin are not updated
148+
during upgrade.
149+
150+
:param local_package_path - Absolute path to the presto rpm to be
151+
installed
152+
:param local_config_dir - (optional) Directory to store the cluster
153+
configuration in. If not specified, a temp
154+
directory is used.
155+
"""
156+
stop()
157+
158+
if not local_config_dir:
159+
local_config_dir = mkdtemp()
160+
print('Saving cluster configuration to %s' % local_config_dir)
161+
162+
configure_cmds.gather_directory(local_config_dir)
163+
filenames = connector.gather_connectors(local_config_dir)
164+
165+
package.deploy_upgrade(local_package_path)
166+
167+
configure_cmds.deploy_all(local_config_dir)
168+
connector.deploy_files(
169+
filenames,
170+
os.path.join(local_config_dir, env.host, 'catalog'),
171+
constants.REMOTE_CATALOG_DIR
172+
)
173+
174+
128175
def service(control=None):
129176
if check_presto_version() != '':
130177
return False

prestoadmin/util/filesystem.py

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ def ensure_parent_directories_exist(path):
3030
raise e
3131

3232

33+
def ensure_directory_exists(path):
34+
try:
35+
os.makedirs(path)
36+
except OSError as e:
37+
if e.errno != errno.EEXIST:
38+
raise e
39+
40+
3341
def write_to_file_if_not_exists(content, path):
3442
flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY
3543

tests/docker_cluster.py

+3
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ def stop_host(self, container_name):
169169
self.client.stop(container_name)
170170
self.client.wait(container_name)
171171

172+
def start_host(self, container_name):
173+
self.client.start(container_name)
174+
172175
def get_down_hostname(self, host_name):
173176
return host_name
174177

tests/product/base_product_case.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,8 @@ def retry(self, method_to_check):
515515
result = method_to_check()
516516
# No exception thrown, success
517517
return result
518-
except (AssertionError, PrestoError):
518+
except (AssertionError, PrestoError, OSError):
519519
pass
520-
except OSError as e:
521-
if not e.errno == 7:
522-
raise
523520
sleep(RETRY_INTERVAL)
524521
time_spent_waiting += RETRY_INTERVAL
525522
return method_to_check()

tests/product/resources/dummy-rpm.rpm

12 KB
Binary file not shown.

tests/product/test_server_upgrade.py

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
17+
from nose.plugins.attrib import attr
18+
19+
from tests.product.base_product_case import BaseProductTestCase, \
20+
LOCAL_RESOURCES_DIR, docker_only
21+
22+
23+
class TestServerUpgrade(BaseProductTestCase):
24+
25+
def setUp(self):
26+
super(TestServerUpgrade, self).setUp()
27+
self.setup_cluster('presto')
28+
29+
def start_and_assert_started(self):
30+
cmd_output = self.run_prestoadmin('server start')
31+
process_per_host = self.get_process_per_host(cmd_output)
32+
self.assert_started(process_per_host)
33+
34+
def assert_upgraded_to_dummy_rpm(self, hosts):
35+
for container in hosts:
36+
# Still should have the same configs
37+
self.assert_installed(container)
38+
self.assert_has_default_config(container)
39+
self.assert_has_default_connector(container)
40+
41+
# However, dummy_rpm.rpm removes /usr/lib/presto/lib and
42+
# /usr/lib/presto/lib/plugin
43+
self.assert_path_removed(container, '/usr/lib/presto/lib')
44+
self.assert_path_removed(container, '/usr/lib/presto/lib/plugin')
45+
46+
# And adds /usr/lib/presto/README.txt
47+
self.assert_path_exists(container, '/usr/lib/presto/README.txt')
48+
49+
# And modifies the text of the readme in
50+
# /usr/shared/doc/presto/README.txt
51+
self.assert_file_content_regex(
52+
container,
53+
'/usr/shared/doc/presto/README.txt',
54+
r'.*New line of text here.$'
55+
)
56+
57+
def copy_upgrade_rpm_to_cluster(self):
58+
self.cluster.copy_to_host(
59+
os.path.join(LOCAL_RESOURCES_DIR, 'dummy-rpm.rpm'),
60+
self.cluster.master)
61+
path_on_cluster = os.path.join(
62+
self.cluster.mount_dir, 'dummy-rpm.rpm')
63+
return path_on_cluster
64+
65+
@attr('smoketest')
66+
def test_upgrade(self):
67+
self.start_and_assert_started()
68+
69+
self.run_prestoadmin('configuration deploy')
70+
for container in self.cluster.all_hosts():
71+
self.assert_installed(container)
72+
self.assert_has_default_config(container)
73+
self.assert_has_default_connector(container)
74+
75+
path_on_cluster = self.copy_upgrade_rpm_to_cluster()
76+
self.upgrade_and_assert_success(path_on_cluster)
77+
78+
def upgrade_and_assert_success(self, path_on_cluster):
79+
self.run_prestoadmin('server upgrade ' + path_on_cluster)
80+
self.assert_upgraded_to_dummy_rpm(self.cluster.all_hosts())
81+
82+
@docker_only
83+
def test_rolling_upgrade(self):
84+
# Test that if a node is down, and then you upgrade again, it works
85+
self.run_prestoadmin('configuration deploy')
86+
87+
self.cluster.stop_host(self.cluster.slaves[0])
88+
path_on_cluster = self.copy_upgrade_rpm_to_cluster()
89+
self.run_prestoadmin('server upgrade ' + path_on_cluster)
90+
running_hosts = self.cluster.all_hosts()[:]
91+
running_hosts.remove(self.cluster.slaves[0])
92+
self.assert_upgraded_to_dummy_rpm(running_hosts)
93+
94+
self.cluster.start_host(self.cluster.slaves[0])
95+
self.retry(lambda: self.upgrade_and_assert_success(path_on_cluster))

tests/unit/resources/extended-help.txt

+1
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ Commands:
5454
server status
5555
server stop
5656
server uninstall
57+
server upgrade
5758
topology show
5859

tests/unit/resources/help.txt

+1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ Commands:
2828
server status
2929
server stop
3030
server uninstall
31+
server upgrade
3132
topology show
3233

0 commit comments

Comments
 (0)