Merge lp:~gnuoy/charms/precise/quantum-gateway/control-mtu into lp:~charmers/charms/precise/quantum-gateway/trunk

Proposed by Liam Young
Status: Rejected
Rejected by: James Page
Proposed branch: lp:~gnuoy/charms/precise/quantum-gateway/control-mtu
Merge into: lp:~charmers/charms/precise/quantum-gateway/trunk
Prerequisite: lp:~gnuoy/charms/precise/quantum-gateway/external-nets
Diff against target: 534 lines (+149/-33)
5 files modified
config.yaml (+5/-0)
hooks/charmhelpers/core/hookenv.py (+78/-23)
hooks/charmhelpers/core/host.py (+49/-9)
hooks/quantum_hooks.py (+3/-0)
hooks/quantum_utils.py (+14/-1)
To merge this branch: bzr merge lp:~gnuoy/charms/precise/quantum-gateway/control-mtu
Reviewer Review Type Date Requested Status
James Page Disapprove
Review via email: mp+195230@code.launchpad.net

Description of the change

As per http://techbackground.blogspot.co.uk/2013/06/path-mtu-discovery-and-gre.html this branch allows the mtu on the eth and bond interfaces to be set.

To post a comment you must log in.
44. By Liam Young

Fix text conflicts

Revision history for this message
James Page (james-page) wrote :

Discussed with liam and we agreed that the correct way todo this was during network interface configuration during install via MAAS.

review: Disapprove

Unmerged revisions

44. By Liam Young

Fix text conflicts

43. By Liam Young

Add ability to control mtu on nics and sync charmhelpers (to pull in nic mtu functions). I've only pulled in the charmhelpers core update as doing the full sync caused import failures on an unconnected ImportError: No module named storage.linux.lvm

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'config.yaml'
2--- config.yaml 2013-11-14 14:22:58 +0000
3+++ config.yaml 2013-11-14 14:22:58 +0000
4@@ -42,6 +42,11 @@
5 Optional configuration to set the external_network_id. Only needed when
6 configuring multiple external networks and should be used in conjunction
7 with run_internal_router.
8+ set-nic-mtu:
9+ default: ""
10+ type: string
11+ description: |
12+ Set mtu on eth and bond devices to given value. Do nothing if unset
13 rabbit-user:
14 type: string
15 default: neutron
16
17=== modified file 'hooks/charmhelpers/core/hookenv.py'
18--- hooks/charmhelpers/core/hookenv.py 2013-07-19 09:46:25 +0000
19+++ hooks/charmhelpers/core/hookenv.py 2013-11-14 14:22:58 +0000
20@@ -9,6 +9,7 @@
21 import yaml
22 import subprocess
23 import UserDict
24+from subprocess import CalledProcessError
25
26 CRITICAL = "CRITICAL"
27 ERROR = "ERROR"
28@@ -21,7 +22,7 @@
29
30
31 def cached(func):
32- ''' Cache return values for multiple executions of func + args
33+ """Cache return values for multiple executions of func + args
34
35 For example:
36
37@@ -32,7 +33,7 @@
38 unit_get('test')
39
40 will cache the result of unit_get + 'test' for future calls.
41- '''
42+ """
43 def wrapper(*args, **kwargs):
44 global cache
45 key = str((func, args, kwargs))
46@@ -46,8 +47,8 @@
47
48
49 def flush(key):
50- ''' Flushes any entries from function cache where the
51- key is found in the function+args '''
52+ """Flushes any entries from function cache where the
53+ key is found in the function+args """
54 flush_list = []
55 for item in cache:
56 if key in item:
57@@ -57,7 +58,7 @@
58
59
60 def log(message, level=None):
61- "Write a message to the juju log"
62+ """Write a message to the juju log"""
63 command = ['juju-log']
64 if level:
65 command += ['-l', level]
66@@ -66,7 +67,7 @@
67
68
69 class Serializable(UserDict.IterableUserDict):
70- "Wrapper, an object that can be serialized to yaml or json"
71+ """Wrapper, an object that can be serialized to yaml or json"""
72
73 def __init__(self, obj):
74 # wrap the object
75@@ -96,11 +97,11 @@
76 self.data = state
77
78 def json(self):
79- "Serialize the object to json"
80+ """Serialize the object to json"""
81 return json.dumps(self.data)
82
83 def yaml(self):
84- "Serialize the object to yaml"
85+ """Serialize the object to yaml"""
86 return yaml.dump(self.data)
87
88
89@@ -119,38 +120,38 @@
90
91
92 def in_relation_hook():
93- "Determine whether we're running in a relation hook"
94+ """Determine whether we're running in a relation hook"""
95 return 'JUJU_RELATION' in os.environ
96
97
98 def relation_type():
99- "The scope for the current relation hook"
100+ """The scope for the current relation hook"""
101 return os.environ.get('JUJU_RELATION', None)
102
103
104 def relation_id():
105- "The relation ID for the current relation hook"
106+ """The relation ID for the current relation hook"""
107 return os.environ.get('JUJU_RELATION_ID', None)
108
109
110 def local_unit():
111- "Local unit ID"
112+ """Local unit ID"""
113 return os.environ['JUJU_UNIT_NAME']
114
115
116 def remote_unit():
117- "The remote unit for the current relation hook"
118+ """The remote unit for the current relation hook"""
119 return os.environ['JUJU_REMOTE_UNIT']
120
121
122 def service_name():
123- "The name service group this unit belongs to"
124+ """The name service group this unit belongs to"""
125 return local_unit().split('/')[0]
126
127
128 @cached
129 def config(scope=None):
130- "Juju charm configuration"
131+ """Juju charm configuration"""
132 config_cmd_line = ['config-get']
133 if scope is not None:
134 config_cmd_line.append(scope)
135@@ -163,6 +164,7 @@
136
137 @cached
138 def relation_get(attribute=None, unit=None, rid=None):
139+ """Get relation information"""
140 _args = ['relation-get', '--format=json']
141 if rid:
142 _args.append('-r')
143@@ -174,9 +176,14 @@
144 return json.loads(subprocess.check_output(_args))
145 except ValueError:
146 return None
147+ except CalledProcessError, e:
148+ if e.returncode == 2:
149+ return None
150+ raise
151
152
153 def relation_set(relation_id=None, relation_settings={}, **kwargs):
154+ """Set relation information for the current unit"""
155 relation_cmd_line = ['relation-set']
156 if relation_id is not None:
157 relation_cmd_line.extend(('-r', relation_id))
158@@ -192,7 +199,7 @@
159
160 @cached
161 def relation_ids(reltype=None):
162- "A list of relation_ids"
163+ """A list of relation_ids"""
164 reltype = reltype or relation_type()
165 relid_cmd_line = ['relation-ids', '--format=json']
166 if reltype is not None:
167@@ -203,7 +210,7 @@
168
169 @cached
170 def related_units(relid=None):
171- "A list of related units"
172+ """A list of related units"""
173 relid = relid or relation_id()
174 units_cmd_line = ['relation-list', '--format=json']
175 if relid is not None:
176@@ -213,7 +220,7 @@
177
178 @cached
179 def relation_for_unit(unit=None, rid=None):
180- "Get the json represenation of a unit's relation"
181+ """Get the json represenation of a unit's relation"""
182 unit = unit or remote_unit()
183 relation = relation_get(unit=unit, rid=rid)
184 for key in relation:
185@@ -225,7 +232,7 @@
186
187 @cached
188 def relations_for_id(relid=None):
189- "Get relations of a specific relation ID"
190+ """Get relations of a specific relation ID"""
191 relation_data = []
192 relid = relid or relation_ids()
193 for unit in related_units(relid):
194@@ -237,7 +244,7 @@
195
196 @cached
197 def relations_of_type(reltype=None):
198- "Get relations of a specific type"
199+ """Get relations of a specific type"""
200 relation_data = []
201 reltype = reltype or relation_type()
202 for relid in relation_ids(reltype):
203@@ -249,7 +256,7 @@
204
205 @cached
206 def relation_types():
207- "Get a list of relation types supported by this charm"
208+ """Get a list of relation types supported by this charm"""
209 charmdir = os.environ.get('CHARM_DIR', '')
210 mdf = open(os.path.join(charmdir, 'metadata.yaml'))
211 md = yaml.safe_load(mdf)
212@@ -264,6 +271,7 @@
213
214 @cached
215 def relations():
216+ """Get a nested dictionary of relation data for all related units"""
217 rels = {}
218 for reltype in relation_types():
219 relids = {}
220@@ -277,15 +285,35 @@
221 return rels
222
223
224+@cached
225+def is_relation_made(relation, keys='private-address'):
226+ '''
227+ Determine whether a relation is established by checking for
228+ presence of key(s). If a list of keys is provided, they
229+ must all be present for the relation to be identified as made
230+ '''
231+ if isinstance(keys, str):
232+ keys = [keys]
233+ for r_id in relation_ids(relation):
234+ for unit in related_units(r_id):
235+ context = {}
236+ for k in keys:
237+ context[k] = relation_get(k, rid=r_id,
238+ unit=unit)
239+ if None not in context.values():
240+ return True
241+ return False
242+
243+
244 def open_port(port, protocol="TCP"):
245- "Open a service network port"
246+ """Open a service network port"""
247 _args = ['open-port']
248 _args.append('{}/{}'.format(port, protocol))
249 subprocess.check_call(_args)
250
251
252 def close_port(port, protocol="TCP"):
253- "Close a service network port"
254+ """Close a service network port"""
255 _args = ['close-port']
256 _args.append('{}/{}'.format(port, protocol))
257 subprocess.check_call(_args)
258@@ -293,6 +321,7 @@
259
260 @cached
261 def unit_get(attribute):
262+ """Get the unit ID for the remote unit"""
263 _args = ['unit-get', '--format=json', attribute]
264 try:
265 return json.loads(subprocess.check_output(_args))
266@@ -301,22 +330,46 @@
267
268
269 def unit_private_ip():
270+ """Get this unit's private IP address"""
271 return unit_get('private-address')
272
273
274 class UnregisteredHookError(Exception):
275+ """Raised when an undefined hook is called"""
276 pass
277
278
279 class Hooks(object):
280+ """A convenient handler for hook functions.
281+
282+ Example:
283+ hooks = Hooks()
284+
285+ # register a hook, taking its name from the function name
286+ @hooks.hook()
287+ def install():
288+ ...
289+
290+ # register a hook, providing a custom hook name
291+ @hooks.hook("config-changed")
292+ def config_changed():
293+ ...
294+
295+ if __name__ == "__main__":
296+ # execute a hook based on the name the program is called by
297+ hooks.execute(sys.argv)
298+ """
299+
300 def __init__(self):
301 super(Hooks, self).__init__()
302 self._hooks = {}
303
304 def register(self, name, function):
305+ """Register a hook"""
306 self._hooks[name] = function
307
308 def execute(self, args):
309+ """Execute a registered hook based on args[0]"""
310 hook_name = os.path.basename(args[0])
311 if hook_name in self._hooks:
312 self._hooks[hook_name]()
313@@ -324,6 +377,7 @@
314 raise UnregisteredHookError(hook_name)
315
316 def hook(self, *hook_names):
317+ """Decorator, registering them as hooks"""
318 def wrapper(decorated):
319 for hook_name in hook_names:
320 self.register(hook_name, decorated)
321@@ -337,4 +391,5 @@
322
323
324 def charm_dir():
325+ """Return the root directory of the current charm"""
326 return os.environ.get('CHARM_DIR')
327
328=== modified file 'hooks/charmhelpers/core/host.py'
329--- hooks/charmhelpers/core/host.py 2013-09-02 16:14:27 +0000
330+++ hooks/charmhelpers/core/host.py 2013-11-14 14:22:58 +0000
331@@ -19,18 +19,22 @@
332
333
334 def service_start(service_name):
335+ """Start a system service"""
336 return service('start', service_name)
337
338
339 def service_stop(service_name):
340+ """Stop a system service"""
341 return service('stop', service_name)
342
343
344 def service_restart(service_name):
345+ """Restart a system service"""
346 return service('restart', service_name)
347
348
349 def service_reload(service_name, restart_on_failure=False):
350+ """Reload a system service, optionally falling back to restart if reload fails"""
351 service_result = service('reload', service_name)
352 if not service_result and restart_on_failure:
353 service_result = service('restart', service_name)
354@@ -38,11 +42,13 @@
355
356
357 def service(action, service_name):
358+ """Control a system service"""
359 cmd = ['service', service_name, action]
360 return subprocess.call(cmd) == 0
361
362
363 def service_running(service):
364+ """Determine whether a system service is running"""
365 try:
366 output = subprocess.check_output(['service', service, 'status'])
367 except subprocess.CalledProcessError:
368@@ -55,7 +61,7 @@
369
370
371 def adduser(username, password=None, shell='/bin/bash', system_user=False):
372- """Add a user"""
373+ """Add a user to the system"""
374 try:
375 user_info = pwd.getpwnam(username)
376 log('user {0} already exists!'.format(username))
377@@ -138,7 +144,7 @@
378
379
380 def mount(device, mountpoint, options=None, persist=False):
381- '''Mount a filesystem'''
382+ """Mount a filesystem at a particular mountpoint"""
383 cmd_args = ['mount']
384 if options is not None:
385 cmd_args.extend(['-o', options])
386@@ -155,7 +161,7 @@
387
388
389 def umount(mountpoint, persist=False):
390- '''Unmount a filesystem'''
391+ """Unmount a filesystem"""
392 cmd_args = ['umount', mountpoint]
393 try:
394 subprocess.check_output(cmd_args)
395@@ -169,7 +175,7 @@
396
397
398 def mounts():
399- '''List of all mounted volumes as [[mountpoint,device],[...]]'''
400+ """Get a list of all mounted volumes as [[mountpoint,device],[...]]"""
401 with open('/proc/mounts') as f:
402 # [['/mount/point','/dev/path'],[...]]
403 system_mounts = [m[1::-1] for m in [l.strip().split()
404@@ -178,7 +184,7 @@
405
406
407 def file_hash(path):
408- ''' Generate a md5 hash of the contents of 'path' or None if not found '''
409+ """Generate a md5 hash of the contents of 'path' or None if not found """
410 if os.path.exists(path):
411 h = hashlib.md5()
412 with open(path, 'r') as source:
413@@ -189,7 +195,7 @@
414
415
416 def restart_on_change(restart_map):
417- ''' Restart services based on configuration files changing
418+ """Restart services based on configuration files changing
419
420 This function is used a decorator, for example
421
422@@ -202,7 +208,7 @@
423 In this example, the cinder-api and cinder-volume services
424 would be restarted if /etc/ceph/ceph.conf is changed by the
425 ceph_client_changed function.
426- '''
427+ """
428 def wrap(f):
429 def wrapped_f(*args):
430 checksums = {}
431@@ -220,7 +226,7 @@
432
433
434 def lsb_release():
435- '''Return /etc/lsb-release in a dict'''
436+ """Return /etc/lsb-release in a dict"""
437 d = {}
438 with open('/etc/lsb-release', 'r') as lsb:
439 for l in lsb:
440@@ -230,7 +236,7 @@
441
442
443 def pwgen(length=None):
444- '''Generate a random pasword.'''
445+ """Generate a random pasword."""
446 if length is None:
447 length = random.choice(range(35, 45))
448 alphanumeric_chars = [
449@@ -239,3 +245,37 @@
450 random_chars = [
451 random.choice(alphanumeric_chars) for _ in range(length)]
452 return(''.join(random_chars))
453+
454+
455+def list_nics(nic_type):
456+ '''Return a list of nics of given type(s)'''
457+ if isinstance(nic_type, basestring):
458+ int_types = [nic_type]
459+ else:
460+ int_types = nic_type
461+ interfaces = []
462+ for int_type in int_types:
463+ cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
464+ ip_output = subprocess.check_output(cmd).split('\n')
465+ ip_output = (line for line in ip_output if line)
466+ for line in ip_output:
467+ if line.split()[1].startswith(int_type):
468+ interfaces.append(line.split()[1].replace(":", ""))
469+ return interfaces
470+
471+
472+def set_nic_mtu(nic, mtu):
473+ '''Set MTU on a network interface'''
474+ cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
475+ subprocess.check_call(cmd)
476+
477+
478+def get_nic_mtu(nic):
479+ cmd = ['ip', 'addr', 'show', nic]
480+ ip_output = subprocess.check_output(cmd).split('\n')
481+ mtu = ""
482+ for line in ip_output:
483+ words = line.split()
484+ if 'mtu' in words:
485+ mtu = words[words.index("mtu") + 1]
486+ return mtu
487
488=== modified file 'hooks/quantum_hooks.py'
489--- hooks/quantum_hooks.py 2013-11-14 14:22:58 +0000
490+++ hooks/quantum_hooks.py 2013-11-14 14:22:58 +0000
491@@ -41,6 +41,7 @@
492 valid_plugin,
493 configure_ovs,
494 reassign_agent_resources,
495+ set_mtu_on_nics,
496 )
497 from quantum_contexts import (
498 DB_USER, QUANTUM_DB,
499@@ -81,6 +82,8 @@
500 else:
501 log('Please provide a valid plugin config', level=ERROR)
502 sys.exit(1)
503+ if config('set-nic-mtu'):
504+ set_mtu_on_nics(config('set-nic-mtu'))
505
506
507 @hooks.hook('upgrade-charm')
508
509=== modified file 'hooks/quantum_utils.py'
510--- hooks/quantum_utils.py 2013-11-14 14:22:58 +0000
511+++ hooks/quantum_utils.py 2013-11-14 14:22:58 +0000
512@@ -1,4 +1,10 @@
513-from charmhelpers.core.host import service_running
514+from charmhelpers.core.host import (
515+ service_running,
516+ list_nics,
517+ get_nic_mtu,
518+ set_nic_mtu,
519+)
520+
521 from charmhelpers.core.hookenv import (
522 log,
523 config,
524@@ -408,3 +414,10 @@
525 add_bridge_port(EXT_BRIDGE, ext_port)
526 if config('plugin') == NVP:
527 add_bridge(INT_BRIDGE)
528+
529+
530+def set_mtu_on_nics(mtu):
531+ for nic in list_nics(['bond', 'eth']):
532+ if not get_nic_mtu(nic) == mtu:
533+ log('Setting %s to %s' % (nic, mtu))
534+ set_nic_mtu(nic, mtu)

Subscribers

People subscribed via source and target branches

to all changes: