Resync helpers
This commit is contained in:
parent
2f8a854869
commit
7633d43403
@ -37,6 +37,7 @@ def zap_disk(block_device):
|
||||
check_call(['dd', 'if=/dev/zero', 'of=%s' % (block_device),
|
||||
'bs=512', 'count=100', 'seek=%s' % (gpt_end)])
|
||||
|
||||
|
||||
def is_device_mounted(device):
|
||||
'''Given a device path, return True if that device is mounted, and False
|
||||
if it isn't.
|
||||
|
116
hooks/charmhelpers/core/fstab.py
Normal file
116
hooks/charmhelpers/core/fstab.py
Normal file
@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__author__ = 'Jorge Niedbalski R. <jorge.niedbalski@canonical.com>'
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class Fstab(file):
|
||||
"""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 = d
|
||||
self.p = 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
|
||||
file.__init__(self, self._path, 'r+')
|
||||
|
||||
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():
|
||||
try:
|
||||
if not line.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')
|
||||
self.truncate()
|
||||
return entry
|
||||
|
||||
def remove_entry(self, entry):
|
||||
self.seek(0)
|
||||
|
||||
lines = self.readlines()
|
||||
|
||||
found = False
|
||||
for index, line in enumerate(lines):
|
||||
if not line.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))
|
||||
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))
|
@ -25,7 +25,7 @@ cache = {}
|
||||
def cached(func):
|
||||
"""Cache return values for multiple executions of func + args
|
||||
|
||||
For example:
|
||||
For example::
|
||||
|
||||
@cached
|
||||
def unit_get(attribute):
|
||||
@ -445,18 +445,19 @@ class UnregisteredHookError(Exception):
|
||||
class Hooks(object):
|
||||
"""A convenient handler for hook functions.
|
||||
|
||||
Example:
|
||||
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
|
||||
|
@ -12,11 +12,11 @@ import random
|
||||
import string
|
||||
import subprocess
|
||||
import hashlib
|
||||
import apt_pkg
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from hookenv import log
|
||||
from fstab import Fstab
|
||||
|
||||
|
||||
def service_start(service_name):
|
||||
@ -35,7 +35,8 @@ def 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"""
|
||||
"""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)
|
||||
@ -144,7 +145,19 @@ def write_file(path, content, owner='root', group='root', perms=0444):
|
||||
target.write(content)
|
||||
|
||||
|
||||
def mount(device, mountpoint, options=None, persist=False):
|
||||
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:
|
||||
@ -155,9 +168,9 @@ def mount(device, mountpoint, options=None, persist=False):
|
||||
except subprocess.CalledProcessError, e:
|
||||
log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
|
||||
return False
|
||||
|
||||
if persist:
|
||||
# TODO: update fstab
|
||||
pass
|
||||
return fstab_add(device, mountpoint, filesystem, options=options)
|
||||
return True
|
||||
|
||||
|
||||
@ -169,9 +182,9 @@ def umount(mountpoint, persist=False):
|
||||
except subprocess.CalledProcessError, e:
|
||||
log('Error unmounting {}\n{}'.format(mountpoint, e.output))
|
||||
return False
|
||||
|
||||
if persist:
|
||||
# TODO: update fstab
|
||||
pass
|
||||
return fstab_remove(mountpoint)
|
||||
return True
|
||||
|
||||
|
||||
@ -198,13 +211,13 @@ def file_hash(path):
|
||||
def restart_on_change(restart_map, stopstart=False):
|
||||
"""Restart services based on configuration files changing
|
||||
|
||||
This function is used a decorator, for example
|
||||
This function is used a decorator, for example::
|
||||
|
||||
@restart_on_change({
|
||||
'/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ]
|
||||
})
|
||||
def ceph_client_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
|
||||
@ -300,12 +313,19 @@ def get_nic_hwaddr(nic):
|
||||
|
||||
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
|
||||
|
||||
* 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
|
||||
|
||||
'''
|
||||
import apt_pkg
|
||||
if not pkgcache:
|
||||
apt_pkg.init()
|
||||
# Force Apt to build its cache in memory. That way we avoid race
|
||||
# conditions with other applications building the cache in the same
|
||||
# place.
|
||||
apt_pkg.config.set("Dir::Cache::pkgcache", "")
|
||||
pkgcache = apt_pkg.Cache()
|
||||
pkg = pkgcache[package]
|
||||
return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
|
||||
|
@ -13,7 +13,6 @@ from charmhelpers.core.hookenv import (
|
||||
config,
|
||||
log,
|
||||
)
|
||||
import apt_pkg
|
||||
import os
|
||||
|
||||
|
||||
@ -117,6 +116,7 @@ class BaseFetchHandler(object):
|
||||
|
||||
def filter_installed_packages(packages):
|
||||
"""Returns a list of packages that require installation"""
|
||||
import apt_pkg
|
||||
apt_pkg.init()
|
||||
|
||||
# Tell apt to build an in-memory cache to prevent race conditions (if
|
||||
@ -235,31 +235,39 @@ def configure_sources(update=False,
|
||||
sources_var='install_sources',
|
||||
keys_var='install_keys'):
|
||||
"""
|
||||
Configure multiple sources from charm configuration
|
||||
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.
|
||||
|
||||
Example config:
|
||||
install_sources:
|
||||
install_sources: |
|
||||
- "ppa:foo"
|
||||
- "http://example.com/repo precise main"
|
||||
install_keys:
|
||||
install_keys: |
|
||||
- null
|
||||
- "a1b2c3d4"
|
||||
|
||||
Note that 'null' (a.k.a. None) should not be quoted.
|
||||
"""
|
||||
sources = safe_load(config(sources_var))
|
||||
keys = config(keys_var)
|
||||
if keys is not None:
|
||||
keys = safe_load(keys)
|
||||
if isinstance(sources, basestring) and (
|
||||
keys is None or isinstance(keys, basestring)):
|
||||
add_source(sources, keys)
|
||||
sources = safe_load((config(sources_var) or '').strip()) or []
|
||||
keys = safe_load((config(keys_var) or '').strip()) or None
|
||||
|
||||
if isinstance(sources, basestring):
|
||||
sources = [sources]
|
||||
|
||||
if keys is None:
|
||||
for source in sources:
|
||||
add_source(source, None)
|
||||
else:
|
||||
if not len(sources) == len(keys):
|
||||
msg = 'Install sources and keys lists are different lengths'
|
||||
raise SourceConfigError(msg)
|
||||
for src_num in range(len(sources)):
|
||||
add_source(sources[src_num], keys[src_num])
|
||||
if isinstance(keys, basestring):
|
||||
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)
|
||||
|
||||
|
@ -39,7 +39,8 @@ class BzrUrlFetchHandler(BaseFetchHandler):
|
||||
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)
|
||||
dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
|
||||
branch_name)
|
||||
if not os.path.exists(dest_dir):
|
||||
mkdir(dest_dir, perms=0755)
|
||||
try:
|
||||
|
Loading…
x
Reference in New Issue
Block a user