Merge lp:~james-page/charm-helpers/ovs-helper into lp:charm-helpers
- ovs-helper
- Merge into devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Adam Gandelman (community) | Approve | ||
Review via email: mp+172296@code.launchpad.net |
Commit message
Description of the change
Various helpers for interacting with OpenvSwitch.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'charmhelpers/contrib/network' |
2 | === added file 'charmhelpers/contrib/network/__init__.py' |
3 | === added directory 'charmhelpers/contrib/network/ovs' |
4 | === added file 'charmhelpers/contrib/network/ovs/__init__.py' |
5 | --- charmhelpers/contrib/network/ovs/__init__.py 1970-01-01 00:00:00 +0000 |
6 | +++ charmhelpers/contrib/network/ovs/__init__.py 2013-07-01 10:37:46 +0000 |
7 | @@ -0,0 +1,72 @@ |
8 | +''' Helpers for interacting with OpenvSwitch ''' |
9 | +import subprocess |
10 | +import os |
11 | +from charmhelpers.core.hookenv import ( |
12 | + log, WARNING |
13 | +) |
14 | +from charmhelpers.core.host import ( |
15 | + service |
16 | +) |
17 | + |
18 | + |
19 | +def add_bridge(name): |
20 | + ''' Add the named bridge to openvswitch ''' |
21 | + log('Creating bridge {}'.format(name)) |
22 | + subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-br", name]) |
23 | + |
24 | + |
25 | +def del_bridge(name): |
26 | + ''' Delete the named bridge from openvswitch ''' |
27 | + log('Deleting bridge {}'.format(name)) |
28 | + subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-br", name]) |
29 | + |
30 | + |
31 | +def add_bridge_port(name, port): |
32 | + ''' Add a port to the named openvswitch bridge ''' |
33 | + log('Adding port {} to bridge {}'.format(port, name)) |
34 | + subprocess.check_call(["ovs-vsctl", "--", "--may-exist", "add-port", |
35 | + name, port]) |
36 | + subprocess.check_call(["ip", "link", "set", port, "up"]) |
37 | + |
38 | + |
39 | +def del_bridge_port(name, port): |
40 | + ''' Delete a port from the named openvswitch bridge ''' |
41 | + log('Deleting port {} from bridge {}'.format(port, name)) |
42 | + subprocess.check_call(["ovs-vsctl", "--", "--if-exists", "del-port", |
43 | + name, port]) |
44 | + subprocess.check_call(["ip", "link", "set", port, "down"]) |
45 | + |
46 | + |
47 | +def set_manager(manager): |
48 | + ''' Set the controller for the local openvswitch ''' |
49 | + log('Setting manager for local ovs to {}'.format(manager)) |
50 | + subprocess.check_call(['ovs-vsctl', 'set-manager', |
51 | + 'ssl:{}'.format(manager)]) |
52 | + |
53 | + |
54 | +CERT_PATH = '/etc/openvswitch/ovsclient-cert.pem' |
55 | + |
56 | + |
57 | +def get_certificate(): |
58 | + ''' Read openvswitch certificate from disk ''' |
59 | + if os.path.exists(CERT_PATH): |
60 | + log('Reading ovs certificate from {}'.format(CERT_PATH)) |
61 | + with open(CERT_PATH, 'r') as cert: |
62 | + full_cert = cert.read() |
63 | + begin_marker = "-----BEGIN CERTIFICATE-----" |
64 | + end_marker = "-----END CERTIFICATE-----" |
65 | + begin_index = full_cert.find(begin_marker) |
66 | + end_index = full_cert.rfind(end_marker) |
67 | + if end_index == -1 or begin_index == -1: |
68 | + raise RuntimeError("Certificate does not contain valid begin" |
69 | + " and end markers.") |
70 | + full_cert = full_cert[begin_index:(end_index + len(end_marker))] |
71 | + return full_cert |
72 | + else: |
73 | + log('Certificate not found', level=WARNING) |
74 | + return None |
75 | + |
76 | + |
77 | +def full_restart(): |
78 | + ''' Full restart and reload of openvswitch ''' |
79 | + service('force-reload-kmod', 'openvswitch-switch') |
80 | |
81 | === added directory 'tests/contrib/network' |
82 | === added file 'tests/contrib/network/__init__.py' |
83 | === added file 'tests/contrib/network/test_ovs.py' |
84 | --- tests/contrib/network/test_ovs.py 1970-01-01 00:00:00 +0000 |
85 | +++ tests/contrib/network/test_ovs.py 2013-07-01 10:37:46 +0000 |
86 | @@ -0,0 +1,202 @@ |
87 | +from contextlib import contextmanager |
88 | +from mock import patch, call, MagicMock |
89 | +from testtools import TestCase |
90 | + |
91 | +import charmhelpers.contrib.network.ovs as ovs |
92 | + |
93 | + |
94 | +@contextmanager |
95 | +def patch_open(): |
96 | + '''Patch open() to allow mocking both open() itself and the file that is |
97 | + yielded. |
98 | + |
99 | + Yields the mock for "open" and "file", respectively.''' |
100 | + mock_open = MagicMock(spec=open) |
101 | + mock_file = MagicMock(spec=file) |
102 | + |
103 | + @contextmanager |
104 | + def stub_open(*args, **kwargs): |
105 | + mock_open(*args, **kwargs) |
106 | + yield mock_file |
107 | + |
108 | + with patch('__builtin__.open', stub_open): |
109 | + yield mock_open, mock_file |
110 | + |
111 | +GOOD_CERT = '''Certificate: |
112 | + Data: |
113 | + Version: 1 (0x0) |
114 | + Serial Number: 13798680962510501282 (0xbf7ec33a136235a2) |
115 | + Signature Algorithm: sha1WithRSAEncryption |
116 | + Issuer: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch |
117 | + Validity |
118 | + Not Before: Jun 28 17:02:19 2013 GMT |
119 | + Not After : Jun 28 17:02:19 2019 GMT |
120 | + Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch |
121 | + Subject Public Key Info: |
122 | + Public Key Algorithm: rsaEncryption |
123 | + Public-Key: (2048 bit) |
124 | + Modulus: |
125 | + 00:e8:a7:db:0a:6d:c0:16:4a:14:96:1d:74:91:15: |
126 | + 64:3f:ae:2a:54:be:2a:fe:10:14:9a:73:39:d8:58: |
127 | + 74:7f:ab:d5:f2:39:aa:9a:27:7c:31:82:f8:74:42: |
128 | + 46:8d:c5:3b:42:55:52:be:75:7f:a5:b1:ec:d5:29: |
129 | + 9f:62:0e:de:31:27:2b:95:1f:24:0d:ca:8c:48:30: |
130 | + 96:9f:ba:b7:9d:eb:c1:bd:93:05:e3:d8:ca:66:5a: |
131 | + e9:cb:a5:7a:3a:8d:27:e2:05:9d:88:fc:a9:ef:af: |
132 | + 47:4c:66:ce:c6:43:73:1a:85:f4:5f:b9:53:5b:29: |
133 | + f3:c3:23:1f:0c:20:95:11:50:71:b2:f6:01:23:3f: |
134 | + 66:0f:5c:43:c2:90:fb:e5:98:73:98:e9:38:bb:1f: |
135 | + 1b:89:97:1e:dc:d7:98:07:68:32:ec:da:1d:69:0b: |
136 | + e2:df:40:fb:64:52:e5:e9:40:27:b0:ca:73:21:51: |
137 | + f6:8f:00:20:c0:2b:1a:d4:01:c2:32:38:9d:d1:8d: |
138 | + 88:71:46:a9:42:0d:ee:3b:1c:88:db:27:69:49:f9: |
139 | + 60:34:70:61:3d:60:df:7e:e4:e1:1d:c6:16:89:05: |
140 | + ba:31:06:eb:88:b5:78:94:5d:8c:9d:88:fe:f2:c2: |
141 | + 80:a1:04:15:d3:84:85:d3:aa:5a:1d:53:5c:f8:57: |
142 | + ae:61 |
143 | + Exponent: 65537 (0x10001) |
144 | + Signature Algorithm: sha1WithRSAEncryption |
145 | + 14:7e:ca:c3:fc:93:60:9f:80:e0:65:2e:ef:41:2d:f9:af:77: |
146 | + da:6d:e2:e0:11:70:17:fb:e5:67:4c:f0:ad:39:ec:96:ef:fe: |
147 | + d5:95:94:70:e5:52:31:68:63:8c:ea:b3:a1:8e:02:e2:91:4b: |
148 | + a8:8c:07:86:fd:80:98:a2:b1:90:2b:9c:2e:ab:f4:73:9d:8f: |
149 | + fd:31:b9:8f:fe:6c:af:d6:bf:72:44:89:08:93:19:ef:2b:c3: |
150 | + 7c:ab:ba:bc:57:ca:f1:17:e4:e8:81:40:ca:65:df:84:be:10: |
151 | + 2c:42:46:af:d2:e0:0d:df:5d:56:53:65:13:e0:20:55:b4:ee: |
152 | + cd:5e:b5:c4:97:1d:3e:a6:c1:9c:7e:b8:87:ee:64:78:a5:59: |
153 | + e5:b2:79:47:9a:8e:59:fa:c4:18:ea:27:fd:a2:d5:76:d0:ae: |
154 | + d9:05:f6:0e:23:ca:7d:66:a1:ba:18:67:f5:6d:bb:51:5a:f5: |
155 | + 52:e9:17:bb:63:15:24:b4:61:25:9f:d9:9c:89:58:93:9a:c3: |
156 | + 74:55:72:3e:f9:ff:ef:54:7d:e8:28:78:ba:3c:c7:15:ba:b9: |
157 | + c6:e3:8c:61:cb:a9:ed:8d:07:16:0d:8d:f6:1c:36:11:69:08: |
158 | + b8:45:7d:fc:fd:d1:ab:2d:9b:4e:9c:dd:11:78:50:c7:87:9f: |
159 | + 4a:24:9c:a0 |
160 | +-----BEGIN CERTIFICATE----- |
161 | +MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC |
162 | +VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w |
163 | +ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG |
164 | +A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj |
165 | +ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD |
166 | +VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE |
167 | +ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy |
168 | +MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt |
169 | +NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb |
170 | +Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS |
171 | +vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd |
172 | +iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4 |
173 | +ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I |
174 | +cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV |
175 | +04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg |
176 | +ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG |
177 | +/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E |
178 | +vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY |
179 | +6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+ |
180 | ++f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH |
181 | +h59KJJyg |
182 | +-----END CERTIFICATE----- |
183 | +''' |
184 | + |
185 | +PEM_ENCODED = '''-----BEGIN CERTIFICATE----- |
186 | +MIIDwjCCAqoCCQC/fsM6E2I1ojANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC |
187 | +VVMxCzAJBgNVBAgTAkNBMRIwEAYDVQQHEwlQYWxvIEFsdG8xFTATBgNVBAoTDE9w |
188 | +ZW4gdlN3aXRjaDEfMB0GA1UECxMWT3BlbiB2U3dpdGNoIGNlcnRpZmllcjE6MDgG |
189 | +A1UEAxMxb3ZzY2xpZW50IGlkOjU4MTQ5N2E1LWJjMDAtNGVjYy1iNzkwLTU3NTZj |
190 | +ZWUxNmE0ODAeFw0xMzA2MjgxNzAyMTlaFw0xOTA2MjgxNzAyMTlaMIGiMQswCQYD |
191 | +VQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVBhbG8gQWx0bzEVMBMGA1UE |
192 | +ChMMT3BlbiB2U3dpdGNoMR8wHQYDVQQLExZPcGVuIHZTd2l0Y2ggY2VydGlmaWVy |
193 | +MTowOAYDVQQDEzFvdnNjbGllbnQgaWQ6NTgxNDk3YTUtYmMwMC00ZWNjLWI3OTAt |
194 | +NTc1NmNlZTE2YTQ4MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6Kfb |
195 | +Cm3AFkoUlh10kRVkP64qVL4q/hAUmnM52Fh0f6vV8jmqmid8MYL4dEJGjcU7QlVS |
196 | +vnV/pbHs1SmfYg7eMScrlR8kDcqMSDCWn7q3nevBvZMF49jKZlrpy6V6Oo0n4gWd |
197 | +iPyp769HTGbOxkNzGoX0X7lTWynzwyMfDCCVEVBxsvYBIz9mD1xDwpD75ZhzmOk4 |
198 | +ux8biZce3NeYB2gy7NodaQvi30D7ZFLl6UAnsMpzIVH2jwAgwCsa1AHCMjid0Y2I |
199 | +cUapQg3uOxyI2ydpSflgNHBhPWDffuThHcYWiQW6MQbriLV4lF2MnYj+8sKAoQQV |
200 | +04SF06paHVNc+FeuYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQAUfsrD/JNgn4Dg |
201 | +ZS7vQS35r3fabeLgEXAX++VnTPCtOeyW7/7VlZRw5VIxaGOM6rOhjgLikUuojAeG |
202 | +/YCYorGQK5wuq/RznY/9MbmP/myv1r9yRIkIkxnvK8N8q7q8V8rxF+TogUDKZd+E |
203 | +vhAsQkav0uAN311WU2UT4CBVtO7NXrXElx0+psGcfriH7mR4pVnlsnlHmo5Z+sQY |
204 | +6if9otV20K7ZBfYOI8p9ZqG6GGf1bbtRWvVS6Re7YxUktGEln9mciViTmsN0VXI+ |
205 | ++f/vVH3oKHi6PMcVurnG44xhy6ntjQcWDY32HDYRaQi4RX38/dGrLZtOnN0ReFDH |
206 | +h59KJJyg |
207 | +-----END CERTIFICATE-----''' |
208 | + |
209 | +BAD_CERT = ''' NO MARKERS ''' |
210 | + |
211 | + |
212 | +class OVSHelpersTest(TestCase): |
213 | + |
214 | + @patch.object(ovs, 'log') |
215 | + @patch('subprocess.check_call') |
216 | + def test_add_bridge(self, check_call, log): |
217 | + ovs.add_bridge('test') |
218 | + check_call.assert_called_with(["ovs-vsctl", "--", "--may-exist", |
219 | + "add-br", 'test']) |
220 | + self.assertTrue(log.call_count == 1) |
221 | + |
222 | + @patch.object(ovs, 'log') |
223 | + @patch('subprocess.check_call') |
224 | + def test_del_bridge(self, check_call, log): |
225 | + ovs.del_bridge('test') |
226 | + check_call.assert_called_with(["ovs-vsctl", "--", "--if-exists", |
227 | + "del-br", 'test']) |
228 | + self.assertTrue(log.call_count == 1) |
229 | + |
230 | + @patch.object(ovs, 'log') |
231 | + @patch('subprocess.check_call') |
232 | + def test_add_bridge_port(self, check_call, log): |
233 | + ovs.add_bridge_port('test', 'eth1') |
234 | + check_call.assert_has_calls([ |
235 | + call(["ovs-vsctl", "--", "--may-exist", "add-port", |
236 | + 'test', 'eth1']), |
237 | + call(['ip', 'link', 'set', 'eth1', 'up']) |
238 | + ]) |
239 | + self.assertTrue(log.call_count == 1) |
240 | + |
241 | + @patch.object(ovs, 'log') |
242 | + @patch('subprocess.check_call') |
243 | + def test_del_bridge_port(self, check_call, log): |
244 | + ovs.del_bridge_port('test', 'eth1') |
245 | + check_call.assert_has_calls([ |
246 | + call(["ovs-vsctl", "--", "--if-exists", "del-port", |
247 | + 'test', 'eth1']), |
248 | + call(['ip', 'link', 'set', 'eth1', 'down']) |
249 | + ]) |
250 | + self.assertTrue(log.call_count == 1) |
251 | + |
252 | + @patch.object(ovs, 'log') |
253 | + @patch('subprocess.check_call') |
254 | + def test_set_manager(self, check_call, log): |
255 | + ovs.set_manager('manager') |
256 | + check_call.assert_called_with(['ovs-vsctl', 'set-manager', |
257 | + 'ssl:manager']) |
258 | + self.assertTrue(log.call_count == 1) |
259 | + |
260 | + @patch.object(ovs, 'log') |
261 | + @patch('os.path.exists') |
262 | + def test_get_certificate_good_cert(self, exists, log): |
263 | + exists.return_value = True |
264 | + with patch_open() as (mock_open, mock_file): |
265 | + mock_file.read.return_value = GOOD_CERT |
266 | + self.assertEqual(ovs.get_certificate(), PEM_ENCODED) |
267 | + self.assertTrue(log.call_count == 1) |
268 | + |
269 | + @patch.object(ovs, 'log') |
270 | + @patch('os.path.exists') |
271 | + def test_get_certificate_bad_cert(self, exists, log): |
272 | + exists.return_value = True |
273 | + with patch_open() as (mock_open, mock_file): |
274 | + mock_file.read.return_value = BAD_CERT |
275 | + self.assertRaises(RuntimeError, ovs.get_certificate) |
276 | + self.assertTrue(log.call_count == 1) |
277 | + |
278 | + @patch.object(ovs, 'log') |
279 | + @patch('os.path.exists') |
280 | + def test_get_certificate_missing(self, exists, log): |
281 | + exists.return_value = False |
282 | + self.assertIsNone(ovs.get_certificate()) |
283 | + self.assertTrue(log.call_count == 1) |
284 | + |
285 | + @patch.object(ovs, 'service') |
286 | + def test_full_restart(self, service): |
287 | + ovs.full_restart() |
288 | + service.assert_called_with('force-reload-kmod', 'openvswitch-switch') |
Simple utility wrappers for OVS CLI commands with good tests. LGTM