Merged next in and fixed unit tests
This commit is contained in:
		| @@ -191,11 +191,11 @@ def mkdir(path, owner='root', group='root', perms=0o555, force=False): | ||||
|  | ||||
|  | ||||
| def write_file(path, content, owner='root', group='root', perms=0o444): | ||||
|     """Create or overwrite a file with the contents of a string""" | ||||
|     """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, 'w') as target: | ||||
|     with open(path, 'wb') as target: | ||||
|         os.fchown(target.fileno(), uid, gid) | ||||
|         os.fchmod(target.fileno(), perms) | ||||
|         target.write(content) | ||||
| @@ -305,11 +305,11 @@ def restart_on_change(restart_map, stopstart=False): | ||||
|     ceph_client_changed function. | ||||
|     """ | ||||
|     def wrap(f): | ||||
|         def wrapped_f(*args): | ||||
|         def wrapped_f(*args, **kwargs): | ||||
|             checksums = {} | ||||
|             for path in restart_map: | ||||
|                 checksums[path] = file_hash(path) | ||||
|             f(*args) | ||||
|             f(*args, **kwargs) | ||||
|             restarts = [] | ||||
|             for path in restart_map: | ||||
|                 if checksums[path] != file_hash(path): | ||||
| @@ -361,7 +361,7 @@ def list_nics(nic_type): | ||||
|         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('.*: (bond[0-9]+\.[0-9]+)@.*', line) | ||||
|                 matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line) | ||||
|                 if matched: | ||||
|                     interface = matched.groups()[0] | ||||
|                 else: | ||||
|   | ||||
| @@ -26,25 +26,31 @@ from subprocess import check_call | ||||
| from charmhelpers.core.hookenv import ( | ||||
|     log, | ||||
|     DEBUG, | ||||
|     ERROR, | ||||
| ) | ||||
|  | ||||
|  | ||||
| def create(sysctl_dict, sysctl_file): | ||||
|     """Creates a sysctl.conf file from a YAML associative array | ||||
|  | ||||
|     :param sysctl_dict: a dict of sysctl options eg { 'kernel.max_pid': 1337 } | ||||
|     :type sysctl_dict: dict | ||||
|     :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 | ||||
|     """ | ||||
|     sysctl_dict = yaml.load(sysctl_dict) | ||||
|     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.items(): | ||||
|         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), | ||||
|     log("Updating sysctl_file: %s values: %s" % (sysctl_file, sysctl_dict_parsed), | ||||
|         level=DEBUG) | ||||
|  | ||||
|     check_call(["sysctl", "-p", sysctl_file]) | ||||
|   | ||||
| @@ -21,7 +21,7 @@ from charmhelpers.core import hookenv | ||||
|  | ||||
|  | ||||
| def render(source, target, context, owner='root', group='root', | ||||
|            perms=0o444, templates_dir=None): | ||||
|            perms=0o444, templates_dir=None, encoding='UTF-8'): | ||||
|     """ | ||||
|     Render a template. | ||||
|  | ||||
| @@ -64,5 +64,5 @@ def render(source, target, context, owner='root', group='root', | ||||
|                     level=hookenv.ERROR) | ||||
|         raise e | ||||
|     content = template.render(context) | ||||
|     host.mkdir(os.path.dirname(target), owner, group) | ||||
|     host.write_file(target, content, owner, group, perms) | ||||
|     host.mkdir(os.path.dirname(target), owner, group, perms=0o755) | ||||
|     host.write_file(target, content.encode(encoding), owner, group, perms) | ||||
|   | ||||
							
								
								
									
										477
									
								
								hooks/charmhelpers/core/unitdata.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								hooks/charmhelpers/core/unitdata.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,477 @@ | ||||
| #!/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 not charm_rev 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', 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 | ||||
| @@ -33,9 +33,6 @@ from charmhelpers.contrib.openstack.utils import ( | ||||
|     openstack_upgrade_available, | ||||
|     sync_db_with_multi_ipv6_addresses | ||||
| ) | ||||
| from charmhelpers.contrib.openstack.neutron import ( | ||||
|     neutron_plugin_attribute, | ||||
| ) | ||||
|  | ||||
| from neutron_api_utils import ( | ||||
|     NEUTRON_CONF, | ||||
| @@ -105,7 +102,8 @@ def install(): | ||||
|     execd_preinstall() | ||||
|     configure_installation_source(config('openstack-origin')) | ||||
|     apt_update() | ||||
|     apt_install(determine_packages(), fatal=True) | ||||
|     apt_install(determine_packages(config('openstack-origin')), | ||||
|                 fatal=True) | ||||
|     [open_port(port) for port in determine_ports()] | ||||
|  | ||||
|  | ||||
| @@ -113,7 +111,8 @@ def install(): | ||||
| @hooks.hook('config-changed') | ||||
| @restart_on_change(restart_map(), stopstart=True) | ||||
| def config_changed(): | ||||
|     apt_install(filter_installed_packages(determine_packages()), | ||||
|     apt_install(filter_installed_packages( | ||||
|                 determine_packages(config('openstack-origin'))), | ||||
|                 fatal=True) | ||||
|     if config('prefer-ipv6'): | ||||
|         setup_ipv6() | ||||
| @@ -196,9 +195,7 @@ def db_changed(): | ||||
| @hooks.hook('pgsql-db-relation-changed') | ||||
| @restart_on_change(restart_map()) | ||||
| def postgresql_neutron_db_changed(): | ||||
|     plugin = config('neutron-plugin') | ||||
|     # DB config might have been moved to main neutron.conf in H? | ||||
|     CONFIGS.write(neutron_plugin_attribute(plugin, 'config')) | ||||
|     CONFIGS.write(NEUTRON_CONF) | ||||
|  | ||||
|  | ||||
| @hooks.hook('amqp-relation-broken', | ||||
|   | ||||
| @@ -42,9 +42,16 @@ BASE_PACKAGES = [ | ||||
|     'python-keystoneclient', | ||||
|     'python-mysqldb', | ||||
|     'python-psycopg2', | ||||
|     'python-six', | ||||
|     'uuid', | ||||
| ] | ||||
|  | ||||
| KILO_PACKAGES = [ | ||||
|     'python-neutron-lbaas', | ||||
|     'python-neutron-fwaas', | ||||
|     'python-neutron-vpnaas', | ||||
| ] | ||||
|  | ||||
| BASE_SERVICES = [ | ||||
|     'neutron-server' | ||||
| ] | ||||
| @@ -100,7 +107,7 @@ def api_port(service): | ||||
|     return API_PORTS[service] | ||||
|  | ||||
|  | ||||
| def determine_packages(): | ||||
| def determine_packages(source=None): | ||||
|     # currently all packages match service names | ||||
|     packages = [] + BASE_PACKAGES | ||||
|     for v in resource_map().values(): | ||||
| @@ -109,6 +116,8 @@ def determine_packages(): | ||||
|                                         'server_packages', | ||||
|                                         'neutron') | ||||
|         packages.extend(pkgs) | ||||
|     if get_os_codename_install_source(source) >= 'kilo': | ||||
|         packages.extend(KILO_PACKAGES) | ||||
|     return list(set(packages)) | ||||
|  | ||||
|  | ||||
| @@ -208,7 +217,7 @@ def do_openstack_upgrade(configs): | ||||
|     ] | ||||
|     apt_update(fatal=True) | ||||
|     apt_upgrade(options=dpkg_opts, fatal=True, dist=True) | ||||
|     pkgs = determine_packages() | ||||
|     pkgs = determine_packages(new_os_rel) | ||||
|     # Sort packages just to make unit tests easier | ||||
|     pkgs.sort() | ||||
|     apt_install(packages=pkgs, | ||||
|   | ||||
							
								
								
									
										77
									
								
								templates/kilo/neutron.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								templates/kilo/neutron.conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| ############################################################################### | ||||
| # [ WARNING ] | ||||
| # Configuration file maintained by Juju. Local changes may be overwritten. | ||||
| ## Restart trigger {{ restart_trigger }} | ||||
| ############################################################################### | ||||
| [DEFAULT] | ||||
| verbose = {{ verbose }} | ||||
| debug = {{ debug }} | ||||
| use_syslog = {{ use_syslog }} | ||||
| state_path = /var/lib/neutron | ||||
| lock_path = $state_path/lock | ||||
| bind_host = {{ bind_host }} | ||||
| auth_strategy = keystone | ||||
| notification_driver = neutron.openstack.common.notifier.rpc_notifier | ||||
| api_workers = {{ workers }} | ||||
| rpc_workers = {{ workers }} | ||||
|  | ||||
| {% if neutron_bind_port -%} | ||||
| bind_port = {{ neutron_bind_port }} | ||||
| {% else -%} | ||||
| bind_port = 9696 | ||||
| {% endif -%} | ||||
|  | ||||
| {% if core_plugin -%} | ||||
| core_plugin = {{ core_plugin }} | ||||
| {% if neutron_plugin in ['ovs', 'ml2'] -%} | ||||
| service_plugins = router,firewall,lbaas,vpnaas,metering | ||||
| {% endif -%} | ||||
| {% endif -%} | ||||
|  | ||||
| {% if neutron_security_groups -%} | ||||
| allow_overlapping_ips = True | ||||
| neutron_firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver | ||||
| {% endif -%} | ||||
|  | ||||
| {% include "parts/rabbitmq" %} | ||||
|  | ||||
| notify_nova_on_port_status_changes = True | ||||
| notify_nova_on_port_data_changes = True | ||||
| nova_url = {{ nova_url }} | ||||
| nova_region_name = {{ region }} | ||||
| {% if auth_host -%} | ||||
| nova_admin_username = {{ admin_user }} | ||||
| nova_admin_tenant_id = {{ admin_tenant_id }} | ||||
| nova_admin_password = {{ admin_password }} | ||||
| nova_admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }}/v2.0 | ||||
| {% endif -%} | ||||
|  | ||||
| [quotas] | ||||
| quota_driver = neutron.db.quota_db.DbQuotaDriver | ||||
| {% if neutron_security_groups -%} | ||||
| quota_items = network,subnet,port,security_group,security_group_rule | ||||
| {% endif -%} | ||||
|  | ||||
| [agent] | ||||
| root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf | ||||
|  | ||||
| [keystone_authtoken] | ||||
| signing_dir = /var/lib/neutron/keystone-signing | ||||
| {% if service_host -%} | ||||
| service_protocol = {{ service_protocol }} | ||||
| service_host = {{ service_host }} | ||||
| service_port = {{ service_port }} | ||||
| auth_host = {{ auth_host }} | ||||
| auth_port = {{ auth_port }} | ||||
| auth_protocol =  {{ auth_protocol }} | ||||
| admin_tenant_name = {{ admin_tenant_name }} | ||||
| admin_user = {{ admin_user }} | ||||
| admin_password = {{ admin_password }} | ||||
| {% endif -%} | ||||
|  | ||||
| {% include "parts/section-database" %} | ||||
|  | ||||
| [service_providers] | ||||
| service_provider=LOADBALANCER:Haproxy:neutron_lbaas.services.loadbalancer.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default | ||||
| service_provider=VPN:openswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default | ||||
| service_provider=FIREWALL:Iptables:neutron_fwaas.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver:default | ||||
| @@ -152,6 +152,7 @@ class NeutronCCContextTest(CharmTestCase): | ||||
|         plugin.return_value = None | ||||
|         ctxt_data = { | ||||
|             'debug': True, | ||||
|             'enable_dvr': False, | ||||
|             'external_network': 'bob', | ||||
|             'neutron_bind_port': self.api_port, | ||||
|             'verbose': True, | ||||
| @@ -170,6 +171,7 @@ class NeutronCCContextTest(CharmTestCase): | ||||
|         self.test_config.set('overlay-network-type', 'vxlan') | ||||
|         ctxt_data = { | ||||
|             'debug': True, | ||||
|             'enable_dvr': False, | ||||
|             'external_network': 'bob', | ||||
|             'neutron_bind_port': self.api_port, | ||||
|             'verbose': True, | ||||
|   | ||||
| @@ -34,11 +34,11 @@ TO_PATCH = [ | ||||
|     'do_openstack_upgrade', | ||||
|     'execd_preinstall', | ||||
|     'filter_installed_packages', | ||||
|     'get_dvr', | ||||
|     'get_l2population', | ||||
|     'get_overlay_network_type', | ||||
|     'is_relation_made', | ||||
|     'log', | ||||
|     'neutron_plugin_attribute', | ||||
|     'open_port', | ||||
|     'openstack_upgrade_available', | ||||
|     'relation_get', | ||||
| @@ -275,9 +275,11 @@ class NeutronAPIHooksTests(CharmTestCase): | ||||
|     def test_neutron_plugin_api_relation_joined_nol2(self): | ||||
|         _relation_data = { | ||||
|             'neutron-security-groups': False, | ||||
|             'enable-dvr': False, | ||||
|             'l2-population': False, | ||||
|             'overlay-network-type': 'vxlan', | ||||
|         } | ||||
|         self.get_dvr.return_value = False | ||||
|         self.get_l2population.return_value = False | ||||
|         self.get_overlay_network_type.return_value = 'vxlan' | ||||
|         self._call_hook('neutron-plugin-api-relation-joined') | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
|  | ||||
| from mock import MagicMock, patch | ||||
| from collections import OrderedDict | ||||
| from copy import deepcopy | ||||
| import charmhelpers.contrib.openstack.templating as templating | ||||
|  | ||||
| templating.OSConfigRenderer = MagicMock() | ||||
| @@ -65,10 +66,18 @@ class TestNeutronAPIUtils(CharmTestCase): | ||||
|  | ||||
|     def test_determine_packages(self): | ||||
|         pkg_list = nutils.determine_packages() | ||||
|         expect = nutils.BASE_PACKAGES | ||||
|         expect = deepcopy(nutils.BASE_PACKAGES) | ||||
|         expect.extend(['neutron-server', 'neutron-plugin-ml2']) | ||||
|         self.assertItemsEqual(pkg_list, expect) | ||||
|  | ||||
|     def test_determine_packages_kilo(self): | ||||
|         self.get_os_codename_install_source.return_value = 'kilo' | ||||
|         pkg_list = nutils.determine_packages() | ||||
|         expect = deepcopy(nutils.BASE_PACKAGES) | ||||
|         expect.extend(['neutron-server', 'neutron-plugin-ml2']) | ||||
|         expect.extend(nutils.KILO_PACKAGES) | ||||
|         self.assertItemsEqual(pkg_list, expect) | ||||
|  | ||||
|     def test_determine_ports(self): | ||||
|         port_list = nutils.determine_ports() | ||||
|         self.assertItemsEqual(port_list, [9696]) | ||||
| @@ -169,7 +178,7 @@ class TestNeutronAPIUtils(CharmTestCase): | ||||
|         self.apt_upgrade.assert_called_with(options=dpkg_opts, | ||||
|                                             fatal=True, | ||||
|                                             dist=True) | ||||
|         pkgs = nutils.BASE_PACKAGES | ||||
|         pkgs = nutils.determine_packages() | ||||
|         pkgs.sort() | ||||
|         self.apt_install.assert_called_with(packages=pkgs, | ||||
|                                             options=dpkg_opts, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Liam Young
					Liam Young