summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilal Baqar <bbaqar@plumgrid.com>2015-05-19 14:07:07 -0700
committerBilal Baqar <bbaqar@plumgrid.com>2015-05-19 14:07:07 -0700
commit2e2935412afb8c6e815a04a6e6dfca7844ea0aae (patch)
tree5257e33c505ee9d46f7ef6aa3d94318c79c6ce1f
PLUMgrid gateway initial charm
-rw-r--r--.coveragebin0 -> 7655 bytes
-rw-r--r--.project17
-rw-r--r--.pydevproject9
-rw-r--r--Makefile24
-rw-r--r--README.md16
-rw-r--r--bin/charm_helpers_sync.py253
-rw-r--r--charm-helpers-sync.yaml12
-rw-r--r--config.yaml5
-rw-r--r--copyright9
l---------hooks/config-changed1
l---------hooks/install1
l---------hooks/neutron-plugin-api-relation-broken1
l---------hooks/neutron-plugin-api-relation-changed1
l---------hooks/neutron-plugin-api-relation-departed1
l---------hooks/neutron-plugin-api-relation-joined1
-rw-r--r--hooks/pg_gw_context.py93
-rwxr-xr-xhooks/pg_gw_hooks.py47
-rw-r--r--hooks/pg_gw_utils.py128
l---------hooks/plumgrid-plugin-relation-broken1
l---------hooks/plumgrid-plugin-relation-changed1
l---------hooks/plumgrid-plugin-relation-departed1
l---------hooks/plumgrid-plugin-relation-joined1
l---------hooks/plumgrid-relation-broken1
l---------hooks/plumgrid-relation-changed1
l---------hooks/plumgrid-relation-departed1
l---------hooks/plumgrid-relation-joined1
l---------hooks/stop1
l---------hooks/upgrade-charm1
-rw-r--r--icon.svg304
-rw-r--r--metadata.yaml24
-rw-r--r--setup.cfg5
-rw-r--r--templates/icehouse/hostname2
-rw-r--r--templates/icehouse/hosts10
-rw-r--r--templates/icehouse/ifcs.conf6
-rw-r--r--templates/icehouse/network.filters94
-rw-r--r--templates/icehouse/plumgrid.conf11
-rw-r--r--templates/parts/rabbitmq21
-rwxr-xr-xtests/00-setup5
-rwxr-xr-xtests/14-juno39
-rw-r--r--tests/files/plumgrid-gateway.yaml98
-rw-r--r--unit_tests/__init__.py4
-rw-r--r--unit_tests/test_pg_gw_context.py82
-rw-r--r--unit_tests/test_pg_gw_hooks.py68
-rw-r--r--unit_tests/test_pg_gw_utils.py79
-rw-r--r--unit_tests/test_utils.py121
45 files changed, 1602 insertions, 0 deletions
diff --git a/.coverage b/.coverage
new file mode 100644
index 0000000..122a4c4
--- /dev/null
+++ b/.coverage
Binary files differ
diff --git a/.project b/.project
new file mode 100644
index 0000000..a146609
--- /dev/null
+++ b/.project
@@ -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>
diff --git a/.pydevproject b/.pydevproject
new file mode 100644
index 0000000..d6fbb94
--- /dev/null
+++ b/.pydevproject
@@ -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>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..06a02f1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
1#!/usr/bin/make
2PYTHON := /usr/bin/env python
3
4lint:
5 @flake8 --exclude hooks/charmhelpers hooks
6 @flake8 --exclude hooks/charmhelpers unit_tests
7 @charm proof
8
9unit_test:
10 @echo Starting tests...
11 @$(PYTHON) /usr/bin/nosetests --nologcapture unit_tests
12
13bin/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
18sync: 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
22publish: lint unit_test
23 bzr push lp:charms/plumgrid-gateway
24 bzr push lp:charms/trusty/plumgrid-gateway
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ca83cb5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,16 @@
1# Overview
2
3This charm provides the PLUMgrid Gateway configuration for a node.
4
5
6# Usage
7
8To 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
diff --git a/bin/charm_helpers_sync.py b/bin/charm_helpers_sync.py
new file mode 100644
index 0000000..f67fdb9
--- /dev/null
+++ b/bin/charm_helpers_sync.py
@@ -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
22import logging
23import optparse
24import os
25import subprocess
26import shutil
27import sys
28import tempfile
29import yaml
30from fnmatch import fnmatch
31
32import six
33
34CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
35
36
37def 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
44def 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
52def _module_path(module):
53 return os.path.join(*module.split('.'))
54
55
56def _src_path(src, module):
57 return os.path.join(src, 'charmhelpers', _module_path(module))
58
59
60def _dest_path(dest, module):
61 return os.path.join(dest, _module_path(module))
62
63
64def _is_pyfile(path):
65 return os.path.isfile(path + '.py')
66
67
68def 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
83def 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
96def 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
127def 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
137def 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
162def parse_sync_options(options):
163 if not options:
164 return []
165 return options.split(',')
166
167
168def 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
178def 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
196if __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)
diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml
new file mode 100644
index 0000000..9b5e79e
--- /dev/null
+++ b/charm-helpers-sync.yaml
@@ -0,0 +1,12 @@
1branch: lp:charm-helpers
2destination: hooks/charmhelpers
3include:
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
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..ae581a2
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,5 @@
1options:
2 external-interface:
3 default: eth1
4 type: string
5 description: The interface that will provide external connectivity
diff --git a/copyright b/copyright
new file mode 100644
index 0000000..d44f24c
--- /dev/null
+++ b/copyright
@@ -0,0 +1,9 @@
1Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0
2
3Files: *
4Copyright: 2012, Canonical Ltd.
5License: GPL-3
6
7License: 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'
diff --git a/hooks/config-changed b/hooks/config-changed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/config-changed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/install b/hooks/install
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/install
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/neutron-plugin-api-relation-broken b/hooks/neutron-plugin-api-relation-broken
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/neutron-plugin-api-relation-broken
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/neutron-plugin-api-relation-changed b/hooks/neutron-plugin-api-relation-changed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/neutron-plugin-api-relation-changed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/neutron-plugin-api-relation-departed b/hooks/neutron-plugin-api-relation-departed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/neutron-plugin-api-relation-departed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/neutron-plugin-api-relation-joined b/hooks/neutron-plugin-api-relation-joined
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/neutron-plugin-api-relation-joined
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/pg_gw_context.py b/hooks/pg_gw_context.py
new file mode 100644
index 0000000..b42a90e
--- /dev/null
+++ b/hooks/pg_gw_context.py
@@ -0,0 +1,93 @@
1from charmhelpers.core.hookenv import (
2 relation_ids,
3 related_units,
4 relation_get,
5 config,
6)
7from charmhelpers.contrib.openstack import context
8
9from socket import gethostname as get_unit_hostname
10
11'''
12#This function will be used to get information from neutron-api
13def _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
37def _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
53class 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
diff --git a/hooks/pg_gw_hooks.py b/hooks/pg_gw_hooks.py
new file mode 100755
index 0000000..821c54c
--- /dev/null
+++ b/hooks/pg_gw_hooks.py
@@ -0,0 +1,47 @@
1#!/usr/bin/python
2
3import sys
4
5from charmhelpers.core.hookenv import (
6 Hooks,
7 UnregisteredHookError,
8 log,
9)
10
11from pg_gw_utils import (
12 register_configs,
13 ensure_files,
14 restart_pg,
15 stop_pg,
16)
17
18hooks = Hooks()
19CONFIGS = register_configs()
20
21
22@hooks.hook()
23def install():
24 ensure_files()
25
26
27@hooks.hook('plumgrid-plugin-relation-joined')
28def plumgrid_dir():
29 ensure_files()
30 CONFIGS.write_all()
31 restart_pg()
32
33
34@hooks.hook('stop')
35def stop():
36 stop_pg()
37
38
39def main():
40 try:
41 hooks.execute(sys.argv)
42 except UnregisteredHookError as e:
43 log('Unknown hook {} - skipping.'.format(e))
44
45
46if __name__ == '__main__':
47 main()
diff --git a/hooks/pg_gw_utils.py b/hooks/pg_gw_utils.py
new file mode 100644
index 0000000..3668cc0
--- /dev/null
+++ b/hooks/pg_gw_utils.py
@@ -0,0 +1,128 @@
1from charmhelpers.contrib.openstack.neutron import neutron_plugin_attribute
2from copy import deepcopy
3from charmhelpers.core.hookenv import log
4from charmhelpers.core.host import (
5 write_file,
6)
7from charmhelpers.contrib.openstack import templating
8from collections import OrderedDict
9from charmhelpers.contrib.openstack.utils import (
10 os_release,
11)
12import pg_gw_context
13import subprocess
14import time
15
16#Dont need these right now
17NOVA_CONF_DIR = "/etc/nova"
18NEUTRON_CONF_DIR = "/etc/neutron"
19NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
20NEUTRON_DEFAULT = '/etc/default/neutron-server'
21
22#Puppet Files
23P_PGKA_CONF = '/opt/pg/etc/puppet/modules/sal/templates/keepalived.conf.erb'
24P_PG_CONF = '/opt/pg/etc/puppet/modules/plumgrid/templates/plumgrid.conf.erb'
25P_PGDEF_CONF = '/opt/pg/etc/puppet/modules/sal/templates/default.conf.erb'
26
27#Plumgrid Files
28PGKA_CONF = '/var/lib/libvirt/filesystems/plumgrid/etc/keepalived/keepalived.conf'
29PG_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/etc/plumgrid.conf'
30PGDEF_CONF = '/var/lib/libvirt/filesystems/plumgrid/opt/pg/sal/nginx/conf.d/default.conf'
31PGHN_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hostname'
32PGHS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/etc/hosts'
33PGIFCS_CONF = '/var/lib/libvirt/filesystems/plumgrid-data/conf/pg/ifcs.conf'
34IFCTL_CONF = '/var/run/plumgrid/lxc/ifc_list_gateway'
35IFCTL_P_CONF = '/var/lib/libvirt/filesystems/plumgrid/var/run/plumgrid/lxc/ifc_list_gateway'
36
37#EDGE SPECIFIC
38SUDOERS_CONF = '/etc/sudoers.d/ifc_ctl_sudoers'
39FILTERS_CONF_DIR = '/etc/nova/rootwrap.d'
40FILTERS_CONF = '%s/network.filters' % FILTERS_CONF_DIR
41
42BASE_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
65TEMPLATES = 'templates/'
66
67
68def determine_packages():
69 return neutron_plugin_attribute('plumgrid', 'packages', 'neutron')
70
71
72def 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
81def 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
90def 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
98def 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
105def 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
115def 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
121def _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)
diff --git a/hooks/plumgrid-plugin-relation-broken b/hooks/plumgrid-plugin-relation-broken
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-plugin-relation-broken
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-plugin-relation-changed b/hooks/plumgrid-plugin-relation-changed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-plugin-relation-changed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-plugin-relation-departed b/hooks/plumgrid-plugin-relation-departed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-plugin-relation-departed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-plugin-relation-joined b/hooks/plumgrid-plugin-relation-joined
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-plugin-relation-joined
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-relation-broken b/hooks/plumgrid-relation-broken
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-relation-broken
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-relation-changed b/hooks/plumgrid-relation-changed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-relation-changed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-relation-departed b/hooks/plumgrid-relation-departed
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-relation-departed
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/plumgrid-relation-joined b/hooks/plumgrid-relation-joined
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/plumgrid-relation-joined
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/stop b/hooks/stop
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/stop
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/hooks/upgrade-charm b/hooks/upgrade-charm
new file mode 120000
index 0000000..3aec9ba
--- /dev/null
+++ b/hooks/upgrade-charm
@@ -0,0 +1 @@
pg_gw_hooks.py \ No newline at end of file
diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..44e925a
--- /dev/null
+++ b/icon.svg
@@ -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>
diff --git a/metadata.yaml b/metadata.yaml
new file mode 100644
index 0000000..ceed1b4
--- /dev/null
+++ b/metadata.yaml
@@ -0,0 +1,24 @@
1name: plumgrid-gateway
2subordinate: false
3maintainer: Bilal Baqar <bbaqar@plumgrid.com>
4summary: "OpenStack Neutron OpenvSwitch Agent"
5description: |
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
16tags:
17 - openstack
18requires:
19 plumgrid-plugin:
20 interface: plumgrid-plugin
21 plumgrid:
22 interface: plumgrid
23 neutron-plugin-api:
24 interface: neutron-plugin-api
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..72c20b2
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
1[nosetests]
2verbosity=1
3with-coverage=1
4cover-erase=1
5cover-package=hooks
diff --git a/templates/icehouse/hostname b/templates/icehouse/hostname
new file mode 100644
index 0000000..1712a7b
--- /dev/null
+++ b/templates/icehouse/hostname
@@ -0,0 +1,2 @@
1{{ pg_hostname }}
2
diff --git a/templates/icehouse/hosts b/templates/icehouse/hosts
new file mode 100644
index 0000000..99e3be5
--- /dev/null
+++ b/templates/icehouse/hosts
@@ -0,0 +1,10 @@
1127.0.0.1 localhost
2127.0.1.1 {{ pg_hostname }}
3
4# The following lines are desirable for IPv6 capable hosts
5::1 ip6-localhost ip6-loopback
6fe00::0 ip6-localnet
7ff00::0 ip6-mcastprefix
8ff02::1 ip6-allnodes
9ff02::2 ip6-allrouters
10
diff --git a/templates/icehouse/ifcs.conf b/templates/icehouse/ifcs.conf
new file mode 100644
index 0000000..2c5794e
--- /dev/null
+++ b/templates/icehouse/ifcs.conf
@@ -0,0 +1,6 @@
1{{ interface }} = fabric_core host
2{% if ext_interface -%}
3{{ ext_interface }} = access_phys
4
5{% endif -%}
6
diff --git a/templates/icehouse/network.filters b/templates/icehouse/network.filters
new file mode 100644
index 0000000..568e8d4
--- /dev/null
+++ b/templates/icehouse/network.filters
@@ -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
27ip: 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', ....
32ovs-vsctl: CommandFilter, ovs-vsctl, root
33
34# nova/network/linux_net.py: 'ovs-ofctl', ....
35ovs-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', ....
40ivs-ctl: CommandFilter, ivs-ctl, root
41
42# nova/virt/libvirt/vif.py: 'ifc_ctl', ...
43ifc_ctl: CommandFilter, /opt/pg/bin/ifc_ctl, root
44
45# nova/virt/libvirt/vif.py: 'ebrctl', ...
46ebrctl: CommandFilter, ebrctl, root
47
48# nova/virt/libvirt/vif.py: 'mm-ctl', ...
49mm-ctl: CommandFilter, mm-ctl, root
50
51# nova/network/linux_net.py: 'ebtables', '-D' ...
52# nova/network/linux_net.py: 'ebtables', '-I' ...
53ebtables: CommandFilter, ebtables, root
54ebtables_usr: CommandFilter, ebtables, root
55
56# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ...
57iptables-save: CommandFilter, iptables-save, root
58ip6tables-save: CommandFilter, ip6tables-save, root
59
60# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,)
61iptables-restore: CommandFilter, iptables-restore, root
62ip6tables-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'],..
66arping: CommandFilter, arping, root
67
68# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address
69dhcp_release: CommandFilter, dhcp_release, root
70
71# nova/network/linux_net.py: 'kill', '-9', pid
72# nova/network/linux_net.py: 'kill', '-HUP', pid
73kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
74
75# nova/network/linux_net.py: 'kill', pid
76kill_radvd: KillFilter, root, /usr/sbin/radvd
77
78# nova/network/linux_net.py: dnsmasq call
79dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq
80
81# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'..
82radvd: 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
88brctl: CommandFilter, brctl, root
89
90# nova/network/linux_net.py: 'sysctl', ....
91sysctl: CommandFilter, sysctl, root
92
93# nova/network/linux_net.py: 'conntrack'
94conntrack: CommandFilter, conntrack, root
diff --git a/templates/icehouse/plumgrid.conf b/templates/icehouse/plumgrid.conf
new file mode 100644
index 0000000..52a9dc8
--- /dev/null
+++ b/templates/icehouse/plumgrid.conf
@@ -0,0 +1,11 @@
1plumgrid_ip={{ local_ip }}
2plumgrid_port=8001
3mgmt_dev={{ interface }}
4label={{ label}}
5plumgrid_rsync_port=2222
6plumgrid_rest_addr=0.0.0.0:9180
7fabric_mode={{ fabric_mode }}
8start_plumgrid_iovisor=yes
9start_plumgrid=`/opt/pg/scripts/pg_is_director.sh $plumgrid_ip`
10location=
11
diff --git a/templates/parts/rabbitmq b/templates/parts/rabbitmq
new file mode 100644
index 0000000..7aabc51
--- /dev/null
+++ b/templates/parts/rabbitmq
@@ -0,0 +1,21 @@
1{% if rabbitmq_host or rabbitmq_hosts -%}
2rabbit_userid = {{ rabbitmq_user }}
3rabbit_virtual_host = {{ rabbitmq_virtual_host }}
4rabbit_password = {{ rabbitmq_password }}
5{% if rabbitmq_hosts -%}
6rabbit_hosts = {{ rabbitmq_hosts }}
7{% if rabbitmq_ha_queues -%}
8rabbit_ha_queues = True
9rabbit_durable_queues = False
10{% endif -%}
11{% else -%}
12rabbit_host = {{ rabbitmq_host }}
13{% endif -%}
14{% if rabbit_ssl_port -%}
15rabbit_use_ssl = True
16rabbit_port = {{ rabbit_ssl_port }}
17{% if rabbit_ssl_ca -%}
18kombu_ssl_ca_certs = {{ rabbit_ssl_ca }}
19{% endif -%}
20{% endif -%}
21{% endif -%}
diff --git a/tests/00-setup b/tests/00-setup
new file mode 100755
index 0000000..858fe13
--- /dev/null
+++ b/tests/00-setup
@@ -0,0 +1,5 @@
1#!/bin/bash
2
3sudo add-apt-repository ppa:juju/stable -y
4sudo apt-get update
5sudo apt-get install amulet python3-requests juju-deployer -y
diff --git a/tests/14-juno b/tests/14-juno
new file mode 100755
index 0000000..f984cd9
--- /dev/null
+++ b/tests/14-juno
@@ -0,0 +1,39 @@
1#!/usr/bin/env python3
2
3import amulet
4import requests
5import unittest
6
7class 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
38if __name__ == '__main__':
39 unittest.main()
diff --git a/tests/files/plumgrid-gateway.yaml b/tests/files/plumgrid-gateway.yaml
new file mode 100644
index 0000000..4bb13bd
--- /dev/null
+++ b/tests/files/plumgrid-gateway.yaml
@@ -0,0 +1,98 @@
1test:
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'
diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py
new file mode 100644
index 0000000..43aa361
--- /dev/null
+++ b/unit_tests/__init__.py
@@ -0,0 +1,4 @@
1import sys
2
3sys.path.append('actions/')
4sys.path.append('hooks/')
diff --git a/unit_tests/test_pg_gw_context.py b/unit_tests/test_pg_gw_context.py
new file mode 100644
index 0000000..40b4b4a
--- /dev/null
+++ b/unit_tests/test_pg_gw_context.py
@@ -0,0 +1,82 @@
1from test_utils import CharmTestCase
2from mock import patch
3import pg_gw_context as context
4import charmhelpers
5
6TO_PATCH = [
7 #'_pg_dir_settings',
8 'config',
9 'get_unit_hostname',
10]
11
12
13def fake_context(settings):
14 def outer():
15 def inner():
16 return settings
17 return inner
18 return outer
19
20
21class 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())
diff --git a/unit_tests/test_pg_gw_hooks.py b/unit_tests/test_pg_gw_hooks.py
new file mode 100644
index 0000000..d4cbcf5
--- /dev/null
+++ b/unit_tests/test_pg_gw_hooks.py
@@ -0,0 +1,68 @@
1from mock import MagicMock, patch
2
3from test_utils import CharmTestCase
4
5with 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
12utils.register_configs = MagicMock()
13utils.restart_map = MagicMock()
14
15import pg_gw_hooks as hooks
16
17utils.register_configs = _reg
18utils.restart_map = _map
19
20TO_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]
39NEUTRON_CONF_DIR = "/etc/neutron"
40
41NEUTRON_CONF = '%s/neutron.conf' % NEUTRON_CONF_DIR
42
43
44class 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()
diff --git a/unit_tests/test_pg_gw_utils.py b/unit_tests/test_pg_gw_utils.py
new file mode 100644
index 0000000..a9e9cbc
--- /dev/null
+++ b/unit_tests/test_pg_gw_utils.py
@@ -0,0 +1,79 @@
1from mock import MagicMock
2from collections import OrderedDict
3import charmhelpers.contrib.openstack.templating as templating
4
5templating.OSConfigRenderer = MagicMock()
6
7import pg_gw_utils as nutils
8
9from test_utils import (
10 CharmTestCase,
11)
12import charmhelpers.core.hookenv as hookenv
13
14
15TO_PATCH = [
16 'os_release',
17 'neutron_plugin_attribute',
18]
19
20
21class 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
30class 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])
diff --git a/unit_tests/test_utils.py b/unit_tests/test_utils.py
new file mode 100644
index 0000000..86ee0f7
--- /dev/null
+++ b/unit_tests/test_utils.py
@@ -0,0 +1,121 @@
1import logging
2import unittest
3import os
4import yaml
5
6from contextlib import contextmanager
7from mock import patch, MagicMock
8
9
10def 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
32def 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
47class 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
68class 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
90class 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
107def 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