Merge lp:~james-page/charm-helpers/ovs-helper into lp:charm-helpers

Proposed by James Page
Status: Merged
Merged at revision: 39
Proposed branch: lp:~james-page/charm-helpers/ovs-helper
Merge into: lp:charm-helpers
Diff against target: 288 lines (+274/-0)
2 files modified
charmhelpers/contrib/network/ovs/__init__.py (+72/-0)
tests/contrib/network/test_ovs.py (+202/-0)
To merge this branch: bzr merge lp:~james-page/charm-helpers/ovs-helper
Reviewer Review Type Date Requested Status
Adam Gandelman (community) Approve
Review via email: mp+172296@code.launchpad.net

Description of the change

Various helpers for interacting with OpenvSwitch.

To post a comment you must log in.
Revision history for this message
Adam Gandelman (gandelman-a) wrote :

Simple utility wrappers for OVS CLI commands with good tests. LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'charmhelpers/contrib/network'
=== added file 'charmhelpers/contrib/network/__init__.py'
=== added directory 'charmhelpers/contrib/network/ovs'
=== added file 'charmhelpers/contrib/network/ovs/__init__.py'
--- charmhelpers/contrib/network/ovs/__init__.py 1970-01-01 00:00:00 +0000
+++ charmhelpers/contrib/network/ovs/__init__.py 2013-07-01 10:37:46 +0000
@@ -0,0 +1,72 @@
1''' Helpers for interacting with OpenvSwitch '''
2import subprocess
3import os
4from charmhelpers.core.hookenv import (
5 log, WARNING
6)
7from charmhelpers.core.host import (
8 service
9)
10
11
12def add_bridge(name):
13 ''' Add the named bridge to openvswitch '''
14 log('Creating bridge {}'.format(name))
15 subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-br", name])
16
17
18def del_bridge(name):
19 ''' Delete the named bridge from openvswitch '''
20 log('Deleting bridge {}'.format(name))
21 subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-br", name])
22
23
24def add_bridge_port(name, port):
25 ''' Add a port to the named openvswitch bridge '''
26 log('Adding port {} to bridge {}'.format(port, name))
27 subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-port",
28 name, port])
29 subprocess.check_call(["ip", "link", "set", port, "up"])
30
31
32def del_bridge_port(name, port):
33 ''' Delete a port from the named openvswitch bridge '''
34 log('Deleting port {} from bridge {}'.format(port, name))
35 subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-port",
36 name, port])
37 subprocess.check_call(["ip", "link", "set", port, "down"])
38
39
40def set_manager(manager):
41 ''' Set the controller for the local openvswitch '''
42 log('Setting manager for local ovs to {}'.format(manager))
43 subprocess.check_call(['ovs-vsctl', 'set-manager',
44 'ssl:{}'.format(manager)])
45
46
47CERT_PATH = '/etc/openvswitch/ovsclient-cert.pem'
48
49
50def get_certificate():
51 ''' Read openvswitch certificate from disk '''
52 if os.path.exists(CERT_PATH):
53 log('Reading ovs certificate from {}'.format(CERT_PATH))
54 with open(CERT_PATH, 'r') as cert:
55 full_cert = cert.read()
56 begin_marker = "-----BEGIN CERTIFICATE-----"
57 end_marker = "-----END CERTIFICATE-----"
58 begin_index = full_cert.find(begin_marker)
59 end_index = full_cert.rfind(end_marker)
60 if end_index == -1 or begin_index == -1:
61 raise RuntimeError("Certificate does not contain valid begin"
62 " and end markers.")
63 full_cert = full_cert[begin_index:(end_index + len(end_marker))]
64 return full_cert
65 else:
66 log('Certificate not found', level=WARNING)
67 return None
68
69
70def full_restart():
71 ''' Full restart and reload of openvswitch '''
72 service('force-reload-kmod', 'openvswitch-switch')
073
=== added directory 'tests/contrib/network'
=== added file 'tests/contrib/network/__init__.py'
=== added file 'tests/contrib/network/test_ovs.py'
--- tests/contrib/network/test_ovs.py 1970-01-01 00:00:00 +0000
+++ tests/contrib/network/test_ovs.py 2013-07-01 10:37:46 +0000
@@ -0,0 +1,202 @@
1from contextlib import contextmanager
2from mock import patch, call, MagicMock
3from testtools import TestCase
4
5import charmhelpers.contrib.network.ovs as ovs
6
7
8@contextmanager
9def patch_open():
10 '''Patch open() to allow mocking both open() itself and the file that is
11 yielded.
12
13 Yields the mock for "open" and "file", respectively.'''
14 mock_open = MagicMock(spec=open)
15 mock_file = MagicMock(spec=file)
16
17 @contextmanager
18 def stub_open(*args, **kwargs):
19 mock_open(*args, **kwargs)
20 yield mock_file
21
22 with patch('__builtin__.open', stub_open):
23 yield mock_open, mock_file
24
25GOOD_CERT = '''Certificate:
26 Data:
27 Version: 1 (0x0)
28 Serial Number: 13798680962510501282 (0xbf7ec33a136235a2)
29 Signature Algorithm: sha1WithRSAEncryption
30 Issuer: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
31 Validity
32 Not Before: Jun 28 17:02:19 2013 GMT
33 Not After : Jun 28 17:02:19 2019 GMT
34 Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch
35 Subject Public Key Info:
36 Public Key Algorithm: rsaEncryption
37 Public-Key: (2048 bit)
38 Modulus:
39 00:e8:a7:db:0a:6d:c0:16:4a:14:96:1d:74:91:15:
40 64:3f:ae:2a:54:be:2a:fe:10:14:9a:73:39:d8:58:
41 74:7f:ab:d5:f2:39:aa:9a:27:7c:31:82:f8:74:42:
42 46:8d:c5:3b:42:55:52:be:75:7f:a5:b1:ec:d5:29:
43 9f:62:0e:de:31:27:2b:95:1f:24:0d:ca:8c:48:30:
44 96:9f:ba:b7:9d:eb:c1:bd:93:05:e3:d8:ca:66:5a:
45 e9:cb:a5:7a:3a:8d:27:e2:05:9d:88:fc:a9:ef:af:
46 47:4c:66:ce:c6:43:73:1a:85:f4:5f:b9:53:5b:29:
47 f3:c3:23:1f:0c:20:95:11:50:71:b2:f6:01:23:3f:
48 66:0f:5c:43:c2:90:fb:e5:98:73:98:e9:38:bb:1f:
49 1b:89:97:1e:dc:d7:98:07:68:32:ec:da:1d:69:0b:
50 e2:df:40:fb:64:52:e5:e9:40:27:b0:ca:73:21:51:
51 f6:8f:00:20:c0:2b:1a:d4:01:c2:32:38:9d:d1:8d:
52 88:71:46:a9:42:0d:ee:3b:1c:88:db:27:69:49:f9:
53 60:34:70:61:3d:60:df:7e:e4:e1:1d:c6:16:89:05:
54 ba:31:06:eb:88:b5:78:94:5d:8c:9d:88:fe:f2:c2:
55 80:a1:04:15:d3:84:85:d3:aa:5a:1d:53:5c:f8:57:
56 ae:61
57 Exponent: 65537 (0x10001)
58 Signature Algorithm: sha1WithRSAEncryption
59 14:7e:ca:c3:fc:93:60:9f:80:e0:65:2e:ef:41:2d:f9:af:77:
60 da:6d:e2:e0:11:70:17:fb:e5:67:4c:f0:ad:39:ec:96:ef:fe:
61 d5:95:94:70:e5:52:31:68:63:8c:ea:b3:a1:8e:02:e2:91:4b:
62 a8:8c:07:86:fd:80:98:a2:b1:90:2b:9c:2e:ab:f4:73:9d:8f:
63 fd:31:b9:8f:fe:6c:af:d6:bf:72:44:89:08:93:19:ef:2b:c3:
64 7c:ab:ba:bc:57:ca:f1:17:e4:e8:81:40:ca:65:df:84:be:10:
65 2c:42:46:af:d2:e0:0d:df:5d:56:53:65:13:e0:20:55:b4:ee:
66 cd:5e:b5:c4:97:1d:3e:a6:c1:9c:7e:b8:87:ee:64:78:a5:59:
67 e5:b2:79:47:9a:8e:59:fa:c4:18:ea:27:fd:a2:d5:76:d0:ae:
68 d9:05:f6:0e:23:ca:7d:66:a1:ba:18:67:f5:6d:bb:51:5a:f5:
69 52:e9:17:bb:63:15:24:b4:61:25:9f:d9:9c:89:58:93:9a:c3:
70 74:55:72:3e:f9:ff:ef:54:7d:e8:28:78:ba:3c:c7:15:ba:b9:
71 c6:e3:8c:61:cb:a9:ed:8d:07:16:0d:8d:f6:1c:36:11:69:08:
72 b8:45:7d:fc:fd:d1:ab:2d:9b:4e:9c:dd:11:78:50:c7:87:9f:
73 4a:24:9c:a0
74-----BEGIN CERTIFICATE-----
75MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
76VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
77ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
78A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
79ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
80VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
81ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
82MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
83NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
84Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
85vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
86iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
87ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
88cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
8904SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
90ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
91/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
92vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
936if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
94+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
95h59KJJyg
96-----END CERTIFICATE-----
97'''
98
99PEM_ENCODED = '''-----BEGIN CERTIFICATE-----
100MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC
101VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w
102ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG
103A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj
104ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD
105VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE
106ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy
107MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt
108NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb
109Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS
110vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd
111iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4
112ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I
113cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV
11404SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg
115ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG
116/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E
117vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY
1186if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+
119+f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH
120h59KJJyg
121-----END CERTIFICATE-----'''
122
123BAD_CERT = ''' NO MARKERS '''
124
125
126class OVSHelpersTest(TestCase):
127
128 @patch.object(ovs, 'log')
129 @patch('subprocess.check_call')
130 def test_add_bridge(self, check_call, log):
131 ovs.add_bridge('test')
132 check_call.assert_called_with(["ovs-vsctl", "--", "--may-exist",
133 "add-br", 'test'])
134 self.assertTrue(log.call_count == 1)
135
136 @patch.object(ovs, 'log')
137 @patch('subprocess.check_call')
138 def test_del_bridge(self, check_call, log):
139 ovs.del_bridge('test')
140 check_call.assert_called_with(["ovs-vsctl", "--", "--if-exists",
141 "del-br", 'test'])
142 self.assertTrue(log.call_count == 1)
143
144 @patch.object(ovs, 'log')
145 @patch('subprocess.check_call')
146 def test_add_bridge_port(self, check_call, log):
147 ovs.add_bridge_port('test', 'eth1')
148 check_call.assert_has_calls([
149 call(["ovs-vsctl", "--", "--may-exist", "add-port",
150 'test', 'eth1']),
151 call(['ip', 'link', 'set', 'eth1', 'up'])
152 ])
153 self.assertTrue(log.call_count == 1)
154
155 @patch.object(ovs, 'log')
156 @patch('subprocess.check_call')
157 def test_del_bridge_port(self, check_call, log):
158 ovs.del_bridge_port('test', 'eth1')
159 check_call.assert_has_calls([
160 call(["ovs-vsctl", "--", "--if-exists", "del-port",
161 'test', 'eth1']),
162 call(['ip', 'link', 'set', 'eth1', 'down'])
163 ])
164 self.assertTrue(log.call_count == 1)
165
166 @patch.object(ovs, 'log')
167 @patch('subprocess.check_call')
168 def test_set_manager(self, check_call, log):
169 ovs.set_manager('manager')
170 check_call.assert_called_with(['ovs-vsctl', 'set-manager',
171 'ssl:manager'])
172 self.assertTrue(log.call_count == 1)
173
174 @patch.object(ovs, 'log')
175 @patch('os.path.exists')
176 def test_get_certificate_good_cert(self, exists, log):
177 exists.return_value = True
178 with patch_open() as (mock_open, mock_file):
179 mock_file.read.return_value = GOOD_CERT
180 self.assertEqual(ovs.get_certificate(), PEM_ENCODED)
181 self.assertTrue(log.call_count == 1)
182
183 @patch.object(ovs, 'log')
184 @patch('os.path.exists')
185 def test_get_certificate_bad_cert(self, exists, log):
186 exists.return_value = True
187 with patch_open() as (mock_open, mock_file):
188 mock_file.read.return_value = BAD_CERT
189 self.assertRaises(RuntimeError, ovs.get_certificate)
190 self.assertTrue(log.call_count == 1)
191
192 @patch.object(ovs, 'log')
193 @patch('os.path.exists')
194 def test_get_certificate_missing(self, exists, log):
195 exists.return_value = False
196 self.assertIsNone(ovs.get_certificate())
197 self.assertTrue(log.call_count == 1)
198
199 @patch.object(ovs, 'service')
200 def test_full_restart(self, service):
201 ovs.full_restart()
202 service.assert_called_with('force-reload-kmod', 'openvswitch-switch')

Subscribers

People subscribed via source and target branches