aboutsummaryrefslogtreecommitdiffstats
path: root/charms/trusty/keepalived
diff options
context:
space:
mode:
Diffstat (limited to 'charms/trusty/keepalived')
-rw-r--r--charms/trusty/keepalived/.project17
-rw-r--r--charms/trusty/keepalived/.pydevproject8
-rw-r--r--charms/trusty/keepalived/README.md23
-rw-r--r--charms/trusty/keepalived/bin/charm_helpers_sync.py253
-rw-r--r--charms/trusty/keepalived/charm-helpers-sync.yaml5
-rw-r--r--charms/trusty/keepalived/config.yaml11
-rw-r--r--charms/trusty/keepalived/copyright17
-rw-r--r--charms/trusty/keepalived/hooks/actions.py5
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/__init__.py38
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/__init__.py15
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/decorators.py57
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/fstab.py134
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/hookenv.py744
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/host.py468
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/services/__init__.py18
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/services/base.py350
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/services/helpers.py267
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/strutils.py42
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/sysctl.py56
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/templating.py68
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/core/unitdata.py477
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/fetch/__init__.py439
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/fetch/archiveurl.py161
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/fetch/bzrurl.py78
-rw-r--r--charms/trusty/keepalived/hooks/charmhelpers/fetch/giturl.py73
-rwxr-xr-xcharms/trusty/keepalived/hooks/config-changed3
-rwxr-xr-xcharms/trusty/keepalived/hooks/install18
-rwxr-xr-xcharms/trusty/keepalived/hooks/juju_info-relation-joined3
-rwxr-xr-xcharms/trusty/keepalived/hooks/leader-elected3
-rw-r--r--charms/trusty/keepalived/hooks/services.py57
-rw-r--r--charms/trusty/keepalived/hooks/setup.py17
-rwxr-xr-xcharms/trusty/keepalived/hooks/start3
-rwxr-xr-xcharms/trusty/keepalived/hooks/stop3
-rwxr-xr-xcharms/trusty/keepalived/hooks/upgrade-charm3
-rw-r--r--charms/trusty/keepalived/icon.svg293
-rw-r--r--charms/trusty/keepalived/metadata.yaml18
-rw-r--r--charms/trusty/keepalived/templates/50-keepalived.conf3
-rw-r--r--charms/trusty/keepalived/templates/keepalived.conf10
-rwxr-xr-xcharms/trusty/keepalived/tests/00-setup5
-rwxr-xr-xcharms/trusty/keepalived/tests/10-deploy51
-rwxr-xr-xcharms/trusty/keepalived/unit_tests/test_actions.py21
41 files changed, 0 insertions, 4335 deletions
diff --git a/charms/trusty/keepalived/.project b/charms/trusty/keepalived/.project
deleted file mode 100644
index 21d3bc6..0000000
--- a/charms/trusty/keepalived/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
- <name>keepalived</name>
- <comment></comment>
- <projects>
- </projects>
- <buildSpec>
- <buildCommand>
- <name>org.python.pydev.PyDevBuilder</name>
- <arguments>
- </arguments>
- </buildCommand>
- </buildSpec>
- <natures>
- <nature>org.python.pydev.pythonNature</nature>
- </natures>
-</projectDescription>
diff --git a/charms/trusty/keepalived/.pydevproject b/charms/trusty/keepalived/.pydevproject
deleted file mode 100644
index 509d596..0000000
--- a/charms/trusty/keepalived/.pydevproject
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?eclipse-pydev version="1.0"?><pydev_project>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
-<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
-<path>/${PROJECT_DIR_NAME}/hooks</path>
-</pydev_pathproperty>
-</pydev_project>
diff --git a/charms/trusty/keepalived/README.md b/charms/trusty/keepalived/README.md
deleted file mode 100644
index baf74fa..0000000
--- a/charms/trusty/keepalived/README.md
+++ /dev/null
@@ -1,23 +0,0 @@
-Overview
---------
-
-Keepalived is a routing software written in C. The main goal of this project is
-to provide simple and robust facilities for loadbalancing and high-availability
-to Linux system and Linux based infrastructures. Loadbalancing framework relies
-on well-known and widely used Linux Virtual Server (IPVS) kernel module
-providing Layer4 loadbalancing. Keepalived implements a set of checkers to
-dynamically and adaptively maintain and manage loadbalanced server pool
-according their health. On the other hand high-availability is achieved by VRRP
-protocol. VRRP is a fundamental brick for router failover. In addition,
-Keepalived implements a set of hooks to the VRRP finite state machine providing
-low-level and high-speed protocol interactions. Keepalived frameworks can be
-used independently or all together to provide resilient infrastructures.
-
-Usage
------
-
-Once ready, deploy as follows:
-
- juju deploy keepalived
- juju add-relation haproxy keepalived
-
diff --git a/charms/trusty/keepalived/bin/charm_helpers_sync.py b/charms/trusty/keepalived/bin/charm_helpers_sync.py
deleted file mode 100644
index f67fdb9..0000000
--- a/charms/trusty/keepalived/bin/charm_helpers_sync.py
+++ /dev/null
@@ -1,253 +0,0 @@
-#!/usr/bin/python
-
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-# Authors:
-# Adam Gandelman <adamg@ubuntu.com>
-
-import logging
-import optparse
-import os
-import subprocess
-import shutil
-import sys
-import tempfile
-import yaml
-from fnmatch import fnmatch
-
-import six
-
-CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
-
-
-def parse_config(conf_file):
- if not os.path.isfile(conf_file):
- logging.error('Invalid config file: %s.' % conf_file)
- return False
- return yaml.load(open(conf_file).read())
-
-
-def clone_helpers(work_dir, branch):
- dest = os.path.join(work_dir, 'charm-helpers')
- logging.info('Checking out %s to %s.' % (branch, dest))
- cmd = ['bzr', 'checkout', '--lightweight', branch, dest]
- subprocess.check_call(cmd)
- return dest
-
-
-def _module_path(module):
- return os.path.join(*module.split('.'))
-
-
-def _src_path(src, module):
- return os.path.join(src, 'charmhelpers', _module_path(module))
-
-
-def _dest_path(dest, module):
- return os.path.join(dest, _module_path(module))
-
-
-def _is_pyfile(path):
- return os.path.isfile(path + '.py')
-
-
-def ensure_init(path):
- '''
- ensure directories leading up to path are importable, omitting
- parent directory, eg path='/hooks/helpers/foo'/:
- hooks/
- hooks/helpers/__init__.py
- hooks/helpers/foo/__init__.py
- '''
- for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
- _i = os.path.join(d, '__init__.py')
- if not os.path.exists(_i):
- logging.info('Adding missing __init__.py: %s' % _i)
- open(_i, 'wb').close()
-
-
-def sync_pyfile(src, dest):
- src = src + '.py'
- src_dir = os.path.dirname(src)
- logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
- if not os.path.exists(dest):
- os.makedirs(dest)
- shutil.copy(src, dest)
- if os.path.isfile(os.path.join(src_dir, '__init__.py')):
- shutil.copy(os.path.join(src_dir, '__init__.py'),
- dest)
- ensure_init(dest)
-
-
-def get_filter(opts=None):
- opts = opts or []
- if 'inc=*' in opts:
- # do not filter any files, include everything
- return None
-
- def _filter(dir, ls):
- incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
- _filter = []
- for f in ls:
- _f = os.path.join(dir, f)
-
- if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
- if True not in [fnmatch(_f, inc) for inc in incs]:
- logging.debug('Not syncing %s, does not match include '
- 'filters (%s)' % (_f, incs))
- _filter.append(f)
- else:
- logging.debug('Including file, which matches include '
- 'filters (%s): %s' % (incs, _f))
- elif (os.path.isfile(_f) and not _f.endswith('.py')):
- logging.debug('Not syncing file: %s' % f)
- _filter.append(f)
- elif (os.path.isdir(_f) and not
- os.path.isfile(os.path.join(_f, '__init__.py'))):
- logging.debug('Not syncing directory: %s' % f)
- _filter.append(f)
- return _filter
- return _filter
-
-
-def sync_directory(src, dest, opts=None):
- if os.path.exists(dest):
- logging.debug('Removing existing directory: %s' % dest)
- shutil.rmtree(dest)
- logging.info('Syncing directory: %s -> %s.' % (src, dest))
-
- shutil.copytree(src, dest, ignore=get_filter(opts))
- ensure_init(dest)
-
-
-def sync(src, dest, module, opts=None):
-
- # Sync charmhelpers/__init__.py for bootstrap code.
- sync_pyfile(_src_path(src, '__init__'), dest)
-
- # Sync other __init__.py files in the path leading to module.
- m = []
- steps = module.split('.')[:-1]
- while steps:
- m.append(steps.pop(0))
- init = '.'.join(m + ['__init__'])
- sync_pyfile(_src_path(src, init),
- os.path.dirname(_dest_path(dest, init)))
-
- # Sync the module, or maybe a .py file.
- if os.path.isdir(_src_path(src, module)):
- sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
- elif _is_pyfile(_src_path(src, module)):
- sync_pyfile(_src_path(src, module),
- os.path.dirname(_dest_path(dest, module)))
- else:
- logging.warn('Could not sync: %s. Neither a pyfile or directory, '
- 'does it even exist?' % module)
-
-
-def parse_sync_options(options):
- if not options:
- return []
- return options.split(',')
-
-
-def extract_options(inc, global_options=None):
- global_options = global_options or []
- if global_options and isinstance(global_options, six.string_types):
- global_options = [global_options]
- if '|' not in inc:
- return (inc, global_options)
- inc, opts = inc.split('|')
- return (inc, parse_sync_options(opts) + global_options)
-
-
-def sync_helpers(include, src, dest, options=None):
- if not os.path.isdir(dest):
- os.makedirs(dest)
-
- global_options = parse_sync_options(options)
-
- for inc in include:
- if isinstance(inc, str):
- inc, opts = extract_options(inc, global_options)
- sync(src, dest, inc, opts)
- elif isinstance(inc, dict):
- # could also do nested dicts here.
- for k, v in six.iteritems(inc):
- if isinstance(v, list):
- for m in v:
- inc, opts = extract_options(m, global_options)
- sync(src, dest, '%s.%s' % (k, inc), opts)
-
-if __name__ == '__main__':
- parser = optparse.OptionParser()
- parser.add_option('-c', '--config', action='store', dest='config',
- default=None, help='helper config file')
- parser.add_option('-D', '--debug', action='store_true', dest='debug',
- default=False, help='debug')
- parser.add_option('-b', '--branch', action='store', dest='branch',
- help='charm-helpers bzr branch (overrides config)')
- parser.add_option('-d', '--destination', action='store', dest='dest_dir',
- help='sync destination dir (overrides config)')
- (opts, args) = parser.parse_args()
-
- if opts.debug:
- logging.basicConfig(level=logging.DEBUG)
- else:
- logging.basicConfig(level=logging.INFO)
-
- if opts.config:
- logging.info('Loading charm helper config from %s.' % opts.config)
- config = parse_config(opts.config)
- if not config:
- logging.error('Could not parse config from %s.' % opts.config)
- sys.exit(1)
- else:
- config = {}
-
- if 'branch' not in config:
- config['branch'] = CHARM_HELPERS_BRANCH
- if opts.branch:
- config['branch'] = opts.branch
- if opts.dest_dir:
- config['destination'] = opts.dest_dir
-
- if 'destination' not in config:
- logging.error('No destination dir. specified as option or config.')
- sys.exit(1)
-
- if 'include' not in config:
- if not args:
- logging.error('No modules to sync specified as option or config.')
- sys.exit(1)
- config['include'] = []
- [config['include'].append(a) for a in args]
-
- sync_options = None
- if 'options' in config:
- sync_options = config['options']
- tmpd = tempfile.mkdtemp()
- try:
- checkout = clone_helpers(tmpd, config['branch'])
- sync_helpers(config['include'], checkout, config['destination'],
- options=sync_options)
- except Exception as e:
- logging.error("Could not sync: %s" % e)
- raise e
- finally:
- logging.debug('Cleaning up %s' % tmpd)
- shutil.rmtree(tmpd)
diff --git a/charms/trusty/keepalived/charm-helpers-sync.yaml b/charms/trusty/keepalived/charm-helpers-sync.yaml
deleted file mode 100644
index 0af5672..0000000
--- a/charms/trusty/keepalived/charm-helpers-sync.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-branch: lp:charm-helpers
-destination: hooks/charmhelpers
-include:
- - core
- - fetch
diff --git a/charms/trusty/keepalived/config.yaml b/charms/trusty/keepalived/config.yaml
deleted file mode 100644
index 21f3827..0000000
--- a/charms/trusty/keepalived/config.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-options:
- virtual-ip:
- type: string
- description: |
- Virtual IP/netmask that will be moved between instances,
- e.g.: 10.1.2.3/16
- router-id:
- type: int
- description: |
- Virtual router identifier - a number between 1 and 255
- that's unique within the network segment \ No newline at end of file
diff --git a/charms/trusty/keepalived/copyright b/charms/trusty/keepalived/copyright
deleted file mode 100644
index 567db82..0000000
--- a/charms/trusty/keepalived/copyright
+++ /dev/null
@@ -1,17 +0,0 @@
-Format: http://dep.debian.net/deps/dep5/
-
-Files: *
-Copyright: Copyright 2015, Canonical Ltd., All Rights Reserved.
-License: GPL-3
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- .
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- .
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/charms/trusty/keepalived/hooks/actions.py b/charms/trusty/keepalived/hooks/actions.py
deleted file mode 100644
index 90440ec..0000000
--- a/charms/trusty/keepalived/hooks/actions.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from charmhelpers.core import hookenv
-
-
-def log_start(service_name):
- hookenv.log('keepalived starting')
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/__init__.py b/charms/trusty/keepalived/hooks/charmhelpers/__init__.py
deleted file mode 100644
index f72e7f8..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/__init__.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-# Bootstrap charm-helpers, installing its dependencies if necessary using
-# only standard libraries.
-import subprocess
-import sys
-
-try:
- import six # flake8: noqa
-except ImportError:
- if sys.version_info.major == 2:
- subprocess.check_call(['apt-get', 'install', '-y', 'python-six'])
- else:
- subprocess.check_call(['apt-get', 'install', '-y', 'python3-six'])
- import six # flake8: noqa
-
-try:
- import yaml # flake8: noqa
-except ImportError:
- if sys.version_info.major == 2:
- subprocess.check_call(['apt-get', 'install', '-y', 'python-yaml'])
- else:
- subprocess.check_call(['apt-get', 'install', '-y', 'python3-yaml'])
- import yaml # flake8: noqa
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/__init__.py b/charms/trusty/keepalived/hooks/charmhelpers/core/__init__.py
deleted file mode 100644
index d1400a0..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/__init__.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/decorators.py b/charms/trusty/keepalived/hooks/charmhelpers/core/decorators.py
deleted file mode 100644
index bb05620..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/decorators.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-#
-# Copyright 2014 Canonical Ltd.
-#
-# Authors:
-# Edward Hope-Morley <opentastic@gmail.com>
-#
-
-import time
-
-from charmhelpers.core.hookenv import (
- log,
- INFO,
-)
-
-
-def retry_on_exception(num_retries, base_delay=0, exc_type=Exception):
- """If the decorated function raises exception exc_type, allow num_retries
- retry attempts before raise the exception.
- """
- def _retry_on_exception_inner_1(f):
- def _retry_on_exception_inner_2(*args, **kwargs):
- retries = num_retries
- multiplier = 1
- while True:
- try:
- return f(*args, **kwargs)
- except exc_type:
- if not retries:
- raise
-
- delay = base_delay * multiplier
- multiplier += 1
- log("Retrying '%s' %d more times (delay=%s)" %
- (f.__name__, retries, delay), level=INFO)
- retries -= 1
- if delay:
- time.sleep(delay)
-
- return _retry_on_exception_inner_2
-
- return _retry_on_exception_inner_1
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/fstab.py b/charms/trusty/keepalived/hooks/charmhelpers/core/fstab.py
deleted file mode 100644
index 3056fba..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/fstab.py
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import io
-import os
-
-__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
-
-
-class Fstab(io.FileIO):
- """This class extends file in order to implement a file reader/writer
- for file `/etc/fstab`
- """
-
- class Entry(object):
- """Entry class represents a non-comment line on the `/etc/fstab` file
- """
- def __init__(self, device, mountpoint, filesystem,
- options, d=0, p=0):
- self.device = device
- self.mountpoint = mountpoint
- self.filesystem = filesystem
-
- if not options:
- options = "defaults"
-
- self.options = options
- self.d = int(d)
- self.p = int(p)
-
- def __eq__(self, o):
- return str(self) == str(o)
-
- def __str__(self):
- return "{} {} {} {} {} {}".format(self.device,
- self.mountpoint,
- self.filesystem,
- self.options,
- self.d,
- self.p)
-
- DEFAULT_PATH = os.path.join(os.path.sep, 'etc', 'fstab')
-
- def __init__(self, path=None):
- if path:
- self._path = path
- else:
- self._path = self.DEFAULT_PATH
- super(Fstab, self).__init__(self._path, 'rb+')
-
- def _hydrate_entry(self, line):
- # NOTE: use split with no arguments to split on any
- # whitespace including tabs
- return Fstab.Entry(*filter(
- lambda x: x not in ('', None),
- line.strip("\n").split()))
-
- @property
- def entries(self):
- self.seek(0)
- for line in self.readlines():
- line = line.decode('us-ascii')
- try:
- if line.strip() and not line.strip().startswith("#"):
- yield self._hydrate_entry(line)
- except ValueError:
- pass
-
- def get_entry_by_attr(self, attr, value):
- for entry in self.entries:
- e_attr = getattr(entry, attr)
- if e_attr == value:
- return entry
- return None
-
- def add_entry(self, entry):
- if self.get_entry_by_attr('device', entry.device):
- return False
-
- self.write((str(entry) + '\n').encode('us-ascii'))
- self.truncate()
- return entry
-
- def remove_entry(self, entry):
- self.seek(0)
-
- lines = [l.decode('us-ascii') for l in self.readlines()]
-
- found = False
- for index, line in enumerate(lines):
- if line.strip() and not line.strip().startswith("#"):
- if self._hydrate_entry(line) == entry:
- found = True
- break
-
- if not found:
- return False
-
- lines.remove(line)
-
- self.seek(0)
- self.write(''.join(lines).encode('us-ascii'))
- self.truncate()
- return True
-
- @classmethod
- def remove_by_mountpoint(cls, mountpoint, path=None):
- fstab = cls(path=path)
- entry = fstab.get_entry_by_attr('mountpoint', mountpoint)
- if entry:
- return fstab.remove_entry(entry)
- return False
-
- @classmethod
- def add(cls, device, mountpoint, filesystem, options=None, path=None):
- return cls(path=path).add_entry(Fstab.Entry(device,
- mountpoint, filesystem,
- options=options))
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/hookenv.py b/charms/trusty/keepalived/hooks/charmhelpers/core/hookenv.py
deleted file mode 100644
index 117429f..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/hookenv.py
+++ /dev/null
@@ -1,744 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-"Interactions with the Juju environment"
-# Copyright 2013 Canonical Ltd.
-#
-# Authors:
-# Charm Helpers Developers <juju@lists.ubuntu.com>
-
-from __future__ import print_function
-from functools import wraps
-import os
-import json
-import yaml
-import subprocess
-import sys
-import errno
-import tempfile
-from subprocess import CalledProcessError
-
-import six
-if not six.PY3:
- from UserDict import UserDict
-else:
- from collections import UserDict
-
-CRITICAL = "CRITICAL"
-ERROR = "ERROR"
-WARNING = "WARNING"
-INFO = "INFO"
-DEBUG = "DEBUG"
-MARKER = object()
-
-cache = {}
-
-
-def cached(func):
- """Cache return values for multiple executions of func + args
-
- For example::
-
- @cached
- def unit_get(attribute):
- pass
-
- unit_get('test')
-
- will cache the result of unit_get + 'test' for future calls.
- """
- @wraps(func)
- def wrapper(*args, **kwargs):
- global cache
- key = str((func, args, kwargs))
- try:
- return cache[key]
- except KeyError:
- pass # Drop out of the exception handler scope.
- res = func(*args, **kwargs)
- cache[key] = res
- return res
- return wrapper
-
-
-def flush(key):
- """Flushes any entries from function cache where the
- key is found in the function+args """
- flush_list = []
- for item in cache:
- if key in item:
- flush_list.append(item)
- for item in flush_list:
- del cache[item]
-
-
-def log(message, level=None):
- """Write a message to the juju log"""
- command = ['juju-log']
- if level:
- command += ['-l', level]
- if not isinstance(message, six.string_types):
- message = repr(message)
- command += [message]
- # Missing juju-log should not cause failures in unit tests
- # Send log output to stderr
- try:
- subprocess.call(command)
- except OSError as e:
- if e.errno == errno.ENOENT:
- if level:
- message = "{}: {}".format(level, message)
- message = "juju-log: {}".format(message)
- print(message, file=sys.stderr)
- else:
- raise
-
-
-class Serializable(UserDict):
- """Wrapper, an object that can be serialized to yaml or json"""
-
- def __init__(self, obj):
- # wrap the object
- UserDict.__init__(self)
- self.data = obj
-
- def __getattr__(self, attr):
- # See if this object has attribute.
- if attr in ("json", "yaml", "data"):
- return self.__dict__[attr]
- # Check for attribute in wrapped object.
- got = getattr(self.data, attr, MARKER)
- if got is not MARKER:
- return got
- # Proxy to the wrapped object via dict interface.
- try:
- return self.data[attr]
- except KeyError:
- raise AttributeError(attr)
-
- def __getstate__(self):
- # Pickle as a standard dictionary.
- return self.data
-
- def __setstate__(self, state):
- # Unpickle into our wrapper.
- self.data = state
-
- def json(self):
- """Serialize the object to json"""
- return json.dumps(self.data)
-
- def yaml(self):
- """Serialize the object to yaml"""
- return yaml.dump(self.data)
-
-
-def execution_environment():
- """A convenient bundling of the current execution context"""
- context = {}
- context['conf'] = config()
- if relation_id():
- context['reltype'] = relation_type()
- context['relid'] = relation_id()
- context['rel'] = relation_get()
- context['unit'] = local_unit()
- context['rels'] = relations()
- context['env'] = os.environ
- return context
-
-
-def in_relation_hook():
- """Determine whether we're running in a relation hook"""
- return 'JUJU_RELATION' in os.environ
-
-
-def relation_type():
- """The scope for the current relation hook"""
- return os.environ.get('JUJU_RELATION', None)
-
-
-def relation_id():
- """The relation ID for the current relation hook"""
- return os.environ.get('JUJU_RELATION_ID', None)
-
-
-def local_unit():
- """Local unit ID"""
- return os.environ['JUJU_UNIT_NAME']
-
-
-def remote_unit():
- """The remote unit for the current relation hook"""
- return os.environ.get('JUJU_REMOTE_UNIT', None)
-
-
-def service_name():
- """The name service group this unit belongs to"""
- return local_unit().split('/')[0]
-
-
-def hook_name():
- """The name of the currently executing hook"""
- return os.path.basename(sys.argv[0])
-
-
-class Config(dict):
- """A dictionary representation of the charm's config.yaml, with some
- extra features:
-
- - See which values in the dictionary have changed since the previous hook.
- - For values that have changed, see what the previous value was.
- - Store arbitrary data for use in a later hook.
-
- NOTE: Do not instantiate this object directly - instead call
- ``hookenv.config()``, which will return an instance of :class:`Config`.
-
- Example usage::
-
- >>> # inside a hook
- >>> from charmhelpers.core import hookenv
- >>> config = hookenv.config()
- >>> config['foo']
- 'bar'
- >>> # store a new key/value for later use
- >>> config['mykey'] = 'myval'
-
-
- >>> # user runs `juju set mycharm foo=baz`
- >>> # now we're inside subsequent config-changed hook
- >>> config = hookenv.config()
- >>> config['foo']
- 'baz'
- >>> # test to see if this val has changed since last hook
- >>> config.changed('foo')
- True
- >>> # what was the previous value?
- >>> config.previous('foo')
- 'bar'
- >>> # keys/values that we add are preserved across hooks
- >>> config['mykey']
- 'myval'
-
- """
- CONFIG_FILE_NAME = '.juju-persistent-config'
-
- def __init__(self, *args, **kw):
- super(Config, self).__init__(*args, **kw)
- self.implicit_save = True
- self._prev_dict = None
- self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
- if os.path.exists(self.path):
- self.load_previous()
-
- def __getitem__(self, key):
- """For regular dict lookups, check the current juju config first,
- then the previous (saved) copy. This ensures that user-saved values
- will be returned by a dict lookup.
-
- """
- try:
- return dict.__getitem__(self, key)
- except KeyError:
- return (self._prev_dict or {})[key]
-
- def get(self, key, default=None):
- try:
- return self[key]
- except KeyError:
- return default
-
- def keys(self):
- prev_keys = []
- if self._prev_dict is not None:
- prev_keys = self._prev_dict.keys()
- return list(set(prev_keys + list(dict.keys(self))))
-
- def load_previous(self, path=None):
- """Load previous copy of config from disk.
-
- In normal usage you don't need to call this method directly - it
- is called automatically at object initialization.
-
- :param path:
-
- File path from which to load the previous config. If `None`,
- config is loaded from the default location. If `path` is
- specified, subsequent `save()` calls will write to the same
- path.
-
- """
- self.path = path or self.path
- with open(self.path) as f:
- self._prev_dict = json.load(f)
-
- def changed(self, key):
- """Return True if the current value for this key is different from
- the previous value.
-
- """
- if self._prev_dict is None:
- return True
- return self.previous(key) != self.get(key)
-
- def previous(self, key):
- """Return previous value for this key, or None if there
- is no previous value.
-
- """
- if self._prev_dict:
- return self._prev_dict.get(key)
- return None
-
- def save(self):
- """Save this config to disk.
-
- If the charm is using the :mod:`Services Framework <services.base>`
- or :meth:'@hook <Hooks.hook>' decorator, this
- is called automatically at the end of successful hook execution.
- Otherwise, it should be called directly by user code.
-
- To disable automatic saves, set ``implicit_save=False`` on this
- instance.
-
- """
- if self._prev_dict:
- for k, v in six.iteritems(self._prev_dict):
- if k not in self:
- self[k] = v
- with open(self.path, 'w') as f:
- json.dump(self, f)
-
-
-@cached
-def config(scope=None):
- """Juju charm configuration"""
- config_cmd_line = ['config-get']
- if scope is not None:
- config_cmd_line.append(scope)
- config_cmd_line.append('--format=json')
- try:
- config_data = json.loads(
- subprocess.check_output(config_cmd_line).decode('UTF-8'))
- if scope is not None:
- return config_data
- return Config(config_data)
- except ValueError:
- return None
-
-
-@cached
-def relation_get(attribute=None, unit=None, rid=None):
- """Get relation information"""
- _args = ['relation-get', '--format=json']
- if rid:
- _args.append('-r')
- _args.append(rid)
- _args.append(attribute or '-')
- if unit:
- _args.append(unit)
- try:
- return json.loads(subprocess.check_output(_args).decode('UTF-8'))
- except ValueError:
- return None
- except CalledProcessError as e:
- if e.returncode == 2:
- return None
- raise
-
-
-def relation_set(relation_id=None, relation_settings=None, **kwargs):
- """Set relation information for the current unit"""
- relation_settings = relation_settings if relation_settings else {}
- relation_cmd_line = ['relation-set']
- accepts_file = "--file" in subprocess.check_output(
- relation_cmd_line + ["--help"], universal_newlines=True)
- if relation_id is not None:
- relation_cmd_line.extend(('-r', relation_id))
- settings = relation_settings.copy()
- settings.update(kwargs)
- for key, value in settings.items():
- # Force value to be a string: it always should, but some call
- # sites pass in things like dicts or numbers.
- if value is not None:
- settings[key] = "{}".format(value)
- if accepts_file:
- # --file was introduced in Juju 1.23.2. Use it by default if
- # available, since otherwise we'll break if the relation data is
- # too big. Ideally we should tell relation-set to read the data from
- # stdin, but that feature is broken in 1.23.2: Bug #1454678.
- with tempfile.NamedTemporaryFile(delete=False) as settings_file:
- settings_file.write(yaml.safe_dump(settings).encode("utf-8"))
- subprocess.check_call(
- relation_cmd_line + ["--file", settings_file.name])
- os.remove(settings_file.name)
- else:
- for key, value in settings.items():
- if value is None:
- relation_cmd_line.append('{}='.format(key))
- else:
- relation_cmd_line.append('{}={}'.format(key, value))
- subprocess.check_call(relation_cmd_line)
- # Flush cache of any relation-gets for local unit
- flush(local_unit())
-
-
-def relation_clear(r_id=None):
- ''' Clears any relation data already set on relation r_id '''
- settings = relation_get(rid=r_id,
- unit=local_unit())
- for setting in settings:
- if setting not in ['public-address', 'private-address']:
- settings[setting] = None
- relation_set(relation_id=r_id,
- **settings)
-
-
-@cached
-def relation_ids(reltype=None):
- """A list of relation_ids"""
- reltype = reltype or relation_type()
- relid_cmd_line = ['relation-ids', '--format=json']
- if reltype is not None:
- relid_cmd_line.append(reltype)
- return json.loads(
- subprocess.check_output(relid_cmd_line).decode('UTF-8')) or []
- return []
-
-
-@cached
-def related_units(relid=None):
- """A list of related units"""
- relid = relid or relation_id()
- units_cmd_line = ['relation-list', '--format=json']
- if relid is not None:
- units_cmd_line.extend(('-r', relid))
- return json.loads(
- subprocess.check_output(units_cmd_line).decode('UTF-8')) or []
-
-
-@cached
-def relation_for_unit(unit=None, rid=None):
- """Get the json represenation of a unit's relation"""
- unit = unit or remote_unit()
- relation = relation_get(unit=unit, rid=rid)
- for key in relation:
- if key.endswith('-list'):
- relation[key] = relation[key].split()
- relation['__unit__'] = unit
- return relation
-
-
-@cached
-def relations_for_id(relid=None):
- """Get relations of a specific relation ID"""
- relation_data = []
- relid = relid or relation_ids()
- for unit in related_units(relid):
- unit_data = relation_for_unit(unit, relid)
- unit_data['__relid__'] = relid
- relation_data.append(unit_data)
- return relation_data
-
-
-@cached
-def relations_of_type(reltype=None):
- """Get relations of a specific type"""
- relation_data = []
- reltype = reltype or relation_type()
- for relid in relation_ids(reltype):
- for relation in relations_for_id(relid):
- relation['__relid__'] = relid
- relation_data.append(relation)
- return relation_data
-
-
-@cached
-def metadata():
- """Get the current charm metadata.yaml contents as a python object"""
- with open(os.path.join(charm_dir(), 'metadata.yaml')) as md:
- return yaml.safe_load(md)
-
-
-@cached
-def relation_types():
- """Get a list of relation types supported by this charm"""
- rel_types = []
- md = metadata()
- for key in ('provides', 'requires', 'peers'):
- section = md.get(key)
- if section:
- rel_types.extend(section.keys())
- return rel_types
-
-
-@cached
-def charm_name():
- """Get the name of the current charm as is specified on metadata.yaml"""
- return metadata().get('name')
-
-
-@cached
-def relations():
- """Get a nested dictionary of relation data for all related units"""
- rels = {}
- for reltype in relation_types():
- relids = {}
- for relid in relation_ids(reltype):
- units = {local_unit(): relation_get(unit=local_unit(), rid=relid)}
- for unit in related_units(relid):
- reldata = relation_get(unit=unit, rid=relid)
- units[unit] = reldata
- relids[relid] = units
- rels[reltype] = relids
- return rels
-
-
-@cached
-def is_relation_made(relation, keys='private-address'):
- '''
- Determine whether a relation is established by checking for
- presence of key(s). If a list of keys is provided, they
- must all be present for the relation to be identified as made
- '''
- if isinstance(keys, str):
- keys = [keys]
- for r_id in relation_ids(relation):
- for unit in related_units(r_id):
- context = {}
- for k in keys:
- context[k] = relation_get(k, rid=r_id,
- unit=unit)
- if None not in context.values():
- return True
- return False
-
-
-def open_port(port, protocol="TCP"):
- """Open a service network port"""
- _args = ['open-port']
- _args.append('{}/{}'.format(port, protocol))
- subprocess.check_call(_args)
-
-
-def close_port(port, protocol="TCP"):
- """Close a service network port"""
- _args = ['close-port']
- _args.append('{}/{}'.format(port, protocol))
- subprocess.check_call(_args)
-
-
-@cached
-def unit_get(attribute):
- """Get the unit ID for the remote unit"""
- _args = ['unit-get', '--format=json', attribute]
- try:
- return json.loads(subprocess.check_output(_args).decode('UTF-8'))
- except ValueError:
- return None
-
-
-def unit_public_ip():
- """Get this unit's public IP address"""
- return unit_get('public-address')
-
-
-def unit_private_ip():
- """Get this unit's private IP address"""
- return unit_get('private-address')
-
-
-class UnregisteredHookError(Exception):
- """Raised when an undefined hook is called"""
- pass
-
-
-class Hooks(object):
- """A convenient handler for hook functions.
-
- Example::
-
- hooks = Hooks()
-
- # register a hook, taking its name from the function name
- @hooks.hook()
- def install():
- pass # your code here
-
- # register a hook, providing a custom hook name
- @hooks.hook("config-changed")
- def config_changed():
- pass # your code here
-
- if __name__ == "__main__":
- # execute a hook based on the name the program is called by
- hooks.execute(sys.argv)
- """
-
- def __init__(self, config_save=True):
- super(Hooks, self).__init__()
- self._hooks = {}
- self._config_save = config_save
-
- def register(self, name, function):
- """Register a hook"""
- self._hooks[name] = function
-
- def execute(self, args):
- """Execute a registered hook based on args[0]"""
- hook_name = os.path.basename(args[0])
- if hook_name in self._hooks:
- self._hooks[hook_name]()
- if self._config_save:
- cfg = config()
- if cfg.implicit_save:
- cfg.save()
- else:
- raise UnregisteredHookError(hook_name)
-
- def hook(self, *hook_names):
- """Decorator, registering them as hooks"""
- def wrapper(decorated):
- for hook_name in hook_names:
- self.register(hook_name, decorated)
- else:
- self.register(decorated.__name__, decorated)
- if '_' in decorated.__name__:
- self.register(
- decorated.__name__.replace('_', '-'), decorated)
- return decorated
- return wrapper
-
-
-def charm_dir():
- """Return the root directory of the current charm"""
- return os.environ.get('CHARM_DIR')
-
-
-@cached
-def action_get(key=None):
- """Gets the value of an action parameter, or all key/value param pairs"""
- cmd = ['action-get']
- if key is not None:
- cmd.append(key)
- cmd.append('--format=json')
- action_data = json.loads(subprocess.check_output(cmd).decode('UTF-8'))
- return action_data
-
-
-def action_set(values):
- """Sets the values to be returned after the action finishes"""
- cmd = ['action-set']
- for k, v in list(values.items()):
- cmd.append('{}={}'.format(k, v))
- subprocess.check_call(cmd)
-
-
-def action_fail(message):
- """Sets the action status to failed and sets the error message.
-
- The results set by action_set are preserved."""
- subprocess.check_call(['action-fail', message])
-
-
-def status_set(workload_state, message):
- """Set the workload state with a message
-
- Use status-set to set the workload state with a message which is visible
- to the user via juju status. If the status-set command is not found then
- assume this is juju < 1.23 and juju-log the message unstead.
-
- workload_state -- valid juju workload state.
- message -- status update message
- """
- valid_states = ['maintenance', 'blocked', 'waiting', 'active']
- if workload_state not in valid_states:
- raise ValueError(
- '{!r} is not a valid workload state'.format(workload_state)
- )
- cmd = ['status-set', workload_state, message]
- try:
- ret = subprocess.call(cmd)
- if ret == 0:
- return
- except OSError as e:
- if e.errno != errno.ENOENT:
- raise
- log_message = 'status-set failed: {} {}'.format(workload_state,
- message)
- log(log_message, level='INFO')
-
-
-def status_get():
- """Retrieve the previously set juju workload state
-
- If the status-set command is not found then assume this is juju < 1.23 and
- return 'unknown'
- """
- cmd = ['status-get']
- try:
- raw_status = subprocess.check_output(cmd, universal_newlines=True)
- status = raw_status.rstrip()
- return status
- except OSError as e:
- if e.errno == errno.ENOENT:
- return 'unknown'
- else:
- raise
-
-
-def translate_exc(from_exc, to_exc):
- def inner_translate_exc1(f):
- def inner_translate_exc2(*args, **kwargs):
- try:
- return f(*args, **kwargs)
- except from_exc:
- raise to_exc
-
- return inner_translate_exc2
-
- return inner_translate_exc1
-
-
-@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
-def is_leader():
- """Does the current unit hold the juju leadership
-
- Uses juju to determine whether the current unit is the leader of its peers
- """
- cmd = ['is-leader', '--format=json']
- return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
-
-
-@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
-def leader_get(attribute=None):
- """Juju leader get value(s)"""
- cmd = ['leader-get', '--format=json'] + [attribute or '-']
- return json.loads(subprocess.check_output(cmd).decode('UTF-8'))
-
-
-@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
-def leader_set(settings=None, **kwargs):
- """Juju leader set value(s)"""
- log("Juju leader-set '%s'" % (settings), level=DEBUG)
- cmd = ['leader-set']
- settings = settings or {}
- settings.update(kwargs)
- for k, v in settings.iteritems():
- if v is None:
- cmd.append('{}='.format(k))
- else:
- cmd.append('{}={}'.format(k, v))
- subprocess.check_call(cmd)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/host.py b/charms/trusty/keepalived/hooks/charmhelpers/core/host.py
deleted file mode 100644
index 901a4cf..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/host.py
+++ /dev/null
@@ -1,468 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-"""Tools for working with the host system"""
-# Copyright 2012 Canonical Ltd.
-#
-# Authors:
-# Nick Moffitt <nick.moffitt@canonical.com>
-# Matthew Wedgwood <matthew.wedgwood@canonical.com>
-
-import os
-import re
-import pwd
-import glob
-import grp
-import random
-import string
-import subprocess
-import hashlib
-from contextlib import contextmanager
-from collections import OrderedDict
-
-import six
-
-from .hookenv import log
-from .fstab import Fstab
-
-
-def service_start(service_name):
- """Start a system service"""
- return service('start', service_name)
-
-
-def service_stop(service_name):
- """Stop a system service"""
- return service('stop', service_name)
-
-
-def service_restart(service_name):
- """Restart a system service"""
- return service('restart', service_name)
-
-
-def service_reload(service_name, restart_on_failure=False):
- """Reload a system service, optionally falling back to restart if
- reload fails"""
- service_result = service('reload', service_name)
- if not service_result and restart_on_failure:
- service_result = service('restart', service_name)
- return service_result
-
-
-def service(action, service_name):
- """Control a system service"""
- cmd = ['service', service_name, action]
- return subprocess.call(cmd) == 0
-
-
-def service_running(service):
- """Determine whether a system service is running"""
- try:
- output = subprocess.check_output(
- ['service', service, 'status'],
- stderr=subprocess.STDOUT).decode('UTF-8')
- except subprocess.CalledProcessError:
- return False
- else:
- if ("start/running" in output or "is running" in output):
- return True
- else:
- return False
-
-
-def service_available(service_name):
- """Determine whether a system service is available"""
- try:
- subprocess.check_output(
- ['service', service_name, 'status'],
- stderr=subprocess.STDOUT).decode('UTF-8')
- except subprocess.CalledProcessError as e:
- return b'unrecognized service' not in e.output
- else:
- return True
-
-
-def adduser(username, password=None, shell='/bin/bash', system_user=False):
- """Add a user to the system"""
- try:
- user_info = pwd.getpwnam(username)
- log('user {0} already exists!'.format(username))
- except KeyError:
- log('creating user {0}'.format(username))
- cmd = ['useradd']
- if system_user or password is None:
- cmd.append('--system')
- else:
- cmd.extend([
- '--create-home',
- '--shell', shell,
- '--password', password,
- ])
- cmd.append(username)
- subprocess.check_call(cmd)
- user_info = pwd.getpwnam(username)
- return user_info
-
-
-def add_group(group_name, system_group=False):
- """Add a group to the system"""
- try:
- group_info = grp.getgrnam(group_name)
- log('group {0} already exists!'.format(group_name))
- except KeyError:
- log('creating group {0}'.format(group_name))
- cmd = ['addgroup']
- if system_group:
- cmd.append('--system')
- else:
- cmd.extend([
- '--group',
- ])
- cmd.append(group_name)
- subprocess.check_call(cmd)
- group_info = grp.getgrnam(group_name)
- return group_info
-
-
-def add_user_to_group(username, group):
- """Add a user to a group"""
- cmd = [
- 'gpasswd', '-a',
- username,
- group
- ]
- log("Adding user {} to group {}".format(username, group))
- subprocess.check_call(cmd)
-
-
-def rsync(from_path, to_path, flags='-r', options=None):
- """Replicate the contents of a path"""
- options = options or ['--delete', '--executability']
- cmd = ['/usr/bin/rsync', flags]
- cmd.extend(options)
- cmd.append(from_path)
- cmd.append(to_path)
- log(" ".join(cmd))
- return subprocess.check_output(cmd).decode('UTF-8').strip()
-
-
-def symlink(source, destination):
- """Create a symbolic link"""
- log("Symlinking {} as {}".format(source, destination))
- cmd = [
- 'ln',
- '-sf',
- source,
- destination,
- ]
- subprocess.check_call(cmd)
-
-
-def mkdir(path, owner='root', group='root', perms=0o555, force=False):
- """Create a directory"""
- log("Making dir {} {}:{} {:o}".format(path, owner, group,
- perms))
- uid = pwd.getpwnam(owner).pw_uid
- gid = grp.getgrnam(group).gr_gid
- realpath = os.path.abspath(path)
- path_exists = os.path.exists(realpath)
- if path_exists and force:
- if not os.path.isdir(realpath):
- log("Removing non-directory file {} prior to mkdir()".format(path))
- os.unlink(realpath)
- os.makedirs(realpath, perms)
- elif not path_exists:
- os.makedirs(realpath, perms)
- os.chown(realpath, uid, gid)
- os.chmod(realpath, perms)
-
-
-def write_file(path, content, owner='root', group='root', perms=0o444):
- """Create or overwrite a file with the contents of a byte string."""
- log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
- uid = pwd.getpwnam(owner).pw_uid
- gid = grp.getgrnam(group).gr_gid
- with open(path, 'wb') as target:
- os.fchown(target.fileno(), uid, gid)
- os.fchmod(target.fileno(), perms)
- target.write(content)
-
-
-def fstab_remove(mp):
- """Remove the given mountpoint entry from /etc/fstab
- """
- return Fstab.remove_by_mountpoint(mp)
-
-
-def fstab_add(dev, mp, fs, options=None):
- """Adds the given device entry to the /etc/fstab file
- """
- return Fstab.add(dev, mp, fs, options=options)
-
-
-def mount(device, mountpoint, options=None, persist=False, filesystem="ext3"):
- """Mount a filesystem at a particular mountpoint"""
- cmd_args = ['mount']
- if options is not None:
- cmd_args.extend(['-o', options])
- cmd_args.extend([device, mountpoint])
- try:
- subprocess.check_output(cmd_args)
- except subprocess.CalledProcessError as e:
- log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
- return False
-
- if persist:
- return fstab_add(device, mountpoint, filesystem, options=options)
- return True
-
-
-def umount(mountpoint, persist=False):
- """Unmount a filesystem"""
- cmd_args = ['umount', mountpoint]
- try:
- subprocess.check_output(cmd_args)
- except subprocess.CalledProcessError as e:
- log('Error unmounting {}\n{}'.format(mountpoint, e.output))
- return False
-
- if persist:
- return fstab_remove(mountpoint)
- return True
-
-
-def mounts():
- """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
- with open('/proc/mounts') as f:
- # [['/mount/point','/dev/path'],[...]]
- system_mounts = [m[1::-1] for m in [l.strip().split()
- for l in f.readlines()]]
- return system_mounts
-
-
-def file_hash(path, hash_type='md5'):
- """
- Generate a hash checksum of the contents of 'path' or None if not found.
-
- :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
- such as md5, sha1, sha256, sha512, etc.
- """
- if os.path.exists(path):
- h = getattr(hashlib, hash_type)()
- with open(path, 'rb') as source:
- h.update(source.read())
- return h.hexdigest()
- else:
- return None
-
-
-def path_hash(path):
- """
- Generate a hash checksum of all files matching 'path'. Standard wildcards
- like '*' and '?' are supported, see documentation for the 'glob' module for
- more information.
-
- :return: dict: A { filename: hash } dictionary for all matched files.
- Empty if none found.
- """
- return {
- filename: file_hash(filename)
- for filename in glob.iglob(path)
- }
-
-
-def check_hash(path, checksum, hash_type='md5'):
- """
- Validate a file using a cryptographic checksum.
-
- :param str checksum: Value of the checksum used to validate the file.
- :param str hash_type: Hash algorithm used to generate `checksum`.
- Can be any hash alrgorithm supported by :mod:`hashlib`,
- such as md5, sha1, sha256, sha512, etc.
- :raises ChecksumError: If the file fails the checksum
-
- """
- actual_checksum = file_hash(path, hash_type)
- if checksum != actual_checksum:
- raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
-
-
-class ChecksumError(ValueError):
- pass
-
-
-def restart_on_change(restart_map, stopstart=False):
- """Restart services based on configuration files changing
-
- This function is used a decorator, for example::
-
- @restart_on_change({
- '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
- '/etc/apache/sites-enabled/*': [ 'apache2' ]
- })
- def config_changed():
- pass # your code here
-
- In this example, the cinder-api and cinder-volume services
- would be restarted if /etc/ceph/ceph.conf is changed by the
- ceph_client_changed function. The apache2 service would be
- restarted if any file matching the pattern got changed, created
- or removed. Standard wildcards are supported, see documentation
- for the 'glob' module for more information.
- """
- def wrap(f):
- def wrapped_f(*args, **kwargs):
- checksums = {path: path_hash(path) for path in restart_map}
- f(*args, **kwargs)
- restarts = []
- for path in restart_map:
- if path_hash(path) != checksums[path]:
- restarts += restart_map[path]
- services_list = list(OrderedDict.fromkeys(restarts))
- if not stopstart:
- for service_name in services_list:
- service('restart', service_name)
- else:
- for action in ['stop', 'start']:
- for service_name in services_list:
- service(action, service_name)
- return wrapped_f
- return wrap
-
-
-def lsb_release():
- """Return /etc/lsb-release in a dict"""
- d = {}
- with open('/etc/lsb-release', 'r') as lsb:
- for l in lsb:
- k, v = l.split('=')
- d[k.strip()] = v.strip()
- return d
-
-
-def pwgen(length=None):
- """Generate a random pasword."""
- if length is None:
- # A random length is ok to use a weak PRNG
- length = random.choice(range(35, 45))
- alphanumeric_chars = [
- l for l in (string.ascii_letters + string.digits)
- if l not in 'l0QD1vAEIOUaeiou']
- # Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
- # actual password
- random_generator = random.SystemRandom()
- random_chars = [
- random_generator.choice(alphanumeric_chars) for _ in range(length)]
- return(''.join(random_chars))
-
-
-def list_nics(nic_type):
- '''Return a list of nics of given type(s)'''
- if isinstance(nic_type, six.string_types):
- int_types = [nic_type]
- else:
- int_types = nic_type
- interfaces = []
- for int_type in int_types:
- cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
- ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
- ip_output = (line for line in ip_output if line)
- for line in ip_output:
- if line.split()[1].startswith(int_type):
- matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
- if matched:
- interface = matched.groups()[0]
- else:
- interface = line.split()[1].replace(":", "")
- interfaces.append(interface)
-
- return interfaces
-
-
-def set_nic_mtu(nic, mtu):
- '''Set MTU on a network interface'''
- cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
- subprocess.check_call(cmd)
-
-
-def get_nic_mtu(nic):
- cmd = ['ip', 'addr', 'show', nic]
- ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
- mtu = ""
- for line in ip_output:
- words = line.split()
- if 'mtu' in words:
- mtu = words[words.index("mtu") + 1]
- return mtu
-
-
-def get_nic_hwaddr(nic):
- cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
- ip_output = subprocess.check_output(cmd).decode('UTF-8')
- hwaddr = ""
- words = ip_output.split()
- if 'link/ether' in words:
- hwaddr = words[words.index('link/ether') + 1]
- return hwaddr
-
-
-def cmp_pkgrevno(package, revno, pkgcache=None):
- '''Compare supplied revno with the revno of the installed package
-
- * 1 => Installed revno is greater than supplied arg
- * 0 => Installed revno is the same as supplied arg
- * -1 => Installed revno is less than supplied arg
-
- This function imports apt_cache function from charmhelpers.fetch if
- the pkgcache argument is None. Be sure to add charmhelpers.fetch if
- you call this function, or pass an apt_pkg.Cache() instance.
- '''
- import apt_pkg
- if not pkgcache:
- from charmhelpers.fetch import apt_cache
- pkgcache = apt_cache()
- pkg = pkgcache[package]
- return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
-
-
-@contextmanager
-def chdir(d):
- cur = os.getcwd()
- try:
- yield os.chdir(d)
- finally:
- os.chdir(cur)
-
-
-def chownr(path, owner, group, follow_links=True):
- uid = pwd.getpwnam(owner).pw_uid
- gid = grp.getgrnam(group).gr_gid
- if follow_links:
- chown = os.chown
- else:
- chown = os.lchown
-
- for root, dirs, files in os.walk(path):
- for name in dirs + files:
- full = os.path.join(root, name)
- broken_symlink = os.path.lexists(full) and not os.path.exists(full)
- if not broken_symlink:
- chown(full, uid, gid)
-
-
-def lchownr(path, owner, group):
- chownr(path, owner, group, follow_links=False)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/services/__init__.py b/charms/trusty/keepalived/hooks/charmhelpers/core/services/__init__.py
deleted file mode 100644
index 0928158..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/services/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-from .base import * # NOQA
-from .helpers import * # NOQA
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/services/base.py b/charms/trusty/keepalived/hooks/charmhelpers/core/services/base.py
deleted file mode 100644
index 98d344e..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/services/base.py
+++ /dev/null
@@ -1,350 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import json
-from inspect import getargspec
-from collections import Iterable, OrderedDict
-
-from charmhelpers.core import host
-from charmhelpers.core import hookenv
-
-
-__all__ = ['ServiceManager', 'ManagerCallback',
- 'PortManagerCallback', 'open_ports', 'close_ports', 'manage_ports',
- 'service_restart', 'service_stop']
-
-
-class ServiceManager(object):
- def __init__(self, services=None):
- """
- Register a list of services, given their definitions.
-
- Service definitions are dicts in the following formats (all keys except
- 'service' are optional)::
-
- {
- "service": <service name>,
- "required_data": <list of required data contexts>,
- "provided_data": <list of provided data contexts>,
- "data_ready": <one or more callbacks>,
- "data_lost": <one or more callbacks>,
- "start": <one or more callbacks>,
- "stop": <one or more callbacks>,
- "ports": <list of ports to manage>,
- }
-
- The 'required_data' list should contain dicts of required data (or
- dependency managers that act like dicts and know how to collect the data).
- Only when all items in the 'required_data' list are populated are the list
- of 'data_ready' and 'start' callbacks executed. See `is_ready()` for more
- information.
-
- The 'provided_data' list should contain relation data providers, most likely
- a subclass of :class:`charmhelpers.core.services.helpers.RelationContext`,
- that will indicate a set of data to set on a given relation.
-
- The 'data_ready' value should be either a single callback, or a list of
- callbacks, to be called when all items in 'required_data' pass `is_ready()`.
- Each callback will be called with the service name as the only parameter.
- After all of the 'data_ready' callbacks are called, the 'start' callbacks
- are fired.
-
- The 'data_lost' value should be either a single callback, or a list of
- callbacks, to be called when a 'required_data' item no longer passes
- `is_ready()`. Each callback will be called with the service name as the
- only parameter. After all of the 'data_lost' callbacks are called,
- the 'stop' callbacks are fired.
-
- The 'start' value should be either a single callback, or a list of
- callbacks, to be called when starting the service, after the 'data_ready'
- callbacks are complete. Each callback will be called with the service
- name as the only parameter. This defaults to
- `[host.service_start, services.open_ports]`.
-
- The 'stop' value should be either a single callback, or a list of
- callbacks, to be called when stopping the service. If the service is
- being stopped because it no longer has all of its 'required_data', this
- will be called after all of the 'data_lost' callbacks are complete.
- Each callback will be called with the service name as the only parameter.
- This defaults to `[services.close_ports, host.service_stop]`.
-
- The 'ports' value should be a list of ports to manage. The default
- 'start' handler will open the ports after the service is started,
- and the default 'stop' handler will close the ports prior to stopping
- the service.
-
-
- Examples:
-
- The following registers an Upstart service called bingod that depends on
- a mongodb relation and which runs a custom `db_migrate` function prior to
- restarting the service, and a Runit service called spadesd::
-
- manager = services.ServiceManager([
- {
- 'service': 'bingod',
- 'ports': [80, 443],
- 'required_data': [MongoRelation(), config(), {'my': 'data'}],
- 'data_ready': [
- services.template(source='bingod.conf'),
- services.template(source='bingod.ini',
- target='/etc/bingod.ini',
- owner='bingo', perms=0400),
- ],
- },
- {
- 'service': 'spadesd',
- 'data_ready': services.template(source='spadesd_run.j2',
- target='/etc/sv/spadesd/run',
- perms=0555),
- 'start': runit_start,
- 'stop': runit_stop,
- },
- ])
- manager.manage()
- """
- self._ready_file = os.path.join(hookenv.charm_dir(), 'READY-SERVICES.json')
- self._ready = None
- self.services = OrderedDict()
- for service in services or []:
- service_name = service['service']
- self.services[service_name] = service
-
- def manage(self):
- """
- Handle the current hook by doing The Right Thing with the registered services.
- """
- hook_name = hookenv.hook_name()
- if hook_name == 'stop':
- self.stop_services()
- else:
- self.reconfigure_services()
- self.provide_data()
- cfg = hookenv.config()
- if cfg.implicit_save:
- cfg.save()
-
- def provide_data(self):
- """
- Set the relation data for each provider in the ``provided_data`` list.
-
- A provider must have a `name` attribute, which indicates which relation
- to set data on, and a `provide_data()` method, which returns a dict of
- data to set.
-
- The `provide_data()` method can optionally accept two parameters:
-
- * ``remote_service`` The name of the remote service that the data will
- be provided to. The `provide_data()` method will be called once
- for each connected service (not unit). This allows the method to
- tailor its data to the given service.
- * ``service_ready`` Whether or not the service definition had all of
- its requirements met, and thus the ``data_ready`` callbacks run.
-
- Note that the ``provided_data`` methods are now called **after** the
- ``data_ready`` callbacks are run. This gives the ``data_ready`` callbacks
- a chance to generate any data necessary for the providing to the remote
- services.
- """
- for service_name, service in self.services.items():
- service_ready = self.is_ready(service_name)
- for provider in service.get('provided_data', []):
- for relid in hookenv.relation_ids(provider.name):
- units = hookenv.related_units(relid)
- if not units:
- continue
- remote_service = units[0].split('/')[0]
- argspec = getargspec(provider.provide_data)
- if len(argspec.args) > 1:
- data = provider.provide_data(remote_service, service_ready)
- else:
- data = provider.provide_data()
- if data:
- hookenv.relation_set(relid, data)
-
- def reconfigure_services(self, *service_names):
- """
- Update all files for one or more registered services, and,
- if ready, optionally restart them.
-
- If no service names are given, reconfigures all registered services.
- """
- for service_name in service_names or self.services.keys():
- if self.is_ready(service_name):
- self.fire_event('data_ready', service_name)
- self.fire_event('start', service_name, default=[
- service_restart,
- manage_ports])
- self.save_ready(service_name)
- else:
- if self.was_ready(service_name):
- self.fire_event('data_lost', service_name)
- self.fire_event('stop', service_name, default=[
- manage_ports,
- service_stop])
- self.save_lost(service_name)
-
- def stop_services(self, *service_names):
- """
- Stop one or more registered services, by name.
-
- If no service names are given, stops all registered services.
- """
- for service_name in service_names or self.services.keys():
- self.fire_event('stop', service_name, default=[
- manage_ports,
- service_stop])
-
- def get_service(self, service_name):
- """
- Given the name of a registered service, return its service definition.
- """
- service = self.services.get(service_name)
- if not service:
- raise KeyError('Service not registered: %s' % service_name)
- return service
-
- def fire_event(self, event_name, service_name, default=None):
- """
- Fire a data_ready, data_lost, start, or stop event on a given service.
- """
- service = self.get_service(service_name)
- callbacks = service.get(event_name, default)
- if not callbacks:
- return
- if not isinstance(callbacks, Iterable):
- callbacks = [callbacks]
- for callback in callbacks:
- if isinstance(callback, ManagerCallback):
- callback(self, service_name, event_name)
- else:
- callback(service_name)
-
- def is_ready(self, service_name):
- """
- Determine if a registered service is ready, by checking its 'required_data'.
-
- A 'required_data' item can be any mapping type, and is considered ready
- if `bool(item)` evaluates as True.
- """
- service = self.get_service(service_name)
- reqs = service.get('required_data', [])
- return all(bool(req) for req in reqs)
-
- def _load_ready_file(self):
- if self._ready is not None:
- return
- if os.path.exists(self._ready_file):
- with open(self._ready_file) as fp:
- self._ready = set(json.load(fp))
- else:
- self._ready = set()
-
- def _save_ready_file(self):
- if self._ready is None:
- return
- with open(self._ready_file, 'w') as fp:
- json.dump(list(self._ready), fp)
-
- def save_ready(self, service_name):
- """
- Save an indicator that the given service is now data_ready.
- """
- self._load_ready_file()
- self._ready.add(service_name)
- self._save_ready_file()
-
- def save_lost(self, service_name):
- """
- Save an indicator that the given service is no longer data_ready.
- """
- self._load_ready_file()
- self._ready.discard(service_name)
- self._save_ready_file()
-
- def was_ready(self, service_name):
- """
- Determine if the given service was previously data_ready.
- """
- self._load_ready_file()
- return service_name in self._ready
-
-
-class ManagerCallback(object):
- """
- Special case of a callback that takes the `ServiceManager` instance
- in addition to the service name.
-
- Subclasses should implement `__call__` which should accept three parameters:
-
- * `manager` The `ServiceManager` instance
- * `service_name` The name of the service it's being triggered for
- * `event_name` The name of the event that this callback is handling
- """
- def __call__(self, manager, service_name, event_name):
- raise NotImplementedError()
-
-
-class PortManagerCallback(ManagerCallback):
- """
- Callback class that will open or close ports, for use as either
- a start or stop action.
- """
- def __call__(self, manager, service_name, event_name):
- service = manager.get_service(service_name)
- new_ports = service.get('ports', [])
- port_file = os.path.join(hookenv.charm_dir(), '.{}.ports'.format(service_name))
- if os.path.exists(port_file):
- with open(port_file) as fp:
- old_ports = fp.read().split(',')
- for old_port in old_ports:
- if bool(old_port):
- old_port = int(old_port)
- if old_port not in new_ports:
- hookenv.close_port(old_port)
- with open(port_file, 'w') as fp:
- fp.write(','.join(str(port) for port in new_ports))
- for port in new_ports:
- if event_name == 'start':
- hookenv.open_port(port)
- elif event_name == 'stop':
- hookenv.close_port(port)
-
-
-def service_stop(service_name):
- """
- Wrapper around host.service_stop to prevent spurious "unknown service"
- messages in the logs.
- """
- if host.service_running(service_name):
- host.service_stop(service_name)
-
-
-def service_restart(service_name):
- """
- Wrapper around host.service_restart to prevent spurious "unknown service"
- messages in the logs.
- """
- if host.service_available(service_name):
- if host.service_running(service_name):
- host.service_restart(service_name)
- else:
- host.service_start(service_name)
-
-
-# Convenience aliases
-open_ports = close_ports = manage_ports = PortManagerCallback()
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/services/helpers.py b/charms/trusty/keepalived/hooks/charmhelpers/core/services/helpers.py
deleted file mode 100644
index 3eb5fb4..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/services/helpers.py
+++ /dev/null
@@ -1,267 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import yaml
-from charmhelpers.core import hookenv
-from charmhelpers.core import templating
-
-from charmhelpers.core.services.base import ManagerCallback
-
-
-__all__ = ['RelationContext', 'TemplateCallback',
- 'render_template', 'template']
-
-
-class RelationContext(dict):
- """
- Base class for a context generator that gets relation data from juju.
-
- Subclasses must provide the attributes `name`, which is the name of the
- interface of interest, `interface`, which is the type of the interface of
- interest, and `required_keys`, which is the set of keys required for the
- relation to be considered complete. The data for all interfaces matching
- the `name` attribute that are complete will used to populate the dictionary
- values (see `get_data`, below).
-
- The generated context will be namespaced under the relation :attr:`name`,
- to prevent potential naming conflicts.
-
- :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
- :param list additional_required_keys: Extend the list of :attr:`required_keys`
- """
- name = None
- interface = None
-
- def __init__(self, name=None, additional_required_keys=None):
- if not hasattr(self, 'required_keys'):
- self.required_keys = []
-
- if name is not None:
- self.name = name
- if additional_required_keys:
- self.required_keys.extend(additional_required_keys)
- self.get_data()
-
- def __bool__(self):
- """
- Returns True if all of the required_keys are available.
- """
- return self.is_ready()
-
- __nonzero__ = __bool__
-
- def __repr__(self):
- return super(RelationContext, self).__repr__()
-
- def is_ready(self):
- """
- Returns True if all of the `required_keys` are available from any units.
- """
- ready = len(self.get(self.name, [])) > 0
- if not ready:
- hookenv.log('Incomplete relation: {}'.format(self.__class__.__name__), hookenv.DEBUG)
- return ready
-
- def _is_ready(self, unit_data):
- """
- Helper method that tests a set of relation data and returns True if
- all of the `required_keys` are present.
- """
- return set(unit_data.keys()).issuperset(set(self.required_keys))
-
- def get_data(self):
- """
- Retrieve the relation data for each unit involved in a relation and,
- if complete, store it in a list under `self[self.name]`. This
- is automatically called when the RelationContext is instantiated.
-
- The units are sorted lexographically first by the service ID, then by
- the unit ID. Thus, if an interface has two other services, 'db:1'
- and 'db:2', with 'db:1' having two units, 'wordpress/0' and 'wordpress/1',
- and 'db:2' having one unit, 'mediawiki/0', all of which have a complete
- set of data, the relation data for the units will be stored in the
- order: 'wordpress/0', 'wordpress/1', 'mediawiki/0'.
-
- If you only care about a single unit on the relation, you can just
- access it as `{{ interface[0]['key'] }}`. However, if you can at all
- support multiple units on a relation, you should iterate over the list,
- like::
-
- {% for unit in interface -%}
- {{ unit['key'] }}{% if not loop.last %},{% endif %}
- {%- endfor %}
-
- Note that since all sets of relation data from all related services and
- units are in a single list, if you need to know which service or unit a
- set of data came from, you'll need to extend this class to preserve
- that information.
- """
- if not hookenv.relation_ids(self.name):
- return
-
- ns = self.setdefault(self.name, [])
- for rid in sorted(hookenv.relation_ids(self.name)):
- for unit in sorted(hookenv.related_units(rid)):
- reldata = hookenv.relation_get(rid=rid, unit=unit)
- if self._is_ready(reldata):
- ns.append(reldata)
-
- def provide_data(self):
- """
- Return data to be relation_set for this interface.
- """
- return {}
-
-
-class MysqlRelation(RelationContext):
- """
- Relation context for the `mysql` interface.
-
- :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
- :param list additional_required_keys: Extend the list of :attr:`required_keys`
- """
- name = 'db'
- interface = 'mysql'
-
- def __init__(self, *args, **kwargs):
- self.required_keys = ['host', 'user', 'password', 'database']
- RelationContext.__init__(self, *args, **kwargs)
-
-
-class HttpRelation(RelationContext):
- """
- Relation context for the `http` interface.
-
- :param str name: Override the relation :attr:`name`, since it can vary from charm to charm
- :param list additional_required_keys: Extend the list of :attr:`required_keys`
- """
- name = 'website'
- interface = 'http'
-
- def __init__(self, *args, **kwargs):
- self.required_keys = ['host', 'port']
- RelationContext.__init__(self, *args, **kwargs)
-
- def provide_data(self):
- return {
- 'host': hookenv.unit_get('private-address'),
- 'port': 80,
- }
-
-
-class RequiredConfig(dict):
- """
- Data context that loads config options with one or more mandatory options.
-
- Once the required options have been changed from their default values, all
- config options will be available, namespaced under `config` to prevent
- potential naming conflicts (for example, between a config option and a
- relation property).
-
- :param list *args: List of options that must be changed from their default values.
- """
-
- def __init__(self, *args):
- self.required_options = args
- self['config'] = hookenv.config()
- with open(os.path.join(hookenv.charm_dir(), 'config.yaml')) as fp:
- self.config = yaml.load(fp).get('options', {})
-
- def __bool__(self):
- for option in self.required_options:
- if option not in self['config']:
- return False
- current_value = self['config'][option]
- default_value = self.config[option].get('default')
- if current_value == default_value:
- return False
- if current_value in (None, '') and default_value in (None, ''):
- return False
- return True
-
- def __nonzero__(self):
- return self.__bool__()
-
-
-class StoredContext(dict):
- """
- A data context that always returns the data that it was first created with.
-
- This is useful to do a one-time generation of things like passwords, that
- will thereafter use the same value that was originally generated, instead
- of generating a new value each time it is run.
- """
- def __init__(self, file_name, config_data):
- """
- If the file exists, populate `self` with the data from the file.
- Otherwise, populate with the given data and persist it to the file.
- """
- if os.path.exists(file_name):
- self.update(self.read_context(file_name))
- else:
- self.store_context(file_name, config_data)
- self.update(config_data)
-
- def store_context(self, file_name, config_data):
- if not os.path.isabs(file_name):
- file_name = os.path.join(hookenv.charm_dir(), file_name)
- with open(file_name, 'w') as file_stream:
- os.fchmod(file_stream.fileno(), 0o600)
- yaml.dump(config_data, file_stream)
-
- def read_context(self, file_name):
- if not os.path.isabs(file_name):
- file_name = os.path.join(hookenv.charm_dir(), file_name)
- with open(file_name, 'r') as file_stream:
- data = yaml.load(file_stream)
- if not data:
- raise OSError("%s is empty" % file_name)
- return data
-
-
-class TemplateCallback(ManagerCallback):
- """
- Callback class that will render a Jinja2 template, for use as a ready
- action.
-
- :param str source: The template source file, relative to
- `$CHARM_DIR/templates`
-
- :param str target: The target to write the rendered template to
- :param str owner: The owner of the rendered file
- :param str group: The group of the rendered file
- :param int perms: The permissions of the rendered file
- """
- def __init__(self, source, target,
- owner='root', group='root', perms=0o444):
- self.source = source
- self.target = target
- self.owner = owner
- self.group = group
- self.perms = perms
-
- def __call__(self, manager, service_name, event_name):
- service = manager.get_service(service_name)
- context = {}
- for ctx in service.get('required_data', []):
- context.update(ctx)
- templating.render(self.source, self.target, context,
- self.owner, self.group, self.perms)
-
-
-# Convenience aliases for templates
-render_template = template = TemplateCallback
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/strutils.py b/charms/trusty/keepalived/hooks/charmhelpers/core/strutils.py
deleted file mode 100644
index a2a784a..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/strutils.py
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import six
-
-
-def bool_from_string(value):
- """Interpret string value as boolean.
-
- Returns True if value translates to True otherwise False.
- """
- if isinstance(value, six.string_types):
- value = six.text_type(value)
- else:
- msg = "Unable to interpret non-string value '%s' as boolean" % (value)
- raise ValueError(msg)
-
- value = value.strip().lower()
-
- if value in ['y', 'yes', 'true', 't', 'on']:
- return True
- elif value in ['n', 'no', 'false', 'f', 'off']:
- return False
-
- msg = "Unable to interpret string value '%s' as boolean" % (value)
- raise ValueError(msg)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/sysctl.py b/charms/trusty/keepalived/hooks/charmhelpers/core/sysctl.py
deleted file mode 100644
index 21cc8ab..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/sysctl.py
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import yaml
-
-from subprocess import check_call
-
-from charmhelpers.core.hookenv import (
- log,
- DEBUG,
- ERROR,
-)
-
-__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
-
-
-def create(sysctl_dict, sysctl_file):
- """Creates a sysctl.conf file from a YAML associative array
-
- :param sysctl_dict: a YAML-formatted string of sysctl options eg "{ 'kernel.max_pid': 1337 }"
- :type sysctl_dict: str
- :param sysctl_file: path to the sysctl file to be saved
- :type sysctl_file: str or unicode
- :returns: None
- """
- try:
- sysctl_dict_parsed = yaml.safe_load(sysctl_dict)
- except yaml.YAMLError:
- log("Error parsing YAML sysctl_dict: {}".format(sysctl_dict),
- level=ERROR)
- return
-
- with open(sysctl_file, "w") as fd:
- for key, value in sysctl_dict_parsed.items():
- fd.write("{}={}\n".format(key, value))
-
- log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed),
- level=DEBUG)
-
- check_call(["sysctl", "-p", sysctl_file])
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/templating.py b/charms/trusty/keepalived/hooks/charmhelpers/core/templating.py
deleted file mode 100644
index 4531999..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/templating.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from charmhelpers.core import host
-from charmhelpers.core import hookenv
-
-
-def render(source, target, context, owner='root', group='root',
- perms=0o444, templates_dir=None, encoding='UTF-8'):
- """
- Render a template.
-
- The `source` path, if not absolute, is relative to the `templates_dir`.
-
- The `target` path should be absolute.
-
- The context should be a dict containing the values to be replaced in the
- template.
-
- The `owner`, `group`, and `perms` options will be passed to `write_file`.
-
- If omitted, `templates_dir` defaults to the `templates` folder in the charm.
-
- Note: Using this requires python-jinja2; if it is not installed, calling
- this will attempt to use charmhelpers.fetch.apt_install to install it.
- """
- try:
- from jinja2 import FileSystemLoader, Environment, exceptions
- except ImportError:
- try:
- from charmhelpers.fetch import apt_install
- except ImportError:
- hookenv.log('Could not import jinja2, and could not import '
- 'charmhelpers.fetch to install it',
- level=hookenv.ERROR)
- raise
- apt_install('python-jinja2', fatal=True)
- from jinja2 import FileSystemLoader, Environment, exceptions
-
- if templates_dir is None:
- templates_dir = os.path.join(hookenv.charm_dir(), 'templates')
- loader = Environment(loader=FileSystemLoader(templates_dir))
- try:
- source = source
- template = loader.get_template(source)
- except exceptions.TemplateNotFound as e:
- hookenv.log('Could not load template %s from %s.' %
- (source, templates_dir),
- level=hookenv.ERROR)
- raise e
- content = template.render(context)
- host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
- host.write_file(target, content.encode(encoding), owner, group, perms)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/core/unitdata.py b/charms/trusty/keepalived/hooks/charmhelpers/core/unitdata.py
deleted file mode 100644
index 406a35c..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/core/unitdata.py
+++ /dev/null
@@ -1,477 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-#
-#
-# Authors:
-# Kapil Thangavelu <kapil.foss@gmail.com>
-#
-"""
-Intro
------
-
-A simple way to store state in units. This provides a key value
-storage with support for versioned, transactional operation,
-and can calculate deltas from previous values to simplify unit logic
-when processing changes.
-
-
-Hook Integration
-----------------
-
-There are several extant frameworks for hook execution, including
-
- - charmhelpers.core.hookenv.Hooks
- - charmhelpers.core.services.ServiceManager
-
-The storage classes are framework agnostic, one simple integration is
-via the HookData contextmanager. It will record the current hook
-execution environment (including relation data, config data, etc.),
-setup a transaction and allow easy access to the changes from
-previously seen values. One consequence of the integration is the
-reservation of particular keys ('rels', 'unit', 'env', 'config',
-'charm_revisions') for their respective values.
-
-Here's a fully worked integration example using hookenv.Hooks::
-
- from charmhelper.core import hookenv, unitdata
-
- hook_data = unitdata.HookData()
- db = unitdata.kv()
- hooks = hookenv.Hooks()
-
- @hooks.hook
- def config_changed():
- # Print all changes to configuration from previously seen
- # values.
- for changed, (prev, cur) in hook_data.conf.items():
- print('config changed', changed,
- 'previous value', prev,
- 'current value', cur)
-
- # Get some unit specific bookeeping
- if not db.get('pkg_key'):
- key = urllib.urlopen('https://example.com/pkg_key').read()
- db.set('pkg_key', key)
-
- # Directly access all charm config as a mapping.
- conf = db.getrange('config', True)
-
- # Directly access all relation data as a mapping
- rels = db.getrange('rels', True)
-
- if __name__ == '__main__':
- with hook_data():
- hook.execute()
-
-
-A more basic integration is via the hook_scope context manager which simply
-manages transaction scope (and records hook name, and timestamp)::
-
- >>> from unitdata import kv
- >>> db = kv()
- >>> with db.hook_scope('install'):
- ... # do work, in transactional scope.
- ... db.set('x', 1)
- >>> db.get('x')
- 1
-
-
-Usage
------
-
-Values are automatically json de/serialized to preserve basic typing
-and complex data struct capabilities (dicts, lists, ints, booleans, etc).
-
-Individual values can be manipulated via get/set::
-
- >>> kv.set('y', True)
- >>> kv.get('y')
- True
-
- # We can set complex values (dicts, lists) as a single key.
- >>> kv.set('config', {'a': 1, 'b': True'})
-
- # Also supports returning dictionaries as a record which
- # provides attribute access.
- >>> config = kv.get('config', record=True)
- >>> config.b
- True
-
-
-Groups of keys can be manipulated with update/getrange::
-
- >>> kv.update({'z': 1, 'y': 2}, prefix="gui.")
- >>> kv.getrange('gui.', strip=True)
- {'z': 1, 'y': 2}
-
-When updating values, its very helpful to understand which values
-have actually changed and how have they changed. The storage
-provides a delta method to provide for this::
-
- >>> data = {'debug': True, 'option': 2}
- >>> delta = kv.delta(data, 'config.')
- >>> delta.debug.previous
- None
- >>> delta.debug.current
- True
- >>> delta
- {'debug': (None, True), 'option': (None, 2)}
-
-Note the delta method does not persist the actual change, it needs to
-be explicitly saved via 'update' method::
-
- >>> kv.update(data, 'config.')
-
-Values modified in the context of a hook scope retain historical values
-associated to the hookname.
-
- >>> with db.hook_scope('config-changed'):
- ... db.set('x', 42)
- >>> db.gethistory('x')
- [(1, u'x', 1, u'install', u'2015-01-21T16:49:30.038372'),
- (2, u'x', 42, u'config-changed', u'2015-01-21T16:49:30.038786')]
-
-"""
-
-import collections
-import contextlib
-import datetime
-import json
-import os
-import pprint
-import sqlite3
-import sys
-
-__author__ = 'Kapil Thangavelu <kapil.foss@gmail.com>'
-
-
-class Storage(object):
- """Simple key value database for local unit state within charms.
-
- Modifications are automatically committed at hook exit. That's
- currently regardless of exit code.
-
- To support dicts, lists, integer, floats, and booleans values
- are automatically json encoded/decoded.
- """
- def __init__(self, path=None):
- self.db_path = path
- if path is None:
- self.db_path = os.path.join(
- os.environ.get('CHARM_DIR', ''), '.unit-state.db')
- self.conn = sqlite3.connect('%s' % self.db_path)
- self.cursor = self.conn.cursor()
- self.revision = None
- self._closed = False
- self._init()
-
- def close(self):
- if self._closed:
- return
- self.flush(False)
- self.cursor.close()
- self.conn.close()
- self._closed = True
-
- def _scoped_query(self, stmt, params=None):
- if params is None:
- params = []
- return stmt, params
-
- def get(self, key, default=None, record=False):
- self.cursor.execute(
- *self._scoped_query(
- 'select data from kv where key=?', [key]))
- result = self.cursor.fetchone()
- if not result:
- return default
- if record:
- return Record(json.loads(result[0]))
- return json.loads(result[0])
-
- def getrange(self, key_prefix, strip=False):
- stmt = "select key, data from kv where key like '%s%%'" % key_prefix
- self.cursor.execute(*self._scoped_query(stmt))
- result = self.cursor.fetchall()
-
- if not result:
- return None
- if not strip:
- key_prefix = ''
- return dict([
- (k[len(key_prefix):], json.loads(v)) for k, v in result])
-
- def update(self, mapping, prefix=""):
- for k, v in mapping.items():
- self.set("%s%s" % (prefix, k), v)
-
- def unset(self, key):
- self.cursor.execute('delete from kv where key=?', [key])
- if self.revision and self.cursor.rowcount:
- self.cursor.execute(
- 'insert into kv_revisions values (?, ?, ?)',
- [key, self.revision, json.dumps('DELETED')])
-
- def set(self, key, value):
- serialized = json.dumps(value)
-
- self.cursor.execute(
- 'select data from kv where key=?', [key])
- exists = self.cursor.fetchone()
-
- # Skip mutations to the same value
- if exists:
- if exists[0] == serialized:
- return value
-
- if not exists:
- self.cursor.execute(
- 'insert into kv (key, data) values (?, ?)',
- (key, serialized))
- else:
- self.cursor.execute('''
- update kv
- set data = ?
- where key = ?''', [serialized, key])
-
- # Save
- if not self.revision:
- return value
-
- self.cursor.execute(
- 'select 1 from kv_revisions where key=? and revision=?',
- [key, self.revision])
- exists = self.cursor.fetchone()
-
- if not exists:
- self.cursor.execute(
- '''insert into kv_revisions (
- revision, key, data) values (?, ?, ?)''',
- (self.revision, key, serialized))
- else:
- self.cursor.execute(
- '''
- update kv_revisions
- set data = ?
- where key = ?
- and revision = ?''',
- [serialized, key, self.revision])
-
- return value
-
- def delta(self, mapping, prefix):
- """
- return a delta containing values that have changed.
- """
- previous = self.getrange(prefix, strip=True)
- if not previous:
- pk = set()
- else:
- pk = set(previous.keys())
- ck = set(mapping.keys())
- delta = DeltaSet()
-
- # added
- for k in ck.difference(pk):
- delta[k] = Delta(None, mapping[k])
-
- # removed
- for k in pk.difference(ck):
- delta[k] = Delta(previous[k], None)
-
- # changed
- for k in pk.intersection(ck):
- c = mapping[k]
- p = previous[k]
- if c != p:
- delta[k] = Delta(p, c)
-
- return delta
-
- @contextlib.contextmanager
- def hook_scope(self, name=""):
- """Scope all future interactions to the current hook execution
- revision."""
- assert not self.revision
- self.cursor.execute(
- 'insert into hooks (hook, date) values (?, ?)',
- (name or sys.argv[0],
- datetime.datetime.utcnow().isoformat()))
- self.revision = self.cursor.lastrowid
- try:
- yield self.revision
- self.revision = None
- except:
- self.flush(False)
- self.revision = None
- raise
- else:
- self.flush()
-
- def flush(self, save=True):
- if save:
- self.conn.commit()
- elif self._closed:
- return
- else:
- self.conn.rollback()
-
- def _init(self):
- self.cursor.execute('''
- create table if not exists kv (
- key text,
- data text,
- primary key (key)
- )''')
- self.cursor.execute('''
- create table if not exists kv_revisions (
- key text,
- revision integer,
- data text,
- primary key (key, revision)
- )''')
- self.cursor.execute('''
- create table if not exists hooks (
- version integer primary key autoincrement,
- hook text,
- date text
- )''')
- self.conn.commit()
-
- def gethistory(self, key, deserialize=False):
- self.cursor.execute(
- '''
- select kv.revision, kv.key, kv.data, h.hook, h.date
- from kv_revisions kv,
- hooks h
- where kv.key=?
- and kv.revision = h.version
- ''', [key])
- if deserialize is False:
- return self.cursor.fetchall()
- return map(_parse_history, self.cursor.fetchall())
-
- def debug(self, fh=sys.stderr):
- self.cursor.execute('select * from kv')
- pprint.pprint(self.cursor.fetchall(), stream=fh)
- self.cursor.execute('select * from kv_revisions')
- pprint.pprint(self.cursor.fetchall(), stream=fh)
-
-
-def _parse_history(d):
- return (d[0], d[1], json.loads(d[2]), d[3],
- datetime.datetime.strptime(d[-1], "%Y-%m-%dT%H:%M:%S.%f"))
-
-
-class HookData(object):
- """Simple integration for existing hook exec frameworks.
-
- Records all unit information, and stores deltas for processing
- by the hook.
-
- Sample::
-
- from charmhelper.core import hookenv, unitdata
-
- changes = unitdata.HookData()
- db = unitdata.kv()
- hooks = hookenv.Hooks()
-
- @hooks.hook
- def config_changed():
- # View all changes to configuration
- for changed, (prev, cur) in changes.conf.items():
- print('config changed', changed,
- 'previous value', prev,
- 'current value', cur)
-
- # Get some unit specific bookeeping
- if not db.get('pkg_key'):
- key = urllib.urlopen('https://example.com/pkg_key').read()
- db.set('pkg_key', key)
-
- if __name__ == '__main__':
- with changes():
- hook.execute()
-
- """
- def __init__(self):
- self.kv = kv()
- self.conf = None
- self.rels = None
-
- @contextlib.contextmanager
- def __call__(self):
- from charmhelpers.core import hookenv
- hook_name = hookenv.hook_name()
-
- with self.kv.hook_scope(hook_name):
- self._record_charm_version(hookenv.charm_dir())
- delta_config, delta_relation = self._record_hook(hookenv)
- yield self.kv, delta_config, delta_relation
-
- def _record_charm_version(self, charm_dir):
- # Record revisions.. charm revisions are meaningless
- # to charm authors as they don't control the revision.
- # so logic dependnent on revision is not particularly
- # useful, however it is useful for debugging analysis.
- charm_rev = open(
- os.path.join(charm_dir, 'revision')).read().strip()
- charm_rev = charm_rev or '0'
- revs = self.kv.get('charm_revisions', [])
- if charm_rev not in revs:
- revs.append(charm_rev.strip() or '0')
- self.kv.set('charm_revisions', revs)
-
- def _record_hook(self, hookenv):
- data = hookenv.execution_environment()
- self.conf = conf_delta = self.kv.delta(data['conf'], 'config')
- self.rels = rels_delta = self.kv.delta(data['rels'], 'rels')
- self.kv.set('env', dict(data['env']))
- self.kv.set('unit', data['unit'])
- self.kv.set('relid', data.get('relid'))
- return conf_delta, rels_delta
-
-
-class Record(dict):
-
- __slots__ = ()
-
- def __getattr__(self, k):
- if k in self:
- return self[k]
- raise AttributeError(k)
-
-
-class DeltaSet(Record):
-
- __slots__ = ()
-
-
-Delta = collections.namedtuple('Delta', ['previous', 'current'])
-
-
-_KV = None
-
-
-def kv():
- global _KV
- if _KV is None:
- _KV = Storage()
- return _KV
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/fetch/__init__.py b/charms/trusty/keepalived/hooks/charmhelpers/fetch/__init__.py
deleted file mode 100644
index 9a1a251..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/fetch/__init__.py
+++ /dev/null
@@ -1,439 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import importlib
-from tempfile import NamedTemporaryFile
-import time
-from yaml import safe_load
-from charmhelpers.core.host import (
- lsb_release
-)
-import subprocess
-from charmhelpers.core.hookenv import (
- config,
- log,
-)
-import os
-
-import six
-if six.PY3:
- from urllib.parse import urlparse, urlunparse
-else:
- from urlparse import urlparse, urlunparse
-
-
-CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
-deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
-"""
-PROPOSED_POCKET = """# Proposed
-deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
-"""
-CLOUD_ARCHIVE_POCKETS = {
- # Folsom
- 'folsom': 'precise-updates/folsom',
- 'precise-folsom': 'precise-updates/folsom',
- 'precise-folsom/updates': 'precise-updates/folsom',
- 'precise-updates/folsom': 'precise-updates/folsom',
- 'folsom/proposed': 'precise-proposed/folsom',
- 'precise-folsom/proposed': 'precise-proposed/folsom',
- 'precise-proposed/folsom': 'precise-proposed/folsom',
- # Grizzly
- 'grizzly': 'precise-updates/grizzly',
- 'precise-grizzly': 'precise-updates/grizzly',
- 'precise-grizzly/updates': 'precise-updates/grizzly',
- 'precise-updates/grizzly': 'precise-updates/grizzly',
- 'grizzly/proposed': 'precise-proposed/grizzly',
- 'precise-grizzly/proposed': 'precise-proposed/grizzly',
- 'precise-proposed/grizzly': 'precise-proposed/grizzly',
- # Havana
- 'havana': 'precise-updates/havana',
- 'precise-havana': 'precise-updates/havana',
- 'precise-havana/updates': 'precise-updates/havana',
- 'precise-updates/havana': 'precise-updates/havana',
- 'havana/proposed': 'precise-proposed/havana',
- 'precise-havana/proposed': 'precise-proposed/havana',
- 'precise-proposed/havana': 'precise-proposed/havana',
- # Icehouse
- 'icehouse': 'precise-updates/icehouse',
- 'precise-icehouse': 'precise-updates/icehouse',
- 'precise-icehouse/updates': 'precise-updates/icehouse',
- 'precise-updates/icehouse': 'precise-updates/icehouse',
- 'icehouse/proposed': 'precise-proposed/icehouse',
- 'precise-icehouse/proposed': 'precise-proposed/icehouse',
- 'precise-proposed/icehouse': 'precise-proposed/icehouse',
- # Juno
- 'juno': 'trusty-updates/juno',
- 'trusty-juno': 'trusty-updates/juno',
- 'trusty-juno/updates': 'trusty-updates/juno',
- 'trusty-updates/juno': 'trusty-updates/juno',
- 'juno/proposed': 'trusty-proposed/juno',
- 'trusty-juno/proposed': 'trusty-proposed/juno',
- 'trusty-proposed/juno': 'trusty-proposed/juno',
- # Kilo
- 'kilo': 'trusty-updates/kilo',
- 'trusty-kilo': 'trusty-updates/kilo',
- 'trusty-kilo/updates': 'trusty-updates/kilo',
- 'trusty-updates/kilo': 'trusty-updates/kilo',
- 'kilo/proposed': 'trusty-proposed/kilo',
- 'trusty-kilo/proposed': 'trusty-proposed/kilo',
- 'trusty-proposed/kilo': 'trusty-proposed/kilo',
-}
-
-# The order of this list is very important. Handlers should be listed in from
-# least- to most-specific URL matching.
-FETCH_HANDLERS = (
- 'charmhelpers.fetch.archiveurl.ArchiveUrlFetchHandler',
- 'charmhelpers.fetch.bzrurl.BzrUrlFetchHandler',
- 'charmhelpers.fetch.giturl.GitUrlFetchHandler',
-)
-
-APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
-APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
-APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
-
-
-class SourceConfigError(Exception):
- pass
-
-
-class UnhandledSource(Exception):
- pass
-
-
-class AptLockError(Exception):
- pass
-
-
-class BaseFetchHandler(object):
-
- """Base class for FetchHandler implementations in fetch plugins"""
-
- def can_handle(self, source):
- """Returns True if the source can be handled. Otherwise returns
- a string explaining why it cannot"""
- return "Wrong source type"
-
- def install(self, source):
- """Try to download and unpack the source. Return the path to the
- unpacked files or raise UnhandledSource."""
- raise UnhandledSource("Wrong source type {}".format(source))
-
- def parse_url(self, url):
- return urlparse(url)
-
- def base_url(self, url):
- """Return url without querystring or fragment"""
- parts = list(self.parse_url(url))
- parts[4:] = ['' for i in parts[4:]]
- return urlunparse(parts)
-
-
-def filter_installed_packages(packages):
- """Returns a list of packages that require installation"""
- cache = apt_cache()
- _pkgs = []
- for package in packages:
- try:
- p = cache[package]
- p.current_ver or _pkgs.append(package)
- except KeyError:
- log('Package {} has no installation candidate.'.format(package),
- level='WARNING')
- _pkgs.append(package)
- return _pkgs
-
-
-def apt_cache(in_memory=True):
- """Build and return an apt cache"""
- from apt import apt_pkg
- apt_pkg.init()
- if in_memory:
- apt_pkg.config.set("Dir::Cache::pkgcache", "")
- apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
- return apt_pkg.Cache()
-
-
-def apt_install(packages, options=None, fatal=False):
- """Install one or more packages"""
- if options is None:
- options = ['--option=Dpkg::Options::=--force-confold']
-
- cmd = ['apt-get', '--assume-yes']
- cmd.extend(options)
- cmd.append('install')
- if isinstance(packages, six.string_types):
- cmd.append(packages)
- else:
- cmd.extend(packages)
- log("Installing {} with options: {}".format(packages,
- options))
- _run_apt_command(cmd, fatal)
-
-
-def apt_upgrade(options=None, fatal=False, dist=False):
- """Upgrade all packages"""
- if options is None:
- options = ['--option=Dpkg::Options::=--force-confold']
-
- cmd = ['apt-get', '--assume-yes']
- cmd.extend(options)
- if dist:
- cmd.append('dist-upgrade')
- else:
- cmd.append('upgrade')
- log("Upgrading with options: {}".format(options))
- _run_apt_command(cmd, fatal)
-
-
-def apt_update(fatal=False):
- """Update local apt cache"""
- cmd = ['apt-get', 'update']
- _run_apt_command(cmd, fatal)
-
-
-def apt_purge(packages, fatal=False):
- """Purge one or more packages"""
- cmd = ['apt-get', '--assume-yes', 'purge']
- if isinstance(packages, six.string_types):
- cmd.append(packages)
- else:
- cmd.extend(packages)
- log("Purging {}".format(packages))
- _run_apt_command(cmd, fatal)
-
-
-def apt_hold(packages, fatal=False):
- """Hold one or more packages"""
- cmd = ['apt-mark', 'hold']
- if isinstance(packages, six.string_types):
- cmd.append(packages)
- else:
- cmd.extend(packages)
- log("Holding {}".format(packages))
-
- if fatal:
- subprocess.check_call(cmd)
- else:
- subprocess.call(cmd)
-
-
-def add_source(source, key=None):
- """Add a package source to this system.
-
- @param source: a URL or sources.list entry, as supported by
- add-apt-repository(1). Examples::
-
- ppa:charmers/example
- deb https://stub:key@private.example.com/ubuntu trusty main
-
- In addition:
- 'proposed:' may be used to enable the standard 'proposed'
- pocket for the release.
- 'cloud:' may be used to activate official cloud archive pockets,
- such as 'cloud:icehouse'
- 'distro' may be used as a noop
-
- @param key: A key to be added to the system's APT keyring and used
- to verify the signatures on packages. Ideally, this should be an
- ASCII format GPG public key including the block headers. A GPG key
- id may also be used, but be aware that only insecure protocols are
- available to retrieve the actual public key from a public keyserver
- placing your Juju environment at risk. ppa and cloud archive keys
- are securely added automtically, so sould not be provided.
- """
- if source is None:
- log('Source is not present. Skipping')
- return
-
- if (source.startswith('ppa:') or
- source.startswith('http') or
- source.startswith('deb ') or
- source.startswith('cloud-archive:')):
- subprocess.check_call(['add-apt-repository', '--yes', source])
- elif source.startswith('cloud:'):
- apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
- fatal=True)
- pocket = source.split(':')[-1]
- if pocket not in CLOUD_ARCHIVE_POCKETS:
- raise SourceConfigError(
- 'Unsupported cloud: source option %s' %
- pocket)
- actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
- with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
- apt.write(CLOUD_ARCHIVE.format(actual_pocket))
- elif source == 'proposed':
- release = lsb_release()['DISTRIB_CODENAME']
- with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
- apt.write(PROPOSED_POCKET.format(release))
- elif source == 'distro':
- pass
- else:
- log("Unknown source: {!r}".format(source))
-
- if key:
- if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
- with NamedTemporaryFile('w+') as key_file:
- key_file.write(key)
- key_file.flush()
- key_file.seek(0)
- subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
- else:
- # Note that hkp: is in no way a secure protocol. Using a
- # GPG key id is pointless from a security POV unless you
- # absolutely trust your network and DNS.
- subprocess.check_call(['apt-key', 'adv', '--keyserver',
- 'hkp://keyserver.ubuntu.com:80', '--recv',
- key])
-
-
-def configure_sources(update=False,
- sources_var='install_sources',
- keys_var='install_keys'):
- """
- Configure multiple sources from charm configuration.
-
- The lists are encoded as yaml fragments in the configuration.
- The frament needs to be included as a string. Sources and their
- corresponding keys are of the types supported by add_source().
-
- Example config:
- install_sources: |
- - "ppa:foo"
- - "http://example.com/repo precise main"
- install_keys: |
- - null
- - "a1b2c3d4"
-
- Note that 'null' (a.k.a. None) should not be quoted.
- """
- sources = safe_load((config(sources_var) or '').strip()) or []
- keys = safe_load((config(keys_var) or '').strip()) or None
-
- if isinstance(sources, six.string_types):
- sources = [sources]
-
- if keys is None:
- for source in sources:
- add_source(source, None)
- else:
- if isinstance(keys, six.string_types):
- keys = [keys]
-
- if len(sources) != len(keys):
- raise SourceConfigError(
- 'Install sources and keys lists are different lengths')
- for source, key in zip(sources, keys):
- add_source(source, key)
- if update:
- apt_update(fatal=True)
-
-
-def install_remote(source, *args, **kwargs):
- """
- Install a file tree from a remote source
-
- The specified source should be a url of the form:
- scheme://[host]/path[#[option=value][&...]]
-
- Schemes supported are based on this modules submodules.
- Options supported are submodule-specific.
- Additional arguments are passed through to the submodule.
-
- For example::
-
- dest = install_remote('http://example.com/archive.tgz',
- checksum='deadbeef',
- hash_type='sha1')
-
- This will download `archive.tgz`, validate it using SHA1 and, if
- the file is ok, extract it and return the directory in which it
- was extracted. If the checksum fails, it will raise
- :class:`charmhelpers.core.host.ChecksumError`.
- """
- # We ONLY check for True here because can_handle may return a string
- # explaining why it can't handle a given source.
- handlers = [h for h in plugins() if h.can_handle(source) is True]
- installed_to = None
- for handler in handlers:
- try:
- installed_to = handler.install(source, *args, **kwargs)
- except UnhandledSource:
- pass
- if not installed_to:
- raise UnhandledSource("No handler found for source {}".format(source))
- return installed_to
-
-
-def install_from_config(config_var_name):
- charm_config = config()
- source = charm_config[config_var_name]
- return install_remote(source)
-
-
-def plugins(fetch_handlers=None):
- if not fetch_handlers:
- fetch_handlers = FETCH_HANDLERS
- plugin_list = []
- for handler_name in fetch_handlers:
- package, classname = handler_name.rsplit('.', 1)
- try:
- handler_class = getattr(
- importlib.import_module(package),
- classname)
- plugin_list.append(handler_class())
- except (ImportError, AttributeError):
- # Skip missing plugins so that they can be ommitted from
- # installation if desired
- log("FetchHandler {} not found, skipping plugin".format(
- handler_name))
- return plugin_list
-
-
-def _run_apt_command(cmd, fatal=False):
- """
- Run an APT command, checking output and retrying if the fatal flag is set
- to True.
-
- :param: cmd: str: The apt command to run.
- :param: fatal: bool: Whether the command's output should be checked and
- retried.
- """
- env = os.environ.copy()
-
- if 'DEBIAN_FRONTEND' not in env:
- env['DEBIAN_FRONTEND'] = 'noninteractive'
-
- if fatal:
- retry_count = 0
- result = None
-
- # If the command is considered "fatal", we need to retry if the apt
- # lock was not acquired.
-
- while result is None or result == APT_NO_LOCK:
- try:
- result = subprocess.check_call(cmd, env=env)
- except subprocess.CalledProcessError as e:
- retry_count = retry_count + 1
- if retry_count > APT_NO_LOCK_RETRY_COUNT:
- raise
- result = e.returncode
- log("Couldn't acquire DPKG lock. Will retry in {} seconds."
- "".format(APT_NO_LOCK_RETRY_DELAY))
- time.sleep(APT_NO_LOCK_RETRY_DELAY)
-
- else:
- subprocess.call(cmd, env=env)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/fetch/archiveurl.py b/charms/trusty/keepalived/hooks/charmhelpers/fetch/archiveurl.py
deleted file mode 100644
index 8dfce50..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/fetch/archiveurl.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import hashlib
-import re
-
-from charmhelpers.fetch import (
- BaseFetchHandler,
- UnhandledSource
-)
-from charmhelpers.payload.archive import (
- get_archive_handler,
- extract,
-)
-from charmhelpers.core.host import mkdir, check_hash
-
-import six
-if six.PY3:
- from urllib.request import (
- build_opener, install_opener, urlopen, urlretrieve,
- HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
- )
- from urllib.parse import urlparse, urlunparse, parse_qs
- from urllib.error import URLError
-else:
- from urllib import urlretrieve
- from urllib2 import (
- build_opener, install_opener, urlopen,
- HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler,
- URLError
- )
- from urlparse import urlparse, urlunparse, parse_qs
-
-
-def splituser(host):
- '''urllib.splituser(), but six's support of this seems broken'''
- _userprog = re.compile('^(.*)@(.*)$')
- match = _userprog.match(host)
- if match:
- return match.group(1, 2)
- return None, host
-
-
-def splitpasswd(user):
- '''urllib.splitpasswd(), but six's support of this is missing'''
- _passwdprog = re.compile('^([^:]*):(.*)$', re.S)
- match = _passwdprog.match(user)
- if match:
- return match.group(1, 2)
- return user, None
-
-
-class ArchiveUrlFetchHandler(BaseFetchHandler):
- """
- Handler to download archive files from arbitrary URLs.
-
- Can fetch from http, https, ftp, and file URLs.
-
- Can install either tarballs (.tar, .tgz, .tbz2, etc) or zip files.
-
- Installs the contents of the archive in $CHARM_DIR/fetched/.
- """
- def can_handle(self, source):
- url_parts = self.parse_url(source)
- if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
- return "Wrong source type"
- if get_archive_handler(self.base_url(source)):
- return True
- return False
-
- def download(self, source, dest):
- """
- Download an archive file.
-
- :param str source: URL pointing to an archive file.
- :param str dest: Local path location to download archive file to.
- """
- # propogate all exceptions
- # URLError, OSError, etc
- proto, netloc, path, params, query, fragment = urlparse(source)
- if proto in ('http', 'https'):
- auth, barehost = splituser(netloc)
- if auth is not None:
- source = urlunparse((proto, barehost, path, params, query, fragment))
- username, password = splitpasswd(auth)
- passman = HTTPPasswordMgrWithDefaultRealm()
- # Realm is set to None in add_password to force the username and password
- # to be used whatever the realm
- passman.add_password(None, source, username, password)
- authhandler = HTTPBasicAuthHandler(passman)
- opener = build_opener(authhandler)
- install_opener(opener)
- response = urlopen(source)
- try:
- with open(dest, 'w') as dest_file:
- dest_file.write(response.read())
- except Exception as e:
- if os.path.isfile(dest):
- os.unlink(dest)
- raise e
-
- # Mandatory file validation via Sha1 or MD5 hashing.
- def download_and_validate(self, url, hashsum, validate="sha1"):
- tempfile, headers = urlretrieve(url)
- check_hash(tempfile, hashsum, validate)
- return tempfile
-
- def install(self, source, dest=None, checksum=None, hash_type='sha1'):
- """
- Download and install an archive file, with optional checksum validation.
-
- The checksum can also be given on the `source` URL's fragment.
- For example::
-
- handler.install('http://example.com/file.tgz#sha1=deadbeef')
-
- :param str source: URL pointing to an archive file.
- :param str dest: Local destination path to install to. If not given,
- installs to `$CHARM_DIR/archives/archive_file_name`.
- :param str checksum: If given, validate the archive file after download.
- :param str hash_type: Algorithm used to generate `checksum`.
- Can be any hash alrgorithm supported by :mod:`hashlib`,
- such as md5, sha1, sha256, sha512, etc.
-
- """
- url_parts = self.parse_url(source)
- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), 'fetched')
- if not os.path.exists(dest_dir):
- mkdir(dest_dir, perms=0o755)
- dld_file = os.path.join(dest_dir, os.path.basename(url_parts.path))
- try:
- self.download(source, dld_file)
- except URLError as e:
- raise UnhandledSource(e.reason)
- except OSError as e:
- raise UnhandledSource(e.strerror)
- options = parse_qs(url_parts.fragment)
- for key, value in options.items():
- if not six.PY3:
- algorithms = hashlib.algorithms
- else:
- algorithms = hashlib.algorithms_available
- if key in algorithms:
- check_hash(dld_file, value, key)
- if checksum:
- check_hash(dld_file, checksum, hash_type)
- return extract(dld_file, dest)
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/fetch/bzrurl.py b/charms/trusty/keepalived/hooks/charmhelpers/fetch/bzrurl.py
deleted file mode 100644
index 3531315..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/fetch/bzrurl.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-from charmhelpers.fetch import (
- BaseFetchHandler,
- UnhandledSource
-)
-from charmhelpers.core.host import mkdir
-
-import six
-if six.PY3:
- raise ImportError('bzrlib does not support Python3')
-
-try:
- from bzrlib.branch import Branch
- from bzrlib import bzrdir, workingtree, errors
-except ImportError:
- from charmhelpers.fetch import apt_install
- apt_install("python-bzrlib")
- from bzrlib.branch import Branch
- from bzrlib import bzrdir, workingtree, errors
-
-
-class BzrUrlFetchHandler(BaseFetchHandler):
- """Handler for bazaar branches via generic and lp URLs"""
- def can_handle(self, source):
- url_parts = self.parse_url(source)
- if url_parts.scheme not in ('bzr+ssh', 'lp'):
- return False
- else:
- return True
-
- def branch(self, source, dest):
- url_parts = self.parse_url(source)
- # If we use lp:branchname scheme we need to load plugins
- if not self.can_handle(source):
- raise UnhandledSource("Cannot handle {}".format(source))
- if url_parts.scheme == "lp":
- from bzrlib.plugin import load_plugins
- load_plugins()
- try:
- local_branch = bzrdir.BzrDir.create_branch_convenience(dest)
- except errors.AlreadyControlDirError:
- local_branch = Branch.open(dest)
- try:
- remote_branch = Branch.open(source)
- remote_branch.push(local_branch)
- tree = workingtree.WorkingTree.open(dest)
- tree.update()
- except Exception as e:
- raise e
-
- def install(self, source):
- url_parts = self.parse_url(source)
- branch_name = url_parts.path.strip("/").split("/")[-1]
- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
- branch_name)
- if not os.path.exists(dest_dir):
- mkdir(dest_dir, perms=0o755)
- try:
- self.branch(source, dest_dir)
- except OSError as e:
- raise UnhandledSource(e.strerror)
- return dest_dir
diff --git a/charms/trusty/keepalived/hooks/charmhelpers/fetch/giturl.py b/charms/trusty/keepalived/hooks/charmhelpers/fetch/giturl.py
deleted file mode 100644
index ddc25b7..0000000
--- a/charms/trusty/keepalived/hooks/charmhelpers/fetch/giturl.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# Copyright 2014-2015 Canonical Limited.
-#
-# This file is part of charm-helpers.
-#
-# charm-helpers is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# charm-helpers is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-from charmhelpers.fetch import (
- BaseFetchHandler,
- UnhandledSource
-)
-from charmhelpers.core.host import mkdir
-
-import six
-if six.PY3:
- raise ImportError('GitPython does not support Python 3')
-
-try:
- from git import Repo
-except ImportError:
- from charmhelpers.fetch import apt_install
- apt_install("python-git")
- from git import Repo
-
-from git.exc import GitCommandError # noqa E402
-
-
-class GitUrlFetchHandler(BaseFetchHandler):
- """Handler for git branches via generic and github URLs"""
- def can_handle(self, source):
- url_parts = self.parse_url(source)
- # TODO (mattyw) no support for ssh git@ yet
- if url_parts.scheme not in ('http', 'https', 'git'):
- return False
- else:
- return True
-
- def clone(self, source, dest, branch, depth=None):
- if not self.can_handle(source):
- raise UnhandledSource("Cannot handle {}".format(source))
-
- if depth:
- Repo.clone_from(source, dest, branch=branch, depth=depth)
- else:
- Repo.clone_from(source, dest, branch=branch)
-
- def install(self, source, branch="master", dest=None, depth=None):
- url_parts = self.parse_url(source)
- branch_name = url_parts.path.strip("/").split("/")[-1]
- if dest:
- dest_dir = os.path.join(dest, branch_name)
- else:
- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
- branch_name)
- if not os.path.exists(dest_dir):
- mkdir(dest_dir, perms=0o755)
- try:
- self.clone(source, dest_dir, branch, depth)
- except GitCommandError as e:
- raise UnhandledSource(e.message)
- except OSError as e:
- raise UnhandledSource(e.strerror)
- return dest_dir
diff --git a/charms/trusty/keepalived/hooks/config-changed b/charms/trusty/keepalived/hooks/config-changed
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/config-changed
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/hooks/install b/charms/trusty/keepalived/hooks/install
deleted file mode 100755
index 7004e0a..0000000
--- a/charms/trusty/keepalived/hooks/install
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/python
-
-import setup
-setup.pre_install()
-
-from charmhelpers.core import hookenv
-from charmhelpers.fetch import apt_upgrade, apt_install
-
-
-def install():
- hookenv.log('Installing keepalived')
-
- apt_upgrade(fatal=True, dist=True)
- apt_install('keepalived', fatal=True)
-
-
-if __name__ == "__main__":
- install()
diff --git a/charms/trusty/keepalived/hooks/juju_info-relation-joined b/charms/trusty/keepalived/hooks/juju_info-relation-joined
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/juju_info-relation-joined
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/hooks/leader-elected b/charms/trusty/keepalived/hooks/leader-elected
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/leader-elected
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/hooks/services.py b/charms/trusty/keepalived/hooks/services.py
deleted file mode 100644
index bc456aa..0000000
--- a/charms/trusty/keepalived/hooks/services.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/python
-
-import os
-
-from charmhelpers.core import host
-from charmhelpers.core import hookenv
-from charmhelpers.core.services.base import ServiceManager
-from charmhelpers.core.services import helpers
-
-import actions
-
-SYSCTL_FILE = os.path.join(os.sep, 'etc', 'sysctl.d', '50-keepalived.conf')
-KEEPALIVED_CONFIG_FILE = os.path.join(os.sep, 'etc', 'keepalived',
- 'keepalived.conf')
-config = hookenv.config()
-
-
-def manage():
- manager = ServiceManager([
- {
- 'service': 'keepalived',
- 'required_data': [
- helpers.RequiredConfig('virtual-ip',
- 'router-id'),
- {'is_leader': hookenv.is_leader()}
- ],
- 'data_ready': [
- actions.log_start,
- helpers.template(
- source='keepalived.conf',
- target=KEEPALIVED_CONFIG_FILE,
- perms=0o644
- )
- ],
- # keepalived has no "status" command
- 'stop': [
- lambda arg: host.service_stop('keepalived')
- ],
- 'start': [
- lambda arg: host.service_restart('keepalived')
- ],
- },
- {
- 'service': 'procps',
- 'required_data': [
- {'sysctl': {'net.ipv4.ip_nonlocal_bind': 1}},
- ],
- 'data_ready': [
- helpers.template(
- source='50-keepalived.conf',
- target=SYSCTL_FILE,
- perms=0o644
- )
- ],
- }
- ])
- manager.manage()
diff --git a/charms/trusty/keepalived/hooks/setup.py b/charms/trusty/keepalived/hooks/setup.py
deleted file mode 100644
index 50854d9..0000000
--- a/charms/trusty/keepalived/hooks/setup.py
+++ /dev/null
@@ -1,17 +0,0 @@
-def pre_install():
- """
- Do any setup required before the install hook.
- """
- install_charmhelpers()
-
-
-def install_charmhelpers():
- """
- Install the charmhelpers library, if not present.
- """
- try:
- import charmhelpers # noqa
- except ImportError:
- import subprocess
- subprocess.check_call(['apt-get', 'install', '-y', 'python-pip'])
- subprocess.check_call(['pip', 'install', 'charmhelpers'])
diff --git a/charms/trusty/keepalived/hooks/start b/charms/trusty/keepalived/hooks/start
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/start
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/hooks/stop b/charms/trusty/keepalived/hooks/stop
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/stop
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/hooks/upgrade-charm b/charms/trusty/keepalived/hooks/upgrade-charm
deleted file mode 100755
index 5028988..0000000
--- a/charms/trusty/keepalived/hooks/upgrade-charm
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/python
-import services
-services.manage()
diff --git a/charms/trusty/keepalived/icon.svg b/charms/trusty/keepalived/icon.svg
deleted file mode 100644
index 7b82b7a..0000000
--- a/charms/trusty/keepalived/icon.svg
+++ /dev/null
@@ -1,293 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="96"
- height="96"
- id="svg6517"
- version="1.1"
- inkscape:version="0.91 r13725"
- sodipodi:docname="icon.svg"
- viewBox="0 0 96 96">
- <defs
- id="defs6519">
- <linearGradient
- id="Background">
- <stop
- id="stop4178"
- offset="0"
- style="stop-color:#b8b8b8;stop-opacity:1" />
- <stop
- id="stop4180"
- offset="1"
- style="stop-color:#c9c9c9;stop-opacity:1" />
- </linearGradient>
- <filter
- style="color-interpolation-filters:sRGB;"
- inkscape:label="Inner Shadow"
- id="filter1121">
- <feFlood
- flood-opacity="0.59999999999999998"
- flood-color="rgb(0,0,0)"
- result="flood"
- id="feFlood1123" />
- <feComposite
- in="flood"
- in2="SourceGraphic"
- operator="out"
- result="composite1"
- id="feComposite1125" />
- <feGaussianBlur
- in="composite1"
- stdDeviation="1"
- result="blur"
- id="feGaussianBlur1127" />
- <feOffset
- dx="0"
- dy="2"
- result="offset"
- id="feOffset1129" />
- <feComposite
- in="offset"
- in2="SourceGraphic"
- operator="atop"
- result="composite2"
- id="feComposite1131" />
- </filter>
- <filter
- style="color-interpolation-filters:sRGB;"
- inkscape:label="Drop Shadow"
- id="filter950">
- <feFlood
- flood-opacity="0.25"
- flood-color="rgb(0,0,0)"
- result="flood"
- id="feFlood952" />
- <feComposite
- in="flood"
- in2="SourceGraphic"
- operator="in"
- result="composite1"
- id="feComposite954" />
- <feGaussianBlur
- in="composite1"
- stdDeviation="1"
- result="blur"
- id="feGaussianBlur956" />
- <feOffset
- dx="0"
- dy="1"
- result="offset"
- id="feOffset958" />
- <feComposite
- in="SourceGraphic"
- in2="offset"
- operator="over"
- result="composite2"
- id="feComposite960" />
- <feBlend
- blend="normal"
- id="feBlend3895"
- in2="composite2" />
- </filter>
- <clipPath
- clipPathUnits="userSpaceOnUse"
- id="clipPath873">
- <g
- transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
- id="g875"
- inkscape:label="Layer 1"
- style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
- <path
- style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
- d="m 46.702703,898.22775 50.594594,0 C 138.16216,898.22775 144,904.06497 144,944.92583 l 0,50.73846 c 0,40.86071 -5.83784,46.69791 -46.702703,46.69791 l -50.594594,0 C 5.8378378,1042.3622 0,1036.525 0,995.66429 L 0,944.92583 C 0,904.06497 5.8378378,898.22775 46.702703,898.22775 Z"
- id="path877"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="sssssssss" />
- </g>
- </clipPath>
- <filter
- inkscape:collect="always"
- id="filter891"
- inkscape:label="Badge Shadow">
- <feGaussianBlur
- inkscape:collect="always"
- stdDeviation="0.71999962"
- id="feGaussianBlur893" />
- </filter>
- </defs>
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="8.1490724"
- inkscape:cx="49.021381"
- inkscape:cy="46.975739"
- inkscape:document-units="px"
- inkscape:current-layer="layer3"
- showgrid="true"
- fit-margin-top="0"
- fit-margin-left="0"
- fit-margin-right="0"
- fit-margin-bottom="0"
- inkscape:window-width="1920"
- inkscape:window-height="1056"
- inkscape:window-x="0"
- inkscape:window-y="24"
- inkscape:window-maximized="1"
- showborder="true"
- showguides="true"
- inkscape:guide-bbox="true"
- inkscape:showpageshadow="false">
- <inkscape:grid
- type="xygrid"
- id="grid821" />
- <sodipodi:guide
- orientation="1,0"
- position="16,48"
- id="guide823" />
- <sodipodi:guide
- orientation="0,1"
- position="64,80"
- id="guide825" />
- <sodipodi:guide
- orientation="1,0"
- position="80,40"
- id="guide827" />
- <sodipodi:guide
- orientation="0,1"
- position="64,16"
- id="guide829" />
- </sodipodi:namedview>
- <metadata
- id="metadata6522">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title />
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="BACKGROUND"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(268,-635.29076)"
- style="display:inline">
- <path
- style="fill:#333333;fill-opacity:0.93333334000000001;stroke:none;display:inline;filter:url(#filter1121);opacity:0.7"
- d="m -268,700.15563 0,-33.72973 c 0,-27.24324 3.88785,-31.13513 31.10302,-31.13513 l 33.79408,0 c 27.21507,0 31.1029,3.89189 31.1029,31.13513 l 0,33.72973 c 0,27.24325 -3.88783,31.13514 -31.1029,31.13514 l -33.79408,0 C -264.11215,731.29077 -268,727.39888 -268,700.15563 Z"
- id="path6455"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="sssssssss" />
- </g>
- <g
- inkscape:groupmode="layer"
- id="layer3"
- inkscape:label="PLACEHOLDER LETTER"
- style="display:inline">
- <text
- xml:space="preserve"
- style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;filter:url(#filter950)"
- x="17.458124"
- y="69.1772"
- id="text3891"
- sodipodi:linespacing="125%"><tspan
- sodipodi:role="line"
- id="tspan3893"
- x="17.458124"
- y="69.1772"
- style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:56px;font-family:Ubuntu;-inkscape-font-specification:'Ubuntu Medium';fill:#ffffff;fill-opacity:1">ka</tspan></text>
- <rect
- style="opacity:0.7;fill:none;stroke:none"
- id="rect3021"
- width="64"
- height="64"
- x="16"
- y="15.449201" />
- </g>
- <g
- inkscape:groupmode="layer"
- id="layer2"
- inkscape:label="BADGE"
- style="display:none"
- sodipodi:insensitive="true">
- <g
- style="display:inline"
- transform="translate(-340.00001,-581)"
- id="g4394"
- clip-path="none">
- <g
- id="g855">
- <g
- inkscape:groupmode="maskhelper"
- id="g870"
- clip-path="url(#clipPath873)"
- style="opacity:0.6;filter:url(#filter891)">
- <path
- transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
- d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
- sodipodi:ry="12"
- sodipodi:rx="12"
- sodipodi:cy="552.36218"
- sodipodi:cx="252"
- id="path844"
- style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
- sodipodi:type="arc" />
- </g>
- <g
- id="g862">
- <path
- sodipodi:type="arc"
- style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
- id="path4398"
- sodipodi:cx="252"
- sodipodi:cy="552.36218"
- sodipodi:rx="12"
- sodipodi:ry="12"
- d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
- transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
- <path
- transform="matrix(1.25,0,0,1.25,33,-100.45273)"
- d="m 264,552.36218 a 12,12 0 0 1 -12,12 12,12 0 0 1 -12,-12 12,12 0 0 1 12,-12 12,12 0 0 1 12,12 z"
- sodipodi:ry="12"
- sodipodi:rx="12"
- sodipodi:cy="552.36218"
- sodipodi:cx="252"
- id="path4400"
- style="color:#000000;fill:#dd4814;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
- sodipodi:type="arc" />
- <path
- sodipodi:type="star"
- style="color:#000000;fill:#f5f5f5;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
- id="path4459"
- sodipodi:sides="5"
- sodipodi:cx="666.19574"
- sodipodi:cy="589.50385"
- sodipodi:r1="7.2431178"
- sodipodi:r2="4.3458705"
- sodipodi:arg1="1.0471976"
- sodipodi:arg2="1.6755161"
- inkscape:flatsided="false"
- inkscape:rounded="0.1"
- inkscape:randomized="0"
- d="m 669.8173,595.77657 c -0.39132,0.22593 -3.62645,-1.90343 -4.07583,-1.95066 -0.44938,-0.0472 -4.05653,1.36297 -4.39232,1.06062 -0.3358,-0.30235 0.68963,-4.03715 0.59569,-4.47913 -0.0939,-0.44198 -2.5498,-3.43681 -2.36602,-3.8496 0.18379,-0.41279 4.05267,-0.59166 4.44398,-0.81759 0.39132,-0.22593 2.48067,-3.48704 2.93005,-3.4398 0.44938,0.0472 1.81505,3.67147 2.15084,3.97382 0.3358,0.30236 4.08294,1.2817 4.17689,1.72369 0.0939,0.44198 -2.9309,2.86076 -3.11469,3.27355 -0.18379,0.41279 0.0427,4.27917 -0.34859,4.5051 z"
- transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
- </g>
- </g>
- </g>
- </g>
-</svg>
diff --git a/charms/trusty/keepalived/metadata.yaml b/charms/trusty/keepalived/metadata.yaml
deleted file mode 100644
index 88420e2..0000000
--- a/charms/trusty/keepalived/metadata.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: keepalived
-summary: Failover and monitoring daemon for LVS clusters
-maintainer: MichaƂ Sawicz <michal.sawicz@canonical.com>
-description: |
- keepalived is used for monitoring real servers within a Linux Virtual
- Server (LVS) cluster. keepalived can be configured to remove real
- servers from the cluster pool if it stops responding, as well as send
- a notification email to make the admin aware of the service failure.
- In addition, keepalived implements an independent Virtual Router
- Redundancy Protocol (VRRPv2; see rfc2338 for additional info)
- framework for director failover.
-tags:
- - cache-proxy
-subordinate: true
-requires:
- juju_info:
- interface: juju-info
- scope: container \ No newline at end of file
diff --git a/charms/trusty/keepalived/templates/50-keepalived.conf b/charms/trusty/keepalived/templates/50-keepalived.conf
deleted file mode 100644
index 6b023e1..0000000
--- a/charms/trusty/keepalived/templates/50-keepalived.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-{% for key in sysctl %}
-{{ key }}={{ sysctl[key] }}
-{% endfor %} \ No newline at end of file
diff --git a/charms/trusty/keepalived/templates/keepalived.conf b/charms/trusty/keepalived/templates/keepalived.conf
deleted file mode 100644
index 594e926..0000000
--- a/charms/trusty/keepalived/templates/keepalived.conf
+++ /dev/null
@@ -1,10 +0,0 @@
-vrrp_instance VI_1 {
- interface eth0
- state {% if is_leader %}MASTER{% else %}BACKUP{% endif %}
- priority {% if is_leader %}101{% else %}100{% endif %}
- virtual_router_id {{ config['router-id'] }}
-
- virtual_ipaddress {
- {{ config['virtual-ip'] }}
- }
-} \ No newline at end of file
diff --git a/charms/trusty/keepalived/tests/00-setup b/charms/trusty/keepalived/tests/00-setup
deleted file mode 100755
index f0616a5..0000000
--- a/charms/trusty/keepalived/tests/00-setup
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-sudo add-apt-repository ppa:juju/stable -y
-sudo apt-get update
-sudo apt-get install amulet python-requests -y
diff --git a/charms/trusty/keepalived/tests/10-deploy b/charms/trusty/keepalived/tests/10-deploy
deleted file mode 100755
index 1dfaa80..0000000
--- a/charms/trusty/keepalived/tests/10-deploy
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python3
-
-import amulet
-import requests
-import unittest
-
-
-class TestDeployment(unittest.TestCase):
- @classmethod
- def setUpClass(cls):
- cls.deployment = amulet.Deployment()
-
- cls.deployment.add('keepalived')
- cls.deployment.expose('keepalived')
-
- try:
- cls.deployment.setup(timeout=900)
- cls.deployment.sentry.wait()
- except amulet.helpers.TimeoutError:
- amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time")
- except:
- raise
- cls.unit = cls.deployment.sentry.unit['keepalived/0']
-
- def test_case(self):
- # Now you can use self.deployment.sentry.unit[UNIT] to address each of
- # the units and perform more in-depth steps. You can also reference
- # the first unit as self.unit.
- # There are three test statuses that can be triggered with
- # amulet.raise_status():
- # - amulet.PASS
- # - amulet.FAIL
- # - amulet.SKIP
- # Each unit has the following methods:
- # - .info - An array of the information of that unit from Juju
- # - .file(PATH) - Get the details of a file on that unit
- # - .file_contents(PATH) - Get plain text output of PATH file from that unit
- # - .directory(PATH) - Get details of directory
- # - .directory_contents(PATH) - List files and folders in PATH on that unit
- # - .relation(relation, service:rel) - Get relation data from return service
- # add tests here to confirm service is up and working properly
- # For example, to confirm that it has a functioning HTTP server:
- # page = requests.get('http://{}'.format(self.unit.info['public-address']))
- # page.raise_for_status()
- # More information on writing Amulet tests can be found at:
- # https://juju.ubuntu.com/docs/tools-amulet.html
- pass
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/charms/trusty/keepalived/unit_tests/test_actions.py b/charms/trusty/keepalived/unit_tests/test_actions.py
deleted file mode 100755
index 5105ab4..0000000
--- a/charms/trusty/keepalived/unit_tests/test_actions.py
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import mock
-import unittest
-from pkg_resources import resource_filename
-
-# allow importing actions from the hooks directory
-sys.path.append(resource_filename(__name__, '../hooks'))
-import actions
-
-
-class TestActions(unittest.TestCase):
- @mock.patch('charmhelpers.core.hookenv.log')
- def test_log_start(self, log):
- actions.log_start('test-service')
- log.assert_called_once_with('keepalived starting')
-
-
-if __name__ == '__main__':
- unittest.main()