Skip to content

Commit

Permalink
Merge pull request #351 from mesosphere/dcos-3103-post-uninstall-notes
Browse files Browse the repository at this point in the history
dcos-3103 Add post uninstall notes reporting
  • Loading branch information
jsancio committed Sep 11, 2015
2 parents d10332e + cda72bb commit 20103a1
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 25 deletions.
2 changes: 1 addition & 1 deletion cli/tests/data/dcos.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[core]
reporting = false
timeout = 5
email = "[email protected]"
timeout = 5
dcos_url = "http://change.dcos.url"
[package]
sources = [ "https://github.com/mesosphere/universe/archive/cli-tests.zip",]
Expand Down
8 changes: 6 additions & 2 deletions cli/tests/integrations/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,21 @@ def package_install(package, deploy=False, args=[]):
watch_all_deployments()


def package_uninstall(package, args=[]):
def package_uninstall(package, args=[], stderr=b''):
""" Calls `dcos package uninstall`
:param package: name of the package to uninstall
:type package: str
:param args: extra CLI args
:type args: [str]
:param stderr: expected string in stderr for package uninstall
:type stderr: str
:rtype: None
"""

assert_command(['dcos', 'package', 'uninstall', package] + args)
assert_command(
['dcos', 'package', 'uninstall', package] + args,
stderr=stderr)


def get_services(expected_count=None, args=[]):
Expand Down
29 changes: 26 additions & 3 deletions cli/tests/integrations/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,16 @@ def test_install_specific_version():
b'\tDocumentation: https://mesosphere.github.io/marathon\n'
b'\tIssues: https:/github.com/mesosphere/marathon/issues\n\n')

uninstall_stderr = (
b'Uninstalled package [marathon] version [0.8.1]\n'
b'The Marathon DCOS Service has been uninstalled and will no longer '
b'run.\nPlease follow the instructions at http://docs.mesosphere.com/'
b'services/marathon/#uninstall to clean up any persisted state\n'
)

with _package('marathon',
stdout=stdout,
uninstall_stderr=uninstall_stderr,
args=['--yes', '--package-version=0.8.1']):

returncode, stdout, stderr = exec_command(
Expand Down Expand Up @@ -615,12 +623,22 @@ def test_uninstall_multiple_frameworknames(zk_znode):
_uninstall_chronos(
args=['--app-id=chronos-user-1'],
returncode=1,
stderr='Unable to shutdown the framework for [chronos-user] because '
stderr='Uninstalled package [chronos] version [2.3.4]\n'
'The Chronos DCOS Service has been uninstalled and will no '
'longer run.\nPlease follow the instructions at http://docs.'
'mesosphere.com/services/chronos/#uninstall to clean up any '
'persisted state\n'
'Unable to shutdown the framework for [chronos-user] because '
'there are multiple frameworks with the same name: ')
_uninstall_chronos(
args=['--app-id=chronos-user-2'],
returncode=1,
stderr='Unable to shutdown the framework for [chronos-user] because '
stderr='Uninstalled package [chronos] version [2.3.4]\n'
'The Chronos DCOS Service has been uninstalled and will no '
'longer run.\nPlease follow the instructions at http://docs.'
'mesosphere.com/services/chronos/#uninstall to clean up any '
'persisted state\n'
'Unable to shutdown the framework for [chronos-user] because '
'there are multiple frameworks with the same name: ')

for framework in get_services(args=['--inactive']):
Expand Down Expand Up @@ -825,6 +843,7 @@ def _helloworld():
@contextlib.contextmanager
def _package(name,
stdout=b'',
uninstall_stderr=b'',
args=['--yes']):
"""Context manager that installs a package on entrace, and uninstalls it on
exit.
Expand All @@ -833,6 +852,8 @@ def _package(name,
:type name: str
:param stdout: Expected stdout
:type stdout: str
:param uninstall_stderr: Expected stderr
:type uninstall_stderr: str
:param args: extra CLI args
:type args: [str]
:rtype: None
Expand All @@ -843,4 +864,6 @@ def _package(name,
try:
yield
finally:
assert_command(['dcos', 'package', 'uninstall', name])
assert_command(
['dcos', 'package', 'uninstall', name],
stderr=uninstall_stderr)
23 changes: 21 additions & 2 deletions cli/tests/integrations/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ def setup_module(module):


def teardown_module(module):
package_uninstall('chronos')
package_uninstall(
'chronos',
stderr=b'Uninstalled package [chronos] version [2.3.4]\n'
b'The Chronos DCOS Service has been uninstalled and will no '
b'longer run.\nPlease follow the instructions at http://docs.'
b'mesosphere.com/services/chronos/#uninstall to clean up any '
b'persisted state\n')
delete_zk_nodes()


Expand Down Expand Up @@ -196,7 +202,20 @@ def test_log_multiple_apps():
returncode=1,
stderr=stderr)
finally:
package_uninstall('marathon', ['--all'])
# Uninstall notes and message are printed twice because --all will
# uninstall two packages
package_uninstall(
'marathon', ['--all'],
stderr=b'Uninstalled package [marathon] version [0.9.0]\n'
b'The Marathon DCOS Service has been uninstalled and will '
b'no longer run.\nPlease follow the instructions at http://'
b'docs.mesosphere.com/services/marathon/#uninstall to '
b'clean up any persisted state\n'
b'Uninstalled package [marathon] version [0.9.0]\n'
b'The Marathon DCOS Service has been uninstalled and will '
b'no longer run.\nPlease follow the instructions at http://'
b'docs.mesosphere.com/services/marathon/#uninstall to '
b'clean up any persisted state\n')


def test_log_no_apps():
Expand Down
4 changes: 2 additions & 2 deletions cli/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ commands =

[testenv:py27-integration]
commands =
py.test -vv {env:CI_FLAGS:} tests/integrations{posargs}
py.test -vv -x {env:CI_FLAGS:} tests/integrations{posargs}

[testenv:py34-integration]
commands =
py.test -vv {env:CI_FLAGS:} tests/integrations{posargs}
py.test -vv -x {env:CI_FLAGS:} tests/integrations{posargs}

[testenv:py27-unit]
commands =
Expand Down
62 changes: 47 additions & 15 deletions dcos/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import six
from dcos import (constants, emitting, errors, http, marathon, mesos,
subcommand, util)
from dcos.errors import DCOSException
from dcos.errors import DCOSException, DefaultError

from six.moves import urllib

Expand Down Expand Up @@ -239,6 +239,9 @@ def is_match(app):
', '.join(app_ids)))

for app in matching_apps:
package_json = _decode_and_add_context(
app['id'],
app.get('labels', {}))
# First, remove the app from Marathon
init_client.remove_app(app['id'], force=True)

Expand All @@ -260,6 +263,17 @@ def is_match(app):
logger.info(
'Found the following frameworks: {}'.format(framework_ids))

# Emit post uninstall notes
emitter.publish(
DefaultError(
'Uninstalled package [{}] version [{}]'.format(
package_json['name'],
package_json['version'])))

if 'postUninstallNotes' in package_json:
emitter.publish(
DefaultError(package_json['postUninstallNotes']))

if len(framework_ids) == 1:
dcos_client.shutdown_framework(framework_ids[0])
elif len(framework_ids) > 1:
Expand Down Expand Up @@ -410,26 +424,15 @@ def installed_apps(init_client, endpoints=False):
for a in apps
if a.get('labels', {}).get(PACKAGE_METADATA_KEY)]

def decode_and_add_context(pair):
app_id, labels = pair
encoded = labels.get(PACKAGE_METADATA_KEY, {})
decoded = base64.b64decode(six.b(encoded)).decode()

decoded_json = util.load_jsons(decoded)
decoded_json['appId'] = app_id
decoded_json['packageSource'] = labels.get(PACKAGE_SOURCE_KEY)
decoded_json['releaseVersion'] = labels.get(PACKAGE_RELEASE_KEY)
return decoded_json

# Filter elements that failed to parse correctly as JSON
valid_apps = []
for encoded in encoded_apps:
for app_id, labels in encoded_apps:
try:
decoded = decode_and_add_context(encoded)
decoded = _decode_and_add_context(app_id, labels)
except Exception:
logger.exception(
'Unable to decode package metadata during install: %s',
encoded[0])
app_id)

valid_apps.append(decoded)

Expand All @@ -442,6 +445,35 @@ def decode_and_add_context(pair):
return valid_apps


def _decode_and_add_context(app_id, labels):
""" Create an enhanced package JSON from Marathon labels
{
'appId': <appId>,
'packageSource': <source>,
'registryVersion': <app_version>,
'releaseVersion': <release_version>,
..<package.json properties>..
}
:param app_id: Marathon application id
:type app_id: str
:param labels: Marathon label dictionary
:type labels: dict
:rtype: dict
"""

encoded = labels.get(PACKAGE_METADATA_KEY, {})
decoded = base64.b64decode(six.b(encoded)).decode()

decoded_json = util.load_jsons(decoded)
decoded_json['appId'] = app_id
decoded_json['packageSource'] = labels.get(PACKAGE_SOURCE_KEY)
decoded_json['releaseVersion'] = labels.get(PACKAGE_RELEASE_KEY)

return decoded_json


def search(query, cfg):
"""Returns a list of index entry collections, one for each registry in
the supplied config.
Expand Down

0 comments on commit 20103a1

Please sign in to comment.