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