]>
Commit | Line | Data |
---|---|---|
90c1cb3f | 1 | #!/usr/bin/env python3 |
064af421 BP |
2 | # |
3 | # xapi plugin script to update the cache of configuration items in the | |
9ba4f3c5 | 4 | # ovs-vswitchd configuration that are managed in the xapi database when |
bc391960 | 5 | # integrated with Citrix management tools. |
064af421 | 6 | |
90c1cb3f | 7 | # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2020 Nicira, Inc. |
064af421 | 8 | # |
a14bc59f BP |
9 | # Licensed under the Apache License, Version 2.0 (the "License"); |
10 | # you may not use this file except in compliance with the License. | |
11 | # You may obtain a copy of the License at: | |
064af421 | 12 | # |
a14bc59f | 13 | # http://www.apache.org/licenses/LICENSE-2.0 |
064af421 | 14 | # |
a14bc59f BP |
15 | # Unless required by applicable law or agreed to in writing, software |
16 | # distributed under the License is distributed on an "AS IS" BASIS, | |
17 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
18 | # See the License for the specific language governing permissions and | |
19 | # limitations under the License. | |
064af421 BP |
20 | |
21 | # TBD: - error handling needs to be improved. Currently this can leave | |
22 | # TBD: the system in a bad state if anything goes wrong. | |
23 | ||
064af421 | 24 | import XenAPIPlugin |
7e40e21d | 25 | import os |
064af421 | 26 | import subprocess |
7730bd8f | 27 | import syslog |
da7198b4 | 28 | import re |
064af421 | 29 | |
9ba4f3c5 JK |
30 | vsctl = '/usr/bin/ovs-vsctl' |
31 | ofctl = '/usr/bin/ovs-ofctl' | |
32 | cacert_filename = '/etc/openvswitch/vswitchd.cacert' | |
cbe03363 | 33 | ovsdb_port = '6640' |
9ba4f3c5 | 34 | |
7e40e21d JP |
35 | |
36 | # Delete the CA certificate, so that we go back to boot-strapping mode | |
37 | def delete_cacert(): | |
38 | try: | |
39 | os.remove(cacert_filename) | |
40 | except OSError: | |
41 | # Ignore error if file doesn't exist | |
42 | pass | |
064af421 | 43 | |
9ba4f3c5 | 44 | |
064af421 | 45 | def update(session, args): |
bc31db1f | 46 | # Refresh bridge network UUIDs in case this host joined or left a pool. |
9ba4f3c5 | 47 | script = '/opt/xensource/libexec/interface-reconfigure' |
bc31db1f | 48 | try: |
9ba4f3c5 | 49 | retval = subprocess.call([script, 'rewrite']) |
bc31db1f | 50 | if retval != 0: |
9ba4f3c5 | 51 | syslog.syslog('%s exited with status %d' % (script, retval)) |
bc31db1f | 52 | except OSError, e: |
9ba4f3c5 | 53 | syslog.syslog('%s: failed to execute (%s)' % (script, e.strerror)) |
bc31db1f | 54 | |
064af421 BP |
55 | pools = session.xenapi.pool.get_all() |
56 | # We assume there is only ever one pool... | |
57 | if len(pools) == 0: | |
9ba4f3c5 | 58 | raise XenAPIPlugin.Failure('NO_POOL_FOR_HOST', []) |
064af421 | 59 | if len(pools) > 1: |
9ba4f3c5 | 60 | raise XenAPIPlugin.Failure('MORE_THAN_ONE_POOL_FOR_HOST', []) |
da7198b4 | 61 | new_controller = False |
064af421 | 62 | pool = session.xenapi.pool.get_record(pools[0]) |
032c09dd | 63 | controller = pool.get('vswitch_controller') |
9ba4f3c5 | 64 | ret_str = '' |
032c09dd JK |
65 | currentControllers = vswitchCurrentControllers() |
66 | ||
67 | if not controller and currentControllers: | |
7e40e21d | 68 | delete_cacert() |
59a81d82 JP |
69 | try: |
70 | emergency_reset(session, None) | |
71 | except: | |
72 | pass | |
064af421 | 73 | removeControllerCfg() |
9ba4f3c5 | 74 | ret_str += 'Successfully removed controller config. ' |
ca60ea49 AW |
75 | # controller cannot be empty, otherwise, this will always be True. |
76 | elif controller and controller not in currentControllers: | |
7e40e21d | 77 | delete_cacert() |
59a81d82 JP |
78 | try: |
79 | emergency_reset(session, None) | |
80 | except: | |
81 | pass | |
064af421 | 82 | setControllerCfg(controller) |
da7198b4 | 83 | new_controller = True |
9ba4f3c5 | 84 | ret_str += 'Successfully set controller to %s. ' % controller |
939e5a1b EJ |
85 | |
86 | try: | |
9ba4f3c5 | 87 | pool_fail_mode = pool['other_config']['vswitch-controller-fail-mode'] |
939e5a1b | 88 | except KeyError, e: |
2dd26837 | 89 | pool_fail_mode = None |
939e5a1b | 90 | |
2dd26837 EJ |
91 | bton = {} |
92 | ||
3249bb90 | 93 | for rec in session.xenapi.network.get_all_records().values(): |
2dd26837 EJ |
94 | try: |
95 | bton[rec['bridge']] = rec | |
96 | except KeyError: | |
97 | pass | |
939e5a1b | 98 | |
e084518a | 99 | # If new controller, get management MAC addresses from XAPI now |
da7198b4 DT |
100 | # in case fail_mode set to secure which may affect XAPI access |
101 | mgmt_bridge = None | |
102 | host_mgmt_mac = None | |
103 | host_mgmt_device = None | |
104 | pool_mgmt_macs = {} | |
105 | if new_controller: | |
9ba4f3c5 JK |
106 | query = 'field "management"="true"' |
107 | recs = session.xenapi.PIF.get_all_records_where(query) | |
2d57bfce | 108 | for rec in recs.itervalues(): |
3249bb90 | 109 | pool_mgmt_macs[rec.get('MAC')] = rec.get('device') |
da7198b4 | 110 | |
da54975c | 111 | dib_changed = False |
939e5a1b | 112 | fail_mode_changed = False |
75fca0a4 | 113 | for bridge in vswitchCfgQuery(['list-br']).split(): |
da54975c | 114 | network = bton[bridge] |
75fca0a4 | 115 | bridge = vswitchCfgQuery(['br-to-parent', bridge]) |
da54975c AE |
116 | |
117 | xapi_dib = network['other_config'].get('vswitch-disable-in-band') | |
118 | if not xapi_dib: | |
119 | xapi_dib = '' | |
120 | ||
25769755 BP |
121 | ovs_dib = vswitchCfgQuery(['--', '--if-exists', 'get', 'Bridge', |
122 | bridge, | |
da54975c AE |
123 | 'other_config:disable-in-band']).strip('"') |
124 | ||
125 | # Do nothing if setting is invalid, and warn the user. | |
126 | if xapi_dib not in ['true', 'false', '']: | |
127 | ret_str += '"' + xapi_dib + '"' + \ | |
128 | ' is an invalid value for vswitch-disable-in-band on ' + \ | |
129 | bridge + ' ' | |
130 | ||
131 | # Change bridge disable-in-band option if XAPI and OVS states differ. | |
132 | elif xapi_dib != ovs_dib: | |
133 | # 'true' or 'false' | |
134 | if xapi_dib: | |
135 | vswitchCfgMod(['--', 'set', 'Bridge', bridge, | |
136 | 'other_config:disable-in-band=' + xapi_dib]) | |
137 | # '' or None | |
138 | else: | |
139 | vswitchCfgMod(['--', 'remove', 'Bridge', bridge, | |
140 | 'other_config', 'disable-in-band']) | |
141 | dib_changed = True | |
142 | ||
143 | # Change bridge fail_mode if XAPI state differs from OVS state. | |
9ba4f3c5 JK |
144 | bridge_fail_mode = vswitchCfgQuery(['get', 'Bridge', |
145 | bridge, 'fail_mode']).strip('[]"') | |
939e5a1b | 146 | |
2dd26837 | 147 | try: |
9ba4f3c5 JK |
148 | other_config = bton[bridge]['other_config'] |
149 | fail_mode = other_config['vswitch-controller-fail-mode'] | |
2dd26837 EJ |
150 | except KeyError, e: |
151 | fail_mode = None | |
152 | ||
153 | if fail_mode not in ['secure', 'standalone']: | |
154 | fail_mode = pool_fail_mode | |
155 | ||
156 | if fail_mode != 'secure': | |
157 | fail_mode = 'standalone' | |
158 | ||
939e5a1b EJ |
159 | if bridge_fail_mode != fail_mode: |
160 | vswitchCfgMod(['--', 'set', 'Bridge', bridge, | |
9ba4f3c5 | 161 | 'fail_mode=%s' % fail_mode]) |
939e5a1b EJ |
162 | fail_mode_changed = True |
163 | ||
da7198b4 DT |
164 | # Determine local mgmt MAC address if host being added to secure |
165 | # pool so we can add default flows to allow management traffic | |
9ba4f3c5 JK |
166 | if new_controller and fail_mode_changed and pool_fail_mode == 'secure': |
167 | oc = vswitchCfgQuery(['get', 'Bridge', bridge, 'other-config']) | |
da7198b4 DT |
168 | m = re.match('.*hwaddr="([0-9a-fA-F:].*)".*', oc) |
169 | if m and m.group(1) in pool_mgmt_macs.keys(): | |
170 | mgmt_bridge = bridge | |
171 | host_mgmt_mac = m.group(1) | |
172 | host_mgmt_device = pool_mgmt_macs[host_mgmt_mac] | |
173 | ||
9ba4f3c5 JK |
174 | if (host_mgmt_mac is not None and mgmt_bridge is not None and |
175 | host_mgmt_device is not None): | |
176 | tp = 'idle_timeout=0,priority=0' | |
177 | port = vswitchCfgQuery(['get', 'interface', host_mgmt_device, | |
178 | 'ofport']) | |
179 | ||
180 | addFlow(mgmt_bridge, '%s,in_port=%s,arp,nw_proto=1,actions=local' % | |
181 | (tp, port)) | |
182 | addFlow(mgmt_bridge, '%s,in_port=local,arp,dl_src=%s,actions=%s' % | |
183 | (tp, host_mgmt_mac, port)) | |
184 | addFlow(mgmt_bridge, '%s,in_port=%s,dl_dst=%s,actions=local' % | |
185 | (tp, port, host_mgmt_mac)) | |
186 | addFlow(mgmt_bridge, '%s,in_port=local,dl_src=%s,actions=%s' % | |
187 | (tp, host_mgmt_mac, port)) | |
da7198b4 | 188 | |
da54975c | 189 | if dib_changed: |
9ba4f3c5 | 190 | ret_str += 'Updated in-band management. ' |
939e5a1b | 191 | if fail_mode_changed: |
9ba4f3c5 | 192 | ret_str += 'Updated fail_mode. ' |
939e5a1b EJ |
193 | |
194 | if ret_str != '': | |
195 | return ret_str | |
064af421 | 196 | else: |
9ba4f3c5 JK |
197 | return 'No change to configuration' |
198 | ||
83497018 | 199 | |
032c09dd JK |
200 | def vswitchCurrentControllers(): |
201 | controllers = vswitchCfgQuery(['get-manager']) | |
202 | ||
203 | def parse_controller(controller): | |
204 | if controller.startswith('ssl:'): | |
205 | return controller.split(':')[1] | |
206 | ||
207 | return controller.split(':')[0] | |
208 | ||
209 | return [parse_controller(controller) | |
210 | for controller in controllers.split('\n') | |
211 | if controller] | |
064af421 | 212 | |
9ba4f3c5 | 213 | |
064af421 | 214 | def removeControllerCfg(): |
9ba4f3c5 JK |
215 | vswitchCfgMod(['--', 'del-manager', |
216 | '--', 'del-ssl']) | |
217 | ||
83497018 | 218 | |
064af421 | 219 | def setControllerCfg(controller): |
88b56f29 BP |
220 | # /etc/xensource/xapi-ssl.pem is mentioned twice below because it |
221 | # contains both the private key and the certificate. | |
9ba4f3c5 JK |
222 | vswitchCfgMod(['--', 'del-manager', |
223 | '--', 'del-ssl', | |
224 | '--', '--bootstrap', 'set-ssl', | |
225 | '/etc/xensource/xapi-ssl.pem', | |
226 | '/etc/xensource/xapi-ssl.pem', | |
bc391960 | 227 | cacert_filename, |
cbe03363 | 228 | '--', 'set-manager', 'ssl:' + controller + ':' + ovsdb_port]) |
9ba4f3c5 | 229 | |
064af421 | 230 | |
7e67d10d | 231 | def vswitchCfgQuery(action_args): |
9ba4f3c5 | 232 | cmd = [vsctl, '-vconsole:off'] + action_args |
064af421 | 233 | output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate() |
9ba4f3c5 JK |
234 | if len(output) == 0 or output[0] is None: |
235 | output = '' | |
064af421 BP |
236 | else: |
237 | output = output[0].strip() | |
238 | return output | |
239 | ||
9ba4f3c5 | 240 | |
064af421 | 241 | def vswitchCfgMod(action_args): |
9ba4f3c5 | 242 | cmd = [vsctl, '--timeout=5', '-vconsole:off'] + action_args |
064af421 BP |
243 | exitcode = subprocess.call(cmd) |
244 | if exitcode != 0: | |
9ba4f3c5 JK |
245 | raise XenAPIPlugin.Failure('VSWITCH_CONFIG_MOD_FAILURE', |
246 | [str(exitcode), str(action_args)]) | |
247 | ||
7730bd8f JP |
248 | |
249 | def emergency_reset(session, args): | |
9ba4f3c5 | 250 | cmd = [vsctl, '--timeout=5', 'emer-reset'] |
59a81d82 JP |
251 | exitcode = subprocess.call(cmd) |
252 | if exitcode != 0: | |
9ba4f3c5 JK |
253 | raise XenAPIPlugin.Failure('VSWITCH_EMER_RESET_FAILURE', |
254 | [str(exitcode)]) | |
255 | ||
256 | return 'Successfully reset configuration' | |
59a81d82 | 257 | |
da7198b4 DT |
258 | |
259 | def addFlow(switch, flow): | |
9ba4f3c5 | 260 | cmd = [ofctl, 'add-flow', switch, flow] |
da7198b4 DT |
261 | exitcode = subprocess.call(cmd) |
262 | if exitcode != 0: | |
9ba4f3c5 JK |
263 | raise XenAPIPlugin.Failure('VSWITCH_ADD_FLOW_FAILURE', |
264 | [str(exitcode), str(switch), str(flow)]) | |
265 | ||
266 | ||
267 | if __name__ == '__main__': | |
268 | XenAPIPlugin.dispatch({'update': update, | |
269 | 'emergency_reset': emergency_reset}) |