Browse Source

PLUMgrid gateway initial charm

tags/14.04-eol
Bilal Baqar 4 years ago
commit
2e2935412a
45 changed files with 1602 additions and 0 deletions
  1. BIN
      .coverage
  2. 17
    0
      .project
  3. 9
    0
      .pydevproject
  4. 24
    0
      Makefile
  5. 16
    0
      README.md
  6. 253
    0
      bin/charm_helpers_sync.py
  7. 12
    0
      charm-helpers-sync.yaml
  8. 5
    0
      config.yaml
  9. 9
    0
      copyright
  10. 1
    0
      hooks/config-changed
  11. 1
    0
      hooks/install
  12. 1
    0
      hooks/neutron-plugin-api-relation-broken
  13. 1
    0
      hooks/neutron-plugin-api-relation-changed
  14. 1
    0
      hooks/neutron-plugin-api-relation-departed
  15. 1
    0
      hooks/neutron-plugin-api-relation-joined
  16. 93
    0
      hooks/pg_gw_context.py
  17. 47
    0
      hooks/pg_gw_hooks.py
  18. 128
    0
      hooks/pg_gw_utils.py
  19. 1
    0
      hooks/plumgrid-plugin-relation-broken
  20. 1
    0
      hooks/plumgrid-plugin-relation-changed
  21. 1
    0
      hooks/plumgrid-plugin-relation-departed
  22. 1
    0
      hooks/plumgrid-plugin-relation-joined
  23. 1
    0
      hooks/plumgrid-relation-broken
  24. 1
    0
      hooks/plumgrid-relation-changed
  25. 1
    0
      hooks/plumgrid-relation-departed
  26. 1
    0
      hooks/plumgrid-relation-joined
  27. 1
    0
      hooks/stop
  28. 1
    0
      hooks/upgrade-charm
  29. 304
    0
      icon.svg
  30. 24
    0
      metadata.yaml
  31. 5
    0
      setup.cfg
  32. 2
    0
      templates/icehouse/hostname
  33. 10
    0
      templates/icehouse/hosts
  34. 6
    0
      templates/icehouse/ifcs.conf
  35. 94
    0
      templates/icehouse/network.filters
  36. 11
    0
      templates/icehouse/plumgrid.conf
  37. 21
    0
      templates/parts/rabbitmq
  38. 5
    0
      tests/00-setup
  39. 39
    0
      tests/14-juno
  40. 98
    0
      tests/files/plumgrid-gateway.yaml
  41. 4
    0
      unit_tests/__init__.py
  42. 82
    0
      unit_tests/test_pg_gw_context.py
  43. 68
    0
      unit_tests/test_pg_gw_hooks.py
  44. 79
    0
      unit_tests/test_pg_gw_utils.py
  45. 121
    0
      unit_tests/test_utils.py

BIN
.coverage View File


+ 17
- 0
.project View File

@@ -0,0 +1,17 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<projectDescription>
3
+	<name>neutron-openvswitch</name>
4
+	<comment></comment>
5
+	<projects>
6
+	</projects>
7
+	<buildSpec>
8
+		<buildCommand>
9
+			<name>org.python.pydev.PyDevBuilder</name>
10
+			<arguments>
11
+			</arguments>
12
+		</buildCommand>
13
+	</buildSpec>
14
+	<natures>
15
+		<nature>org.python.pydev.pythonNature</nature>
16
+	</natures>
17
+</projectDescription>

+ 9
- 0
.pydevproject View File

@@ -0,0 +1,9 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<?eclipse-pydev version="1.0"?><pydev_project>
3
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
4
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
5
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
6
+<path>/neutron-openvswitch/hooks</path>
7
+<path>/neutron-openvswitch/unit_tests</path>
8
+</pydev_pathproperty>
9
+</pydev_project>

+ 24
- 0
Makefile View File

@@ -0,0 +1,24 @@
1
+#!/usr/bin/make
2
+PYTHON := /usr/bin/env python
3
+
4
+lint:
5
+	@flake8 --exclude hooks/charmhelpers hooks
6
+	@flake8 --exclude hooks/charmhelpers unit_tests
7
+	@charm proof
8
+
9
+unit_test:
10
+	@echo Starting tests...
11
+	@$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests
12
+
13
+bin/charm_helpers_sync.py:
14
+	@mkdir -p bin
15
+	@bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \
16
+        > bin/charm_helpers_sync.py
17
+
18
+sync: bin/charm_helpers_sync.py
19
+	@$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-sync.yaml
20
+	patch -p0 < ~/profsvcs/canonical/charms/charmhelpers.patch
21
+
22
+publish: lint unit_test
23
+	bzr push lp:charms/plumgrid-gateway
24
+	bzr push lp:charms/trusty/plumgrid-gateway

+ 16
- 0
README.md View File

@@ -0,0 +1,16 @@
1
+# Overview
2
+
3
+This charm provides the PLUMgrid Gateway configuration for a node.
4
+
5
+
6
+# Usage
7
+
8
+To deploy (partial deployment of linked charms only):
9
+
10
+    juju deploy neutron-api
11
+    juju deploy neutron-iovisor
12
+    juju deploy plumgrid-director
13
+    juju deploy plumgrid-gateway
14
+    juju add-relation plumgrid-gateway neutron-iovisor
15
+    juju add-relation plumgrid-gateway plumgrid-director
16
+

+ 253
- 0
bin/charm_helpers_sync.py View File

@@ -0,0 +1,253 @@
1
+#!/usr/bin/python
2
+
3
+# Copyright 2014-2015 Canonical Limited.
4
+#
5
+# This file is part of charm-helpers.
6
+#
7
+# charm-helpers is free software: you can redistribute it and/or modify
8
+# it under the terms of the GNU Lesser General Public License version 3 as
9
+# published by the Free Software Foundation.
10
+#
11
+# charm-helpers is distributed in the hope that it will be useful,
12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+# GNU Lesser General Public License for more details.
15
+#
16
+# You should have received a copy of the GNU Lesser General Public License
17
+# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
18
+
19
+# Authors:
20
+#   Adam Gandelman <adamg@ubuntu.com>
21
+
22
+import logging
23
+import optparse
24
+import os
25
+import subprocess
26
+import shutil
27
+import sys
28
+import tempfile
29
+import yaml
30
+from fnmatch import fnmatch
31
+
32
+import six
33
+
34
+CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
35
+
36
+
37
+def parse_config(conf_file):
38
+    if not os.path.isfile(conf_file):
39
+        logging.error('Invalid config file: %s.' % conf_file)
40
+        return False
41
+    return yaml.load(open(conf_file).read())
42
+
43
+
44
+def clone_helpers(work_dir, branch):
45
+    dest = os.path.join(work_dir, 'charm-helpers')
46
+    logging.info('Checking out %s to %s.' % (branch, dest))
47
+    cmd = ['bzr', 'checkout', '--lightweight', branch, dest]
48
+    subprocess.check_call(cmd)
49
+    return dest
50
+
51
+
52
+def _module_path(module):
53
+    return os.path.join(*module.split('.'))
54
+
55
+
56
+def _src_path(src, module):
57
+    return os.path.join(src, 'charmhelpers', _module_path(module))
58
+
59
+
60
+def _dest_path(dest, module):
61
+    return os.path.join(dest, _module_path(module))
62
+
63
+
64
+def _is_pyfile(path):
65
+    return os.path.isfile(path + '.py')
66
+
67
+
68
+def ensure_init(path):
69
+    '''
70
+    ensure directories leading up to path are importable, omitting
71
+    parent directory, eg path='/hooks/helpers/foo'/:
72
+        hooks/
73
+        hooks/helpers/__init__.py
74
+        hooks/helpers/foo/__init__.py
75
+    '''
76
+    for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
77
+        _i = os.path.join(d, '__init__.py')
78
+        if not os.path.exists(_i):
79
+            logging.info('Adding missing __init__.py: %s' % _i)
80
+            open(_i, 'wb').close()
81
+
82
+
83
+def sync_pyfile(src, dest):
84
+    src = src + '.py'
85
+    src_dir = os.path.dirname(src)
86
+    logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
87
+    if not os.path.exists(dest):
88
+        os.makedirs(dest)
89
+    shutil.copy(src, dest)
90
+    if os.path.isfile(os.path.join(src_dir, '__init__.py')):
91
+        shutil.copy(os.path.join(src_dir, '__init__.py'),
92
+                    dest)
93
+    ensure_init(dest)
94
+
95
+
96
+def get_filter(opts=None):
97
+    opts = opts or []
98
+    if 'inc=*' in opts:
99
+        # do not filter any files, include everything
100
+        return None
101
+
102
+    def _filter(dir, ls):
103
+        incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
104
+        _filter = []
105
+        for f in ls:
106
+            _f = os.path.join(dir, f)
107
+
108
+            if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
109
+                if True not in [fnmatch(_f, inc) for inc in incs]:
110
+                    logging.debug('Not syncing %s, does not match include '
111
+                                  'filters (%s)' % (_f, incs))
112
+                    _filter.append(f)
113
+                else:
114
+                    logging.debug('Including file, which matches include '
115
+                                  'filters (%s): %s' % (incs, _f))
116
+            elif (os.path.isfile(_f) and not _f.endswith('.py')):
117
+                logging.debug('Not syncing file: %s' % f)
118
+                _filter.append(f)
119
+            elif (os.path.isdir(_f) and not
120
+                  os.path.isfile(os.path.join(_f, '__init__.py'))):
121
+                logging.debug('Not syncing directory: %s' % f)
122
+                _filter.append(f)
123
+        return _filter
124
+    return _filter
125
+
126
+
127
+def sync_directory(src, dest, opts=None):
128
+    if os.path.exists(dest):
129
+        logging.debug('Removing existing directory: %s' % dest)
130
+        shutil.rmtree(dest)
131
+    logging.info('Syncing directory: %s -> %s.' % (src, dest))
132
+
133
+    shutil.copytree(src, dest, ignore=get_filter(opts))
134
+    ensure_init(dest)
135
+
136
+
137
+def sync(src, dest, module, opts=None):
138
+
139
+    # Sync charmhelpers/__init__.py for bootstrap code.
140
+    sync_pyfile(_src_path(src, '__init__'), dest)
141
+
142
+    # Sync other __init__.py files in the path leading to module.
143
+    m = []
144
+    steps = module.split('.')[:-1]
145
+    while steps:
146
+        m.append(steps.pop(0))
147
+        init = '.'.join(m + ['__init__'])
148
+        sync_pyfile(_src_path(src, init),
149
+                    os.path.dirname(_dest_path(dest, init)))
150
+
151
+    # Sync the module, or maybe a .py file.
152
+    if os.path.isdir(_src_path(src, module)):
153
+        sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
154
+    elif _is_pyfile(_src_path(src, module)):
155
+        sync_pyfile(_src_path(src, module),
156
+                    os.path.dirname(_dest_path(dest, module)))
157
+    else:
158
+        logging.warn('Could not sync: %s. Neither a pyfile or directory, '
159
+                     'does it even exist?' % module)
160
+
161
+
162
+def parse_sync_options(options):
163
+    if not options:
164
+        return []
165
+    return options.split(',')
166
+
167
+
168
+def extract_options(inc, global_options=None):
169
+    global_options = global_options or []
170
+    if global_options and isinstance(global_options, six.string_types):
171
+        global_options = [global_options]
172
+    if '|' not in inc:
173
+        return (inc, global_options)
174
+    inc, opts = inc.split('|')
175
+    return (inc, parse_sync_options(opts) + global_options)
176
+
177
+
178
+def sync_helpers(include, src, dest, options=None):
179
+    if not os.path.isdir(dest):
180
+        os.makedirs(dest)
181
+
182
+    global_options = parse_sync_options(options)
183
+
184
+    for inc in include:
185
+        if isinstance(inc, str):
186
+            inc, opts = extract_options(inc, global_options)
187
+            sync(src, dest, inc, opts)
188
+        elif isinstance(inc, dict):
189
+            # could also do nested dicts here.
190
+            for k, v in six.iteritems(inc):
191
+                if isinstance(v, list):
192
+                    for m in v:
193
+                        inc, opts = extract_options(m, global_options)
194
+                        sync(src, dest, '%s.%s' % (k, inc), opts)
195
+
196
+if __name__ == '__main__':
197
+    parser = optparse.OptionParser()
198
+    parser.add_option('-c', '--config', action='store', dest='config',
199
+                      default=None, help='helper config file')
200
+    parser.add_option('-D', '--debug', action='store_true', dest='debug',
201
+                      default=False, help='debug')
202
+    parser.add_option('-b', '--branch', action='store', dest='branch',
203
+                      help='charm-helpers bzr branch (overrides config)')
204
+    parser.add_option('-d', '--destination', action='store', dest='dest_dir',
205
+                      help='sync destination dir (overrides config)')
206
+    (opts, args) = parser.parse_args()
207
+
208
+    if opts.debug:
209
+        logging.basicConfig(level=logging.DEBUG)
210
+    else:
211
+        logging.basicConfig(level=logging.INFO)
212
+
213
+    if opts.config:
214
+        logging.info('Loading charm helper config from %s.' % opts.config)
215
+        config = parse_config(opts.config)
216
+        if not config:
217
+            logging.error('Could not parse config from %s.' % opts.config)
218
+            sys.exit(1)
219
+    else:
220
+        config = {}
221
+
222
+    if 'branch' not in config:
223
+        config['branch'] = CHARM_HELPERS_BRANCH
224
+    if opts.branch:
225
+        config['branch'] = opts.branch
226
+    if opts.dest_dir:
227
+        config['destination'] = opts.dest_dir
228
+
229
+    if 'destination' not in config:
230
+        logging.error('No destination dir. specified as option or config.')
231
+        sys.exit(1)
232
+
233
+    if 'include' not in config:
234
+        if not args:
235
+            logging.error('No modules to sync specified as option or config.')
236
+            sys.exit(1)
237
+        config['include'] = []
238
+        [config['include'].append(a) for a in args]
239
+
240
+    sync_options = None
241
+    if 'options' in config:
242
+        sync_options = config['options']
243
+    tmpd = tempfile.mkdtemp()
244
+    try:
245
+        checkout = clone_helpers(tmpd, config['branch'])
246
+        sync_helpers(config['include'], checkout, config['destination'],
247
+                     options=sync_options)
248
+    except Exception as e:
249
+        logging.error("Could not sync: %s" % e)
250
+        raise e
251
+    finally:
252
+        logging.debug('Cleaning up %s' % tmpd)
253
+        shutil.rmtree(tmpd)

+ 12
- 0
charm-helpers-sync.yaml View File

@@ -0,0 +1,12 @@
1
+branch: lp:charm-helpers
2
+destination: hooks/charmhelpers
3
+include:
4
+    - core
5
+    - fetch
6
+    - contrib.openstack|inc=*
7
+    - contrib.hahelpers
8
+    - contrib.network.ovs
9
+    - contrib.storage.linux
10
+    - payload.execd
11
+    - contrib.network.ip
12
+    - contrib.python.packages

+ 5
- 0
config.yaml View File

@@ -0,0 +1,5 @@
1
+options:
2
+  external-interface:
3
+    default: eth1
4
+    type: string
5
+    description: The interface that will provide external connectivity

+ 9
- 0
copyright View File

@@ -0,0 +1,9 @@
1
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
2
+
3
+Files: *
4
+Copyright: 2012, Canonical Ltd.
5
+License: GPL-3
6
+
7
+License: GPL-3
8
+ On Debian GNU/Linux system you can find the complete text of the
9
+ GPL-3 license in '/usr/share/common-licenses/GPL-3'

+ 1
- 0
hooks/config-changed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/install View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/neutron-plugin-api-relation-broken View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/neutron-plugin-api-relation-changed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/neutron-plugin-api-relation-departed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/neutron-plugin-api-relation-joined View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 93
- 0
hooks/pg_gw_context.py View File

@@ -0,0 +1,93 @@
1
+from charmhelpers.core.hookenv import (
2
+    relation_ids,
3
+    related_units,
4
+    relation_get,
5
+    config,
6
+)
7
+from charmhelpers.contrib.openstack import context
8
+
9
+from socket import gethostname as get_unit_hostname
10
+
11
+'''
12
+#This function will be used to get information from neutron-api
13
+def _neutron_api_settings():
14
+    neutron_settings = {
15
+        'neutron_security_groups': False,
16
+        'l2_population': True,
17
+        'overlay_network_type': 'gre',
18
+    }
19
+    for rid in relation_ids('neutron-plugin-api'):
20
+        for unit in related_units(rid):
21
+            rdata = relation_get(rid=rid, unit=unit)
22
+            if 'l2-population' not in rdata:
23
+                continue
24
+            neutron_settings = {
25
+                'l2_population': rdata['l2-population'],
26
+                'neutron_security_groups': rdata['neutron-security-groups'],
27
+                'overlay_network_type': rdata['overlay-network-type'],
28
+            }
29
+            # Override with configuration if set to true
30
+            if config('disable-security-groups'):
31
+                neutron_settings['neutron_security_groups'] = False
32
+            return neutron_settings
33
+    return neutron_settings
34
+'''
35
+
36
+
37
+def _pg_dir_settings():
38
+    '''
39
+    Inspects current neutron-plugin relation
40
+    '''
41
+    pg_settings = {
42
+        'pg_dir_ip': '192.168.100.201',
43
+    }
44
+    for rid in relation_ids('plumgrid'):
45
+        for unit in related_units(rid):
46
+            rdata = relation_get(rid=rid, unit=unit)
47
+            pg_settings = {
48
+                'pg_dir_ip': rdata['private-address'],
49
+            }
50
+    return pg_settings
51
+
52
+
53
+class PGGwContext(context.NeutronContext):
54
+    interfaces = []
55
+
56
+    @property
57
+    def plugin(self):
58
+        return 'plumgrid'
59
+
60
+    @property
61
+    def network_manager(self):
62
+        return 'neutron'
63
+
64
+    def _save_flag_file(self):
65
+        pass
66
+
67
+    #@property
68
+    #def neutron_security_groups(self):
69
+    #    neutron_api_settings = _neutron_api_settings()
70
+    #    return neutron_api_settings['neutron_security_groups']
71
+
72
+    def pg_ctxt(self):
73
+        #Generated Config for all Plumgrid templates inside
74
+        #the templates folder
75
+        pg_ctxt = super(PGGwContext, self).pg_ctxt()
76
+        if not pg_ctxt:
77
+            return {}
78
+
79
+        conf = config()
80
+        pg_dir_settings = _pg_dir_settings()
81
+        pg_ctxt['local_ip'] = pg_dir_settings['pg_dir_ip']
82
+
83
+        #neutron_api_settings = _neutron_api_settings()
84
+        #TODO: Either get this value from the director or neutron-api charm
85
+        unit_hostname = get_unit_hostname()
86
+        pg_ctxt['pg_hostname'] = unit_hostname
87
+        pg_ctxt['interface'] = "juju-br0"
88
+        pg_ctxt['label'] = unit_hostname
89
+        pg_ctxt['fabric_mode'] = 'host'
90
+
91
+        pg_ctxt['ext_interface'] = conf['external-interface']
92
+
93
+        return pg_ctxt

+ 47
- 0
hooks/pg_gw_hooks.py View File

@@ -0,0 +1,47 @@
1
+#!/usr/bin/python
2
+
3
+import sys
4
+
5
+from charmhelpers.core.hookenv import (
6
+    Hooks,
7
+    UnregisteredHookError,
8
+    log,
9
+)
10
+
11
+from pg_gw_utils import (
12
+    register_configs,
13
+    ensure_files,
14
+    restart_pg,
15
+    stop_pg,
16
+)
17
+
18
+hooks = Hooks()
19
+CONFIGS = register_configs()
20
+
21
+
22
+@hooks.hook()
23
+def install():
24
+    ensure_files()
25
+
26
+
27
+@hooks.hook('plumgrid-plugin-relation-joined')
28
+def plumgrid_dir():
29
+    ensure_files()
30
+    CONFIGS.write_all()
31
+    restart_pg()
32
+
33
+
34
+@hooks.hook('stop')
35
+def stop():
36
+    stop_pg()
37
+
38
+
39
+def main():
40
+    try:
41
+        hooks.execute(sys.argv)
42
+    except UnregisteredHookError as e:
43
+        log('Unknown hook {} - skipping.'.format(e))
44
+
45
+
46
+if __name__ == '__main__':
47
+    main()

+ 128
- 0
hooks/pg_gw_utils.py View File

@@ -0,0 +1,128 @@
1
+from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
2
+from copy import deepcopy
3
+from charmhelpers.core.hookenv import log
4
+from charmhelpers.core.host import (
5
+    write_file,
6
+)
7
+from charmhelpers.contrib.openstack import templating
8
+from collections import OrderedDict
9
+from charmhelpers.contrib.openstack.utils import (
10
+    os_release,
11
+)
12
+import pg_gw_context
13
+import subprocess
14
+import time
15
+
16
+#Dont need these right now
17
+NOVA_CONF_DIR = "/etc/nova"
18
+NEUTRON_CONF_DIR = "/etc/neutron"
19
+NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
20
+NEUTRON_DEFAULT = '/etc/default/neutron-server'
21
+
22
+#Puppet Files
23
+P_PGKA_CONF = '/opt/pg/etc/puppet/modules/sal/templates/keepalived.conf.erb'
24
+P_PG_CONF = '/opt/pg/etc/puppet/modules/plumgrid/templates/plumgrid.conf.erb'
25
+P_PGDEF_CONF = '/opt/pg/etc/puppet/modules/sal/templates/default.conf.erb'
26
+
27
+#Plumgrid Files
28
+PGKA_CONF = '/var/lib/libvirt/filesystems/plumgrid/etc/keepalived/keepalived.conf'
29
+PG_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/etc/plumgrid.conf'
30
+PGDEF_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/sal/nginx/conf.d/default.conf'
31
+PGHN_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hostname'
32
+PGHS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hosts'
33
+PGIFCS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/pg/ifcs.conf'
34
+IFCTL_CONF = '/var/run/plumgrid/lxc/ifc_list_gateway'
35
+IFCTL_P_CONF = '/var/lib/libvirt/filesystems/plumgrid/var/run/plumgrid/lxc/ifc_list_gateway'
36
+
37
+#EDGE SPECIFIC
38
+SUDOERS_CONF = '/etc/sudoers.d/ifc_ctl_sudoers'
39
+FILTERS_CONF_DIR = '/etc/nova/rootwrap.d'
40
+FILTERS_CONF = '%s/network.filters' % FILTERS_CONF_DIR
41
+
42
+BASE_RESOURCE_MAP = OrderedDict([
43
+    (PG_CONF, {
44
+        'services': ['plumgrid'],
45
+        'contexts': [pg_gw_context.PGGwContext()],
46
+    }),
47
+    (PGHN_CONF, {
48
+        'services': ['plumgrid'],
49
+        'contexts': [pg_gw_context.PGGwContext()],
50
+    }),
51
+    (PGHS_CONF, {
52
+        'services': ['plumgrid'],
53
+        'contexts': [pg_gw_context.PGGwContext()],
54
+    }),
55
+    (PGIFCS_CONF, {
56
+        'services': [],
57
+        'contexts': [pg_gw_context.PGGwContext()],
58
+    }),
59
+    (FILTERS_CONF, {
60
+        'services': [],
61
+        'contexts': [pg_gw_context.PGGwContext()],
62
+    }),
63
+])
64
+
65
+TEMPLATES = 'templates/'
66
+
67
+
68
+def determine_packages():
69
+    return neutron_plugin_attribute('plumgrid', 'packages', 'neutron')
70
+
71
+
72
+def register_configs(release=None):
73
+    release = release or os_release('neutron-common', base='icehouse')
74
+    configs = templating.OSConfigRenderer(templates_dir=TEMPLATES,
75
+                                          openstack_release=release)
76
+    for cfg, rscs in resource_map().iteritems():
77
+        configs.register(cfg, rscs['contexts'])
78
+    return configs
79
+
80
+
81
+def resource_map():
82
+    '''
83
+    Dynamically generate a map of resources that will be managed for a single
84
+    hook execution.
85
+    '''
86
+    resource_map = deepcopy(BASE_RESOURCE_MAP)
87
+    return resource_map
88
+
89
+
90
+def restart_map():
91
+    '''
92
+    Constructs a restart map based on charm config settings and relation
93
+    state.
94
+    '''
95
+    return {k: v['services'] for k, v in resource_map().iteritems()}
96
+
97
+
98
+def ensure_files():
99
+    _exec_cmd(cmd=['cp', '--remove-destination', '-f', P_PG_CONF, PG_CONF])
100
+    write_file(SUDOERS_CONF, "\nnova ALL=(root) NOPASSWD: /opt/pg/bin/ifc_ctl_pp *\n", owner='root', group='root', perms=0o644)
101
+    _exec_cmd(cmd=['mkdir', '-p', FILTERS_CONF_DIR])
102
+    _exec_cmd(cmd=['touch', FILTERS_CONF])
103
+
104
+
105
+def restart_pg():
106
+    _exec_cmd(cmd=['virsh', '-c', 'lxc:', 'destroy', 'plumgrid'], error_msg='ERROR Destroying PLUMgrid')
107
+    _exec_cmd(cmd=['rm', IFCTL_CONF, IFCTL_P_CONF], error_msg='ERROR Removing ifc_ctl_gateway file')
108
+    _exec_cmd(cmd=['iptables', '-F'])
109
+    _exec_cmd(cmd=['virsh', '-c', 'lxc:', 'start', 'plumgrid'], error_msg='ERROR Starting PLUMgrid')
110
+    time.sleep(5)
111
+    _exec_cmd(cmd=['service', 'plumgrid', 'start'], error_msg='ERROR starting PLUMgrid service')
112
+    time.sleep(5)
113
+
114
+
115
+def stop_pg():
116
+    _exec_cmd(cmd=['virsh', '-c', 'lxc:', 'destroy', 'plumgrid'], error_msg='ERROR Destroying PLUMgrid')
117
+    time.sleep(2)
118
+    _exec_cmd(cmd=['rm', IFCTL_CONF, IFCTL_P_CONF], error_msg='ERROR Removing ifc_ctl_gateway file')
119
+
120
+
121
+def _exec_cmd(cmd=None, error_msg='Command exited with ERRORs'):
122
+    if cmd is None:
123
+        log("NO command")
124
+    else:
125
+        try:
126
+            subprocess.check_call(cmd)
127
+        except subprocess.CalledProcessError, e:
128
+            log(error_msg)

+ 1
- 0
hooks/plumgrid-plugin-relation-broken View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-plugin-relation-changed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-plugin-relation-departed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-plugin-relation-joined View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-relation-broken View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-relation-changed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-relation-departed View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/plumgrid-relation-joined View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/stop View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 1
- 0
hooks/upgrade-charm View File

@@ -0,0 +1 @@
1
+pg_gw_hooks.py

+ 304
- 0
icon.svg View File

@@ -0,0 +1,304 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+<svg
5
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
6
+   xmlns:cc="http://creativecommons.org/ns#"
7
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8
+   xmlns:svg="http://www.w3.org/2000/svg"
9
+   xmlns="http://www.w3.org/2000/svg"
10
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+   width="96"
13
+   height="96"
14
+   id="svg6517"
15
+   version="1.1"
16
+   inkscape:version="0.91 r13725"
17
+   sodipodi:docname="Gateway1.svg">
18
+  <defs
19
+     id="defs6519">
20
+    <linearGradient
21
+       id="Background">
22
+      <stop
23
+         id="stop4178"
24
+         offset="0"
25
+         style="stop-color:#b8b8b8;stop-opacity:1" />
26
+      <stop
27
+         id="stop4180"
28
+         offset="1"
29
+         style="stop-color:#c9c9c9;stop-opacity:1" />
30
+    </linearGradient>
31
+    <filter
32
+       style="color-interpolation-filters:sRGB;"
33
+       inkscape:label="Inner Shadow"
34
+       id="filter1121">
35
+      <feFlood
36
+         flood-opacity="0.59999999999999998"
37
+         flood-color="rgb(0,0,0)"
38
+         result="flood"
39
+         id="feFlood1123" />
40
+      <feComposite
41
+         in="flood"
42
+         in2="SourceGraphic"
43
+         operator="out"
44
+         result="composite1"
45
+         id="feComposite1125" />
46
+      <feGaussianBlur
47
+         in="composite1"
48
+         stdDeviation="1"
49
+         result="blur"
50
+         id="feGaussianBlur1127" />
51
+      <feOffset
52
+         dx="0"
53
+         dy="2"
54
+         result="offset"
55
+         id="feOffset1129" />
56
+      <feComposite
57
+         in="offset"
58
+         in2="SourceGraphic"
59
+         operator="atop"
60
+         result="composite2"
61
+         id="feComposite1131" />
62
+    </filter>
63
+    <filter
64
+       style="color-interpolation-filters:sRGB;"
65
+       inkscape:label="Drop Shadow"
66
+       id="filter950">
67
+      <feFlood
68
+         flood-opacity="0.25"
69
+         flood-color="rgb(0,0,0)"
70
+         result="flood"
71
+         id="feFlood952" />
72
+      <feComposite
73
+         in="flood"
74
+         in2="SourceGraphic"
75
+         operator="in"
76
+         result="composite1"
77
+         id="feComposite954" />
78
+      <feGaussianBlur
79
+         in="composite1"
80
+         stdDeviation="1"
81
+         result="blur"
82
+         id="feGaussianBlur956" />
83
+      <feOffset
84
+         dx="0"
85
+         dy="1"
86
+         result="offset"
87
+         id="feOffset958" />
88
+      <feComposite
89
+         in="SourceGraphic"
90
+         in2="offset"
91
+         operator="over"
92
+         result="composite2"
93
+         id="feComposite960" />
94
+    </filter>
95
+    <clipPath
96
+       clipPathUnits="userSpaceOnUse"
97
+       id="clipPath873">
98
+      <g
99
+         transform="matrix(0,-0.66666667,0.66604479,0,-258.25992,677.00001)"
100
+         id="g875"
101
+         inkscape:label="Layer 1"
102
+         style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline">
103
+        <path
104
+           style="fill:#ff00ff;fill-opacity:1;stroke:none;display:inline"
105
+           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"
106
+           id="path877"
107
+           inkscape:connector-curvature="0"
108
+           sodipodi:nodetypes="sssssssss" />
109
+      </g>
110
+    </clipPath>
111
+    <filter
112
+       inkscape:collect="always"
113
+       id="filter891"
114
+       inkscape:label="Badge Shadow">
115
+      <feGaussianBlur
116
+         inkscape:collect="always"
117
+         stdDeviation="0.71999962"
118
+         id="feGaussianBlur893" />
119
+    </filter>
120
+  </defs>
121
+  <sodipodi:namedview
122
+     id="base"
123
+     pagecolor="#ffffff"
124
+     bordercolor="#666666"
125
+     borderopacity="1.0"
126
+     inkscape:pageopacity="0.0"
127
+     inkscape:pageshadow="2"
128
+     inkscape:zoom="4.0745362"
129
+     inkscape:cx="57.131043"
130
+     inkscape:cy="49.018169"
131
+     inkscape:document-units="px"
132
+     inkscape:current-layer="layer1"
133
+     showgrid="true"
134
+     fit-margin-top="0"
135
+     fit-margin-left="0"
136
+     fit-margin-right="0"
137
+     fit-margin-bottom="0"
138
+     inkscape:window-width="1366"
139
+     inkscape:window-height="705"
140
+     inkscape:window-x="-8"
141
+     inkscape:window-y="-8"
142
+     inkscape:window-maximized="1"
143
+     showborder="true"
144
+     showguides="true"
145
+     inkscape:guide-bbox="true"
146
+     inkscape:showpageshadow="false">
147
+    <inkscape:grid
148
+       type="xygrid"
149
+       id="grid821" />
150
+    <sodipodi:guide
151
+       orientation="1,0"
152
+       position="16,48"
153
+       id="guide823" />
154
+    <sodipodi:guide
155
+       orientation="0,1"
156
+       position="64,80"
157
+       id="guide825" />
158
+    <sodipodi:guide
159
+       orientation="1,0"
160
+       position="80,40"
161
+       id="guide827" />
162
+    <sodipodi:guide
163
+       orientation="0,1"
164
+       position="64,16"
165
+       id="guide829" />
166
+  </sodipodi:namedview>
167
+  <metadata
168
+     id="metadata6522">
169
+    <rdf:RDF>
170
+      <cc:Work
171
+         rdf:about="">
172
+        <dc:format>image/svg+xml</dc:format>
173
+        <dc:type
174
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
175
+        <dc:title></dc:title>
176
+      </cc:Work>
177
+    </rdf:RDF>
178
+  </metadata>
179
+  <g
180
+     inkscape:label="BACKGROUND"
181
+     inkscape:groupmode="layer"
182
+     id="layer1"
183
+     transform="translate(268,-635.29076)"
184
+     style="display:inline">
185
+    <path
186
+       style="fill:#029bd6;fill-opacity:0.90980393;stroke:none;display:inline;filter:url(#filter1121)"
187
+       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"
188
+       id="path6455"
189
+       inkscape:connector-curvature="0"
190
+       sodipodi:nodetypes="sssssssss" />
191
+    <g
192
+       transform="matrix(1.2554482,0,0,1.2247945,-280.29427,621.82638)"
193
+       id="g3">
194
+      <g
195
+         id="g5">
196
+        <g
197
+           id="g7">
198
+          <polygon
199
+             style="fill:#ffffff"
200
+             points="72.643,41.599 72.643,24.878 56.501,25.459 60.839,29.796 49.781,40.854 56.813,47.891 67.872,36.83 "
201
+             id="polygon9" />
202
+          <polygon
203
+             style="fill:#ffffff"
204
+             points="40.048,74.195 23.327,74.195 23.908,58.055 28.246,62.393 39.299,51.34 46.332,58.372 35.279,69.426 "
205
+             id="polygon11" />
206
+        </g>
207
+        <g
208
+           id="g13">
209
+          <polygon
210
+             style="fill:#ffffff"
211
+             points="39.721,25.136 23.005,25.136 23.581,41.277 27.918,36.938 38.977,47.997 46.009,40.964 34.951,29.906 "
212
+             id="polygon15" />
213
+          <polygon
214
+             style="fill:#ffffff"
215
+             points="72.965,58.383 72.965,75.1 56.824,74.521 61.162,70.186 50.104,59.127 57.137,52.094 68.195,63.152 "
216
+             id="polygon17" />
217
+        </g>
218
+      </g>
219
+      <circle
220
+         style="fill:none;stroke:#ffffff;stroke-width:3;stroke-miterlimit:10"
221
+         stroke-miterlimit="10"
222
+         cx="47.985001"
223
+         cy="49.990002"
224
+         r="10.165"
225
+         id="circle19" />
226
+    </g>
227
+  </g>
228
+  <g
229
+     inkscape:groupmode="layer"
230
+     id="layer3"
231
+     inkscape:label="PLACE YOUR PICTOGRAM HERE"
232
+     style="display:inline" />
233
+  <g
234
+     inkscape:groupmode="layer"
235
+     id="layer2"
236
+     inkscape:label="BADGE"
237
+     style="display:none"
238
+     sodipodi:insensitive="true">
239
+    <g
240
+       style="display:inline"
241
+       transform="translate(-340.00001,-581)"
242
+       id="g4394"
243
+       clip-path="none">
244
+      <g
245
+         id="g855">
246
+        <g
247
+           inkscape:groupmode="maskhelper"
248
+           id="g870"
249
+           clip-path="url(#clipPath873)"
250
+           style="opacity:0.6;filter:url(#filter891)">
251
+          <path
252
+             transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-237.54282)"
253
+             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"
254
+             sodipodi:ry="12"
255
+             sodipodi:rx="12"
256
+             sodipodi:cy="552.36218"
257
+             sodipodi:cx="252"
258
+             id="path844"
259
+             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"
260
+             sodipodi:type="arc" />
261
+        </g>
262
+        <g
263
+           id="g862">
264
+          <path
265
+             sodipodi:type="arc"
266
+             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"
267
+             id="path4398"
268
+             sodipodi:cx="252"
269
+             sodipodi:cy="552.36218"
270
+             sodipodi:rx="12"
271
+             sodipodi:ry="12"
272
+             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"
273
+             transform="matrix(1.4999992,0,0,1.4999992,-29.999795,-238.54282)" />
274
+          <path
275
+             transform="matrix(1.25,0,0,1.25,33,-100.45273)"
276
+             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"
277
+             sodipodi:ry="12"
278
+             sodipodi:rx="12"
279
+             sodipodi:cy="552.36218"
280
+             sodipodi:cx="252"
281
+             id="path4400"
282
+             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"
283
+             sodipodi:type="arc" />
284
+          <path
285
+             sodipodi:type="star"
286
+             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"
287
+             id="path4459"
288
+             sodipodi:sides="5"
289
+             sodipodi:cx="666.19574"
290
+             sodipodi:cy="589.50385"
291
+             sodipodi:r1="7.2431178"
292
+             sodipodi:r2="4.3458705"
293
+             sodipodi:arg1="1.0471976"
294
+             sodipodi:arg2="1.6755161"
295
+             inkscape:flatsided="false"
296
+             inkscape:rounded="0.1"
297
+             inkscape:randomized="0"
298
+             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"
299
+             transform="matrix(1.511423,-0.16366377,0.16366377,1.511423,-755.37346,-191.93651)" />
300
+        </g>
301
+      </g>
302
+    </g>
303
+  </g>
304
+</svg>

+ 24
- 0
metadata.yaml View File

@@ -0,0 +1,24 @@
1
+name: plumgrid-gateway
2
+subordinate: false
3
+maintainer: Bilal Baqar <bbaqar@plumgrid.com>
4
+summary: "OpenStack Neutron OpenvSwitch Agent"
5
+description: |
6
+  Neutron is a virtual network service for Openstack, and a part of
7
+  Netstack. Just like OpenStack Nova provides an API to dynamically
8
+  request and configure virtual servers, Neutron provides an API to
9
+  dynamically request and configure virtual networks. These networks
10
+  connect "interfaces" from other OpenStack services (e.g., virtual NICs
11
+  from Nova VMs). The Neutron API supports extensions to provide
12
+  advanced network capabilities (e.g., QoS, ACLs, network monitoring,
13
+  etc.)
14
+  .
15
+  This charm provides the Plumgrid Gateway
16
+tags:
17
+  - openstack
18
+requires:
19
+  plumgrid-plugin:
20
+    interface: plumgrid-plugin
21
+  plumgrid:
22
+    interface: plumgrid
23
+  neutron-plugin-api:
24
+    interface: neutron-plugin-api

+ 5
- 0
setup.cfg View File

@@ -0,0 +1,5 @@
1
+[nosetests]
2
+verbosity=1
3
+with-coverage=1
4
+cover-erase=1
5
+cover-package=hooks

+ 2
- 0
templates/icehouse/hostname View File

@@ -0,0 +1,2 @@
1
+{{ pg_hostname }}
2
+

+ 10
- 0
templates/icehouse/hosts View File

@@ -0,0 +1,10 @@
1
+127.0.0.1   localhost
2
+127.0.1.1   {{ pg_hostname }}
3
+
4
+# The following lines are desirable for IPv6 capable hosts
5
+::1     ip6-localhost ip6-loopback
6
+fe00::0 ip6-localnet
7
+ff00::0 ip6-mcastprefix
8
+ff02::1 ip6-allnodes
9
+ff02::2 ip6-allrouters
10
+

+ 6
- 0
templates/icehouse/ifcs.conf View File

@@ -0,0 +1,6 @@
1
+{{ interface }} = fabric_core host
2
+{% if ext_interface -%}
3
+{{ ext_interface }} = access_phys
4
+
5
+{% endif -%}
6
+

+ 94
- 0
templates/icehouse/network.filters View File

@@ -0,0 +1,94 @@
1
+# nova-rootwrap command filters for network nodes
2
+# This file should be owned by (and only-writeable by) the root user
3
+
4
+[Filters]
5
+# nova/virt/libvirt/vif.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap'
6
+# nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up'
7
+# nova/virt/libvirt/vif.py: 'ip', 'link', 'delete', dev
8
+# nova/network/linux_net.py: 'ip', 'addr', 'add', str(floating_ip)+'/32'i..
9
+# nova/network/linux_net.py: 'ip', 'addr', 'del', str(floating_ip)+'/32'..
10
+# nova/network/linux_net.py: 'ip', 'addr', 'add', '169.254.169.254/32',..
11
+# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', dev, 'scope',..
12
+# nova/network/linux_net.py: 'ip', 'addr', 'del/add', ip_params, dev)
13
+# nova/network/linux_net.py: 'ip', 'addr', 'del', params, fields[-1]
14
+# nova/network/linux_net.py: 'ip', 'addr', 'add', params, bridge
15
+# nova/network/linux_net.py: 'ip', '-f', 'inet6', 'addr', 'change', ..
16
+# nova/network/linux_net.py: 'ip', 'link', 'set', 'dev', dev, 'promisc',..
17
+# nova/network/linux_net.py: 'ip', 'link', 'add', 'link', bridge_if ...
18
+# nova/network/linux_net.py: 'ip', 'link', 'set', interface, address,..
19
+# nova/network/linux_net.py: 'ip', 'link', 'set', interface, 'up'
20
+# nova/network/linux_net.py: 'ip', 'link', 'set', bridge, 'up'
21
+# nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', interface, ..
22
+# nova/network/linux_net.py: 'ip', 'link', 'set', dev, address, ..
23
+# nova/network/linux_net.py: 'ip', 'link', 'set', dev, 'up'
24
+# nova/network/linux_net.py: 'ip', 'route', 'add', ..
25
+# nova/network/linux_net.py: 'ip', 'route', 'del', .
26
+# nova/network/linux_net.py: 'ip', 'route', 'show', 'dev', dev
27
+ip: CommandFilter, ip, root
28
+
29
+# nova/virt/libvirt/vif.py: 'ovs-vsctl', ...
30
+# nova/virt/libvirt/vif.py: 'ovs-vsctl', 'del-port', ...
31
+# nova/network/linux_net.py: 'ovs-vsctl', ....
32
+ovs-vsctl: CommandFilter, ovs-vsctl, root
33
+
34
+# nova/network/linux_net.py: 'ovs-ofctl', ....
35
+ovs-ofctl: CommandFilter, ovs-ofctl, root
36
+
37
+# nova/virt/libvirt/vif.py: 'ivs-ctl', ...
38
+# nova/virt/libvirt/vif.py: 'ivs-ctl', 'del-port', ...
39
+# nova/network/linux_net.py: 'ivs-ctl', ....
40
+ivs-ctl: CommandFilter, ivs-ctl, root
41
+
42
+# nova/virt/libvirt/vif.py: 'ifc_ctl', ...
43
+ifc_ctl: CommandFilter, /opt/pg/bin/ifc_ctl, root
44
+
45
+# nova/virt/libvirt/vif.py: 'ebrctl', ...
46
+ebrctl: CommandFilter, ebrctl, root
47
+
48
+# nova/virt/libvirt/vif.py: 'mm-ctl', ...
49
+mm-ctl: CommandFilter, mm-ctl, root
50
+
51
+# nova/network/linux_net.py: 'ebtables', '-D' ...
52
+# nova/network/linux_net.py: 'ebtables', '-I' ...
53
+ebtables: CommandFilter, ebtables, root
54
+ebtables_usr: CommandFilter, ebtables, root
55
+
56
+# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
57
+iptables-save: CommandFilter, iptables-save, root
58
+ip6tables-save: CommandFilter, ip6tables-save, root
59
+
60
+# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,)
61
+iptables-restore: CommandFilter, iptables-restore, root
62
+ip6tables-restore: CommandFilter, ip6tables-restore, root
63
+
64
+# nova/network/linux_net.py: 'arping', '-U', floating_ip, '-A', '-I', ...
65
+# nova/network/linux_net.py: 'arping', '-U', network_ref['dhcp_server'],..
66
+arping: CommandFilter, arping, root
67
+
68
+# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address
69
+dhcp_release: CommandFilter, dhcp_release, root
70
+
71
+# nova/network/linux_net.py: 'kill', '-9', pid
72
+# nova/network/linux_net.py: 'kill', '-HUP', pid
73
+kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
74
+
75
+# nova/network/linux_net.py: 'kill', pid
76
+kill_radvd: KillFilter, root, /usr/sbin/radvd
77
+
78
+# nova/network/linux_net.py: dnsmasq call
79
+dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq
80
+
81
+# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'..
82
+radvd: CommandFilter, radvd, root
83
+
84
+# nova/network/linux_net.py: 'brctl', 'addbr', bridge
85
+# nova/network/linux_net.py: 'brctl', 'setfd', bridge, 0
86
+# nova/network/linux_net.py: 'brctl', 'stp', bridge, 'off'
87
+# nova/network/linux_net.py: 'brctl', 'addif', bridge, interface
88
+brctl: CommandFilter, brctl, root
89
+
90
+# nova/network/linux_net.py: 'sysctl', ....
91
+sysctl: CommandFilter, sysctl, root
92
+
93
+# nova/network/linux_net.py: 'conntrack'
94
+conntrack: CommandFilter, conntrack, root

+ 11
- 0
templates/icehouse/plumgrid.conf View File

@@ -0,0 +1,11 @@
1
+plumgrid_ip={{ local_ip }}
2
+plumgrid_port=8001
3
+mgmt_dev={{ interface }}
4
+label={{ label}}
5
+plumgrid_rsync_port=2222
6
+plumgrid_rest_addr=0.0.0.0:9180
7
+fabric_mode={{ fabric_mode }}
8
+start_plumgrid_iovisor=yes
9
+start_plumgrid=`/opt/pg/scripts/pg_is_director.sh $plumgrid_ip`
10
+location=
11
+

+ 21
- 0
templates/parts/rabbitmq View File

@@ -0,0 +1,21 @@
1
+{% if rabbitmq_host or rabbitmq_hosts -%}
2
+rabbit_userid = {{ rabbitmq_user }}
3
+rabbit_virtual_host = {{ rabbitmq_virtual_host }}
4
+rabbit_password = {{ rabbitmq_password }}
5
+{% if rabbitmq_hosts -%}
6
+rabbit_hosts = {{ rabbitmq_hosts }}
7
+{% if rabbitmq_ha_queues -%}
8
+rabbit_ha_queues = True
9
+rabbit_durable_queues = False
10
+{% endif -%}
11
+{% else -%}
12
+rabbit_host = {{ rabbitmq_host }}
13
+{% endif -%}
14
+{% if rabbit_ssl_port -%}
15
+rabbit_use_ssl = True
16
+rabbit_port = {{ rabbit_ssl_port }}
17
+{% if rabbit_ssl_ca -%}
18
+kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
19
+{% endif -%}
20
+{% endif -%}
21
+{% endif -%}

+ 5
- 0
tests/00-setup View File

@@ -0,0 +1,5 @@
1
+#!/bin/bash
2
+
3
+sudo add-apt-repository ppa:juju/stable -y
4
+sudo apt-get update
5
+sudo apt-get install amulet python3-requests juju-deployer -y

+ 39
- 0
tests/14-juno View File

@@ -0,0 +1,39 @@
1
+#!/usr/bin/env python3
2
+
3
+import amulet
4
+import requests
5
+import unittest
6
+
7
+class TestDeployment(unittest.TestCase):
8
+    @classmethod
9
+    def setUpClass(cls):
10
+        cls.deployment = amulet.Deployment(series='trusty')
11
+        cls.deployment.load_bundle_file(bundle_file='files/plumgrid-gateway.yaml', deployment_name='test')
12
+        try:
13
+            cls.deployment.setup(timeout=2000)
14
+            cls.deployment.sentry.wait()
15
+        except amulet.helpers.TimeoutError:
16
+            amulet.raise_status(amulet.SKIP, msg="Environment wasn't stood up in time")
17
+        except:
18
+            raise
19
+    
20
+    def test_plumgrid_gateway_external_interface(self):
21
+        external_interface = self.deployment.services['plumgrid-gateway']['options']['external-interface']
22
+        if not external_interface:
23
+            amulet.raise_status(amulet.FAIL, msg='plumgrid external-interface parameter was not found.')
24
+        output, code = self.deployment.sentry['plumgrid-gateway/0'].run("ethtool {}".format(external_interface))
25
+        if code != 0:
26
+            amulet.raise_status(amulet.FAIL, msg='external interface not found on the host')
27
+
28
+    def test_plumgrid_gateway_started(self):
29
+        agent_state = self.deployment.sentry['plumgrid-gateway/0'].info['agent-state']
30
+        if agent_state != 'started':
31
+            amulet.raise_status(amulet.FAIL, msg='plumgrid gateway is not in a started state')
32
+
33
+    def test_plumgrid_gateway_relation(self):
34
+        relation = self.deployment.sentry['plumgrid-gateway/0'].relation('plumgrid-plugin', 'neutron-iovisor:plumgrid-plugin')
35
+        if not relation['private-address']:
36
+            amulet.raise_status(amulet.FAIL, msg='private address was not set in the plumgrid gateway relation')
37
+
38
+if __name__ == '__main__':
39
+    unittest.main()

+ 98
- 0
tests/files/plumgrid-gateway.yaml View File

@@ -0,0 +1,98 @@
1
+test:
2
+  series: 'trusty'
3
+  relations:
4
+  - - neutron-api
5
+    - neutron-iovisor
6
+  - - neutron-iovisor
7
+    - plumgrid-gateway
8
+  - - nova-cloud-controller
9
+    - nova-compute
10
+  - - glance
11
+    - nova-compute
12
+  - - nova-compute
13
+    - rabbitmq-server
14
+  - - mysql
15
+    - nova-compute
16
+  - - cinder
17
+    - nova-cloud-controller
18
+  - - nova-cloud-controller
19
+    - rabbitmq-server
20
+  - - glance
21
+    - nova-cloud-controller
22
+  - - keystone
23
+    - nova-cloud-controller
24
+  - - mysql
25
+    - nova-cloud-controller
26
+  - - neutron-api
27
+    - nova-cloud-controller
28
+  services:
29
+    cinder:
30
+      charm: cs:trusty/cinder
31
+      num_units: 1
32
+      options:
33
+        openstack-origin: cloud:trusty-juno
34
+      to: 'lxc:0'
35
+    glance:
36
+      charm: cs:trusty/glance
37
+      num_units: 1
38
+      options:
39
+        openstack-origin: cloud:trusty-juno
40
+      to: 'lxc:0'
41
+    keystone:
42
+      charm: cs:trusty/keystone
43
+      num_units: 1
44
+      options:
45
+        admin-password: plumgrid
46
+        openstack-origin: cloud:trusty-juno
47
+      to: 'lxc:0'
48
+    mysql:
49
+      charm: cs:trusty/mysql
50
+      num_units: 1
51
+      to: 'lxc:0'
52
+    neutron-api:
53
+      charm: cs:~juliann/trusty/neutron-api
54
+      num_units: 1
55
+      options:
56
+        install_keys: 'null'
57
+        install_sources: "deb http://10.22.24.200/debs ./"
58
+        neutron-plugin: "plumgrid"
59
+        neutron-security-groups: "true"
60
+        openstack-origin: "cloud:trusty-juno"
61
+        plumgrid-password: "plumgrid"
62
+        plumgrid-username: "plumgrid"
63
+        plumgrid-virtual-ip: "192.168.100.250"
64
+      to: 'lxc:0'
65
+    neutron-iovisor:
66
+      charm: cs:~juliann/trusty/neutron-iovisor
67
+      num_units: 1
68
+      options:
69
+        install_keys: 'null'
70
+        install_sources: "deb http://10.22.24.200/debs ./"
71
+      to: 'nova-compute'
72
+    plumgrid-gateway:
73
+      charm: cs:~juliann/trusty/plumgrid-gateway
74
+      num_units: 1
75
+      options:
76
+        external-interface: 'eth1'
77
+      to: 'nova-compute'
78
+    nova-cloud-controller:
79
+      charm: cs:trusty/nova-cloud-controller
80
+      num_units: 1
81
+      options:
82
+        console-access-protocol: novnc
83
+        network-manager: Neutron
84
+        openstack-origin: cloud:trusty-juno
85
+        quantum-security-groups: 'yes'
86
+      to: 'lxc:0'
87
+    nova-compute:
88
+      charm: cs:~juliann/trusty/nova-compute
89
+      num_units: 1
90
+      options:
91
+        enable-live-migration: true
92
+        enable-resize: true
93
+        migration-auth-type: ssh
94
+        openstack-origin: cloud:trusty-juno
95
+    rabbitmq-server:
96
+      charm: cs:trusty/rabbitmq-server
97
+      num_units: 1
98
+      to: 'lxc:0'

+ 4
- 0
unit_tests/__init__.py View File

@@ -0,0 +1,4 @@
1
+import sys
2
+
3
+sys.path.append('actions/')
4
+sys.path.append('hooks/')

+ 82
- 0
unit_tests/test_pg_gw_context.py View File

@@ -0,0 +1,82 @@
1
+from test_utils import CharmTestCase
2
+from mock import patch
3
+import pg_gw_context as context
4
+import charmhelpers
5
+
6
+TO_PATCH = [
7
+    #'_pg_dir_settings',
8
+    'config',
9
+    'get_unit_hostname',
10
+]
11
+
12
+
13
+def fake_context(settings):
14
+    def outer():
15
+        def inner():
16
+            return settings
17
+        return inner
18
+    return outer
19
+
20
+
21
+class PGGwContextTest(CharmTestCase):
22
+
23
+    def setUp(self):
24
+        super(PGGwContextTest, self).setUp(context, TO_PATCH)
25
+        self.config.side_effect = self.test_config.get
26
+        self.test_config.set('external-interface', 'eth1')
27
+
28
+    def tearDown(self):
29
+        super(PGGwContextTest, self).tearDown()
30
+
31
+    @patch.object(context.PGGwContext, '_ensure_packages')
32
+    @patch.object(charmhelpers.contrib.openstack.context, 'https')
33
+    @patch.object(charmhelpers.contrib.openstack.context, 'is_clustered')
34
+    @patch.object(charmhelpers.contrib.openstack.context, 'config')
35
+    @patch.object(charmhelpers.contrib.openstack.context, 'unit_private_ip')
36
+    @patch.object(charmhelpers.contrib.openstack.context, 'unit_get')
37
+    @patch.object(charmhelpers.contrib.openstack.context, 'config_flags_parser')
38
+    @patch.object(context.PGGwContext, '_save_flag_file')
39
+    @patch.object(context, '_pg_dir_settings')
40
+    @patch.object(charmhelpers.contrib.openstack.context, 'neutron_plugin_attribute')
41
+    def test_neutroncc_context_api_rel(self, _npa, _pg_dir_settings, _save_flag_file,
42
+                                       _config_flag, _unit_get, _unit_priv_ip, _config,
43
+                                       _is_clus, _https, _ens_pkgs):
44
+        def mock_npa(plugin, section, manager):
45
+            if section == "driver":
46
+                return "neutron.randomdriver"
47
+            if section == "config":
48
+                return "neutron.randomconfig"
49
+        config = {'external-interface': "eth1"}
50
+
51
+        def mock_config(key=None):
52
+            if key:
53
+                return config.get(key)
54
+
55
+            return config
56
+
57
+        self.maxDiff = None
58
+        self.config.side_effect = mock_config
59
+        _npa.side_effect = mock_npa
60
+        _unit_get.return_value = '192.168.100.201'
61
+        _unit_priv_ip.return_value = '192.168.100.201'
62
+        self.get_unit_hostname.return_value = 'node0'
63
+        _is_clus.return_value = False
64
+        _config_flag.return_value = False
65
+        _pg_dir_settings.return_value = {'pg_dir_ip': '192.168.100.201'}
66
+        napi_ctxt = context.PGGwContext()
67
+        expect = {
68
+            'ext_interface': "eth1",
69
+            'config': 'neutron.randomconfig',
70
+            'core_plugin': 'neutron.randomdriver',
71
+            'local_ip': '192.168.100.201',
72
+            'network_manager': 'neutron',
73
+            'neutron_plugin': 'plumgrid',
74
+            'neutron_security_groups': None,
75
+            'neutron_url': 'https://192.168.100.201:9696',
76
+            'pg_hostname': 'node0',
77
+            'interface': 'juju-br0',
78
+            'label': 'node0',
79
+            'fabric_mode': 'host',
80
+            'neutron_alchemy_flags': False,
81
+        }
82
+        self.assertEquals(expect, napi_ctxt())

+ 68
- 0
unit_tests/test_pg_gw_hooks.py View File

@@ -0,0 +1,68 @@
1
+from mock import MagicMock, patch
2
+
3
+from test_utils import CharmTestCase
4
+
5
+with patch('charmhelpers.core.hookenv.config') as config:
6
+    config.return_value = 'neutron'
7
+    import pg_gw_utils as utils
8
+
9
+_reg = utils.register_configs
10
+_map = utils.restart_map
11
+
12
+utils.register_configs = MagicMock()
13
+utils.restart_map = MagicMock()
14
+
15
+import pg_gw_hooks as hooks
16
+
17
+utils.register_configs = _reg
18
+utils.restart_map = _map
19
+
20
+TO_PATCH = [
21
+    #'apt_update',
22
+    #'apt_install',
23
+    #'apt_purge',
24
+    #'config',
25
+    'CONFIGS',
26
+    #'determine_packages',
27
+    #'determine_dvr_packages',
28
+    #'get_shared_secret',
29
+    #'git_install',
30
+    'log',
31
+    #'relation_ids',
32
+    #'relation_set',
33
+    #'configure_ovs',
34
+    #'use_dvr',
35
+    'ensure_files',
36
+    'stop_pg',
37
+    'restart_pg',
38
+]
39
+NEUTRON_CONF_DIR = "/etc/neutron"
40
+
41
+NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
42
+
43
+
44
+class PGGwHooksTests(CharmTestCase):
45
+
46
+    def setUp(self):
47
+        super(PGGwHooksTests, self).setUp(hooks, TO_PATCH)
48
+
49
+        #self.config.side_effect = self.test_config.get
50
+        hooks.hooks._config_save = False
51
+
52
+    def _call_hook(self, hookname):
53
+        hooks.hooks.execute([
54
+            'hooks/{}'.format(hookname)])
55
+
56
+    def test_install_hook(self):
57
+        self._call_hook('install')
58
+        self.ensure_files.assert_called_with()
59
+
60
+    def test_plumgrid_edge_joined(self):
61
+        self._call_hook('plumgrid-plugin-relation-joined')
62
+        self.ensure_files.assert_called_with()
63
+        self.CONFIGS.write_all.assert_called_with()
64
+        self.restart_pg.assert_called_with()
65
+
66
+    def test_stop(self):
67
+        self._call_hook('stop')
68
+        self.stop_pg.assert_called_with()

+ 79
- 0
unit_tests/test_pg_gw_utils.py View File

@@ -0,0 +1,79 @@
1
+from mock import MagicMock
2
+from collections import OrderedDict
3
+import charmhelpers.contrib.openstack.templating as templating
4
+
5
+templating.OSConfigRenderer = MagicMock()
6
+
7
+import pg_gw_utils as nutils
8
+
9
+from test_utils import (
10
+    CharmTestCase,
11
+)
12
+import charmhelpers.core.hookenv as hookenv
13
+
14
+
15
+TO_PATCH = [
16
+    'os_release',
17
+    'neutron_plugin_attribute',
18
+]
19
+
20
+
21
+class DummyContext():
22
+
23
+    def __init__(self, return_value):
24
+        self.return_value = return_value
25
+
26
+    def __call__(self):
27
+        return self.return_value
28
+
29
+
30
+class TestPGGwUtils(CharmTestCase):
31
+
32
+    def setUp(self):
33
+        super(TestPGGwUtils, self).setUp(nutils, TO_PATCH)
34
+        #self.config.side_effect = self.test_config.get
35
+
36
+    def tearDown(self):
37
+        # Reset cached cache
38
+        hookenv.cache = {}
39
+
40
+    def test_register_configs(self):
41
+        class _mock_OSConfigRenderer():
42
+            def __init__(self, templates_dir=None, openstack_release=None):
43
+                self.configs = []
44
+                self.ctxts = []
45
+
46
+            def register(self, config, ctxt):
47
+                self.configs.append(config)
48
+                self.ctxts.append(ctxt)
49
+
50
+        self.os_release.return_value = 'trusty'
51
+        templating.OSConfigRenderer.side_effect = _mock_OSConfigRenderer
52
+        _regconfs = nutils.register_configs()
53
+        confs = ['/var/lib/libvirt/filesystems/plumgrid/opt/pg/etc/plumgrid.conf',
54
+                 '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hostname',
55
+                 '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hosts',
56
+                 '/var/lib/libvirt/filesystems/plumgrid-data/conf/pg/ifcs.conf',
57
+                 '/etc/nova/rootwrap.d/network.filters']
58
+        self.assertItemsEqual(_regconfs.configs, confs)
59
+
60
+    def test_resource_map(self):
61
+        _map = nutils.resource_map()
62
+        svcs = ['plumgrid']
63
+        confs = [nutils.PG_CONF]
64
+        [self.assertIn(q_conf, _map.keys()) for q_conf in confs]
65
+        self.assertEqual(_map[nutils.PG_CONF]['services'], svcs)
66
+
67
+    def test_restart_map(self):
68
+        _restart_map = nutils.restart_map()
69
+        expect = OrderedDict([
70
+            (nutils.PG_CONF, ['plumgrid']),
71
+            (nutils.PGHN_CONF, ['plumgrid']),
72
+            (nutils.PGHS_CONF, ['plumgrid']),
73
+            (nutils.PGIFCS_CONF, []),
74
+            (nutils.FILTERS_CONF, []),
75
+        ])
76
+        self.assertEqual(expect, _restart_map)
77
+        for item in _restart_map:
78
+            self.assertTrue(item in _restart_map)
79
+            self.assertTrue(expect[item] == _restart_map[item])

+ 121
- 0
unit_tests/test_utils.py View File

@@ -0,0 +1,121 @@
1
+import logging
2
+import unittest
3
+import os
4
+import yaml
5
+
6
+from contextlib import contextmanager
7
+from mock import patch, MagicMock
8
+
9
+
10
+def load_config():
11
+    '''
12
+    Walk backwords from __file__ looking for config.yaml, load and return the
13
+    'options' section'
14
+    '''
15
+    config = None
16
+    f = __file__
17
+    while config is None:
18
+        d = os.path.dirname(f)
19
+        if os.path.isfile(os.path.join(d, 'config.yaml')):
20
+            config = os.path.join(d, 'config.yaml')
21
+            break
22
+        f = d
23
+
24
+    if not config:
25
+        logging.error('Could not find config.yaml in any parent directory '
26
+                      'of %s. ' % file)
27
+        raise Exception
28
+
29
+    return yaml.safe_load(open(config).read())['options']
30
+
31
+
32
+def get_default_config():
33
+    '''
34
+    Load default charm config from config.yaml return as a dict.
35
+    If no default is set in config.yaml, its value is None.
36
+    '''
37
+    default_config = {}
38
+    config = load_config()
39
+    for k, v in config.iteritems():
40
+        if 'default' in v:
41
+            default_config[k] = v['default']
42
+        else:
43
+            default_config[k] = None
44
+    return default_config
45
+
46
+
47
+class CharmTestCase(unittest.TestCase):
48
+
49
+    def setUp(self, obj, patches):
50
+        super(CharmTestCase, self).setUp()
51
+        self.patches = patches
52
+        self.obj = obj
53
+        self.test_config = TestConfig()
54
+        self.test_relation = TestRelation()
55
+        self.patch_all()
56
+
57
+    def patch(self, method):
58
+        _m = patch.object(self.obj, method)
59
+        mock = _m.start()
60
+        self.addCleanup(_m.stop)
61
+        return mock
62
+
63
+    def patch_all(self):
64
+        for method in self.patches:
65
+            setattr(self, method, self.patch(method))
66
+
67
+
68
+class TestConfig(object):
69
+
70
+    def __init__(self):
71
+        self.config = get_default_config()
72
+
73
+    def get(self, attr=None):
74
+        if not attr:
75
+            return self.get_all()
76
+        try:
77
+            return self.config[attr]
78
+        except KeyError:
79
+            return None
80
+
81
+    def get_all(self):
82
+        return self.config
83
+
84
+    def set(self, attr, value):
85
+            if attr not in self.config:
86
+                raise KeyError
87
+            self.config[attr] = value
88
+
89
+
90
+class TestRelation(object):
91
+
92
+    def __init__(self, relation_data={}):
93
+        self.relation_data = relation_data
94
+
95
+    def set(self, relation_data):
96
+        self.relation_data = relation_data
97
+
98
+    def get(self, attribute=None, unit=None, rid=None):
99
+        if attribute is None:
100
+            return self.relation_data
101
+        elif attribute in self.relation_data:
102
+            return self.relation_data[attribute]
103
+        return None
104
+
105
+
106
+@contextmanager
107
+def patch_open():
108
+    '''Patch open() to allow mocking both open() itself and the file that is
109
+    yielded.
110
+
111
+    Yields the mock for "open" and "file", respectively.'''
112
+    mock_open = MagicMock(spec=open)
113
+    mock_file = MagicMock(spec=file)
114
+
115
+    @contextmanager
116
+    def stub_open(*args, **kwargs):
117
+        mock_open(*args, **kwargs)
118
+        yield mock_file
119
+
120
+    with patch('__builtin__.open', stub_open):
121
+        yield mock_open, mock_file

Loading…
Cancel
Save