]>
Commit | Line | Data |
---|---|---|
064af421 BP |
1 | #!/usr/bin/env python |
2 | # | |
3 | # xapi plugin script to update the cache of configuration items in the | |
bc391960 JP |
4 | # ovs-vswitchd configuration that are managed in the xapi database when |
5 | # integrated with Citrix management tools. | |
064af421 | 6 | |
e0edde6f | 7 | # Copyright (C) 2009, 2010, 2011 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 BP |
24 | import XenAPIPlugin |
25 | import XenAPI | |
7e40e21d | 26 | import os |
064af421 | 27 | import subprocess |
7730bd8f | 28 | import syslog |
da7198b4 | 29 | import re |
064af421 | 30 | |
f95c85e2 | 31 | vsctl="/usr/bin/ovs-vsctl" |
da7198b4 | 32 | ofctl="/usr/bin/ovs-ofctl" |
bc391960 | 33 | cacert_filename="/etc/openvswitch/vswitchd.cacert" |
7e40e21d JP |
34 | |
35 | # Delete the CA certificate, so that we go back to boot-strapping mode | |
36 | def delete_cacert(): | |
37 | try: | |
38 | os.remove(cacert_filename) | |
39 | except OSError: | |
40 | # Ignore error if file doesn't exist | |
41 | pass | |
064af421 BP |
42 | |
43 | def update(session, args): | |
bc31db1f | 44 | # Refresh bridge network UUIDs in case this host joined or left a pool. |
9575eae3 | 45 | script = "/opt/xensource/libexec/interface-reconfigure" |
bc31db1f | 46 | try: |
9575eae3 | 47 | retval = subprocess.call([script, "rewrite"]) |
bc31db1f BP |
48 | if retval != 0: |
49 | syslog.syslog("%s exited with status %d" % (script, retval)) | |
50 | except OSError, e: | |
51 | syslog.syslog("%s: failed to execute (%s)" % (script, e.strerror)) | |
52 | ||
064af421 BP |
53 | pools = session.xenapi.pool.get_all() |
54 | # We assume there is only ever one pool... | |
55 | if len(pools) == 0: | |
064af421 BP |
56 | raise XenAPIPlugin.Failure("NO_POOL_FOR_HOST", []) |
57 | if len(pools) > 1: | |
064af421 | 58 | raise XenAPIPlugin.Failure("MORE_THAN_ONE_POOL_FOR_HOST", []) |
da7198b4 | 59 | new_controller = False |
064af421 | 60 | pool = session.xenapi.pool.get_record(pools[0]) |
6cd12763 | 61 | controller = pool.get("vswitch_controller", "") |
939e5a1b | 62 | ret_str = "" |
064af421 BP |
63 | currentController = vswitchCurrentController() |
64 | if controller == "" and currentController != "": | |
7e40e21d | 65 | delete_cacert() |
59a81d82 JP |
66 | try: |
67 | emergency_reset(session, None) | |
68 | except: | |
69 | pass | |
064af421 | 70 | removeControllerCfg() |
939e5a1b | 71 | ret_str += "Successfully removed controller config. " |
064af421 | 72 | elif controller != currentController: |
7e40e21d | 73 | delete_cacert() |
59a81d82 JP |
74 | try: |
75 | emergency_reset(session, None) | |
76 | except: | |
77 | pass | |
064af421 | 78 | setControllerCfg(controller) |
da7198b4 | 79 | new_controller = True |
939e5a1b EJ |
80 | ret_str += "Successfully set controller to %s. " % controller |
81 | ||
82 | try: | |
2dd26837 | 83 | pool_fail_mode = pool["other_config"]["vswitch-controller-fail-mode"] |
939e5a1b | 84 | except KeyError, e: |
2dd26837 | 85 | pool_fail_mode = None |
939e5a1b | 86 | |
2dd26837 EJ |
87 | bton = {} |
88 | ||
3249bb90 | 89 | for rec in session.xenapi.network.get_all_records().values(): |
2dd26837 EJ |
90 | try: |
91 | bton[rec['bridge']] = rec | |
92 | except KeyError: | |
93 | pass | |
939e5a1b | 94 | |
e084518a | 95 | # If new controller, get management MAC addresses from XAPI now |
da7198b4 DT |
96 | # in case fail_mode set to secure which may affect XAPI access |
97 | mgmt_bridge = None | |
98 | host_mgmt_mac = None | |
99 | host_mgmt_device = None | |
100 | pool_mgmt_macs = {} | |
101 | if new_controller: | |
3249bb90 | 102 | recs = session.xenapi.PIF.get_all_records_where('field "management"="true"') |
2d57bfce | 103 | for rec in recs.itervalues(): |
3249bb90 | 104 | pool_mgmt_macs[rec.get('MAC')] = rec.get('device') |
da7198b4 | 105 | |
da54975c | 106 | dib_changed = False |
939e5a1b | 107 | fail_mode_changed = False |
75fca0a4 | 108 | for bridge in vswitchCfgQuery(['list-br']).split(): |
da54975c | 109 | network = bton[bridge] |
75fca0a4 | 110 | bridge = vswitchCfgQuery(['br-to-parent', bridge]) |
da54975c AE |
111 | |
112 | xapi_dib = network['other_config'].get('vswitch-disable-in-band') | |
113 | if not xapi_dib: | |
114 | xapi_dib = '' | |
115 | ||
25769755 BP |
116 | ovs_dib = vswitchCfgQuery(['--', '--if-exists', 'get', 'Bridge', |
117 | bridge, | |
da54975c AE |
118 | 'other_config:disable-in-band']).strip('"') |
119 | ||
120 | # Do nothing if setting is invalid, and warn the user. | |
121 | if xapi_dib not in ['true', 'false', '']: | |
122 | ret_str += '"' + xapi_dib + '"' + \ | |
123 | ' is an invalid value for vswitch-disable-in-band on ' + \ | |
124 | bridge + ' ' | |
125 | ||
126 | # Change bridge disable-in-band option if XAPI and OVS states differ. | |
127 | elif xapi_dib != ovs_dib: | |
128 | # 'true' or 'false' | |
129 | if xapi_dib: | |
130 | vswitchCfgMod(['--', 'set', 'Bridge', bridge, | |
131 | 'other_config:disable-in-band=' + xapi_dib]) | |
132 | # '' or None | |
133 | else: | |
134 | vswitchCfgMod(['--', 'remove', 'Bridge', bridge, | |
135 | 'other_config', 'disable-in-band']) | |
136 | dib_changed = True | |
137 | ||
138 | # Change bridge fail_mode if XAPI state differs from OVS state. | |
939e5a1b EJ |
139 | bridge_fail_mode = vswitchCfgQuery(["get", "Bridge", |
140 | bridge, "fail_mode"]).strip('[]"') | |
141 | ||
2dd26837 EJ |
142 | try: |
143 | fail_mode = bton[bridge]["other_config"]["vswitch-controller-fail-mode"] | |
144 | except KeyError, e: | |
145 | fail_mode = None | |
146 | ||
147 | if fail_mode not in ['secure', 'standalone']: | |
148 | fail_mode = pool_fail_mode | |
149 | ||
150 | if fail_mode != 'secure': | |
151 | fail_mode = 'standalone' | |
152 | ||
939e5a1b EJ |
153 | if bridge_fail_mode != fail_mode: |
154 | vswitchCfgMod(['--', 'set', 'Bridge', bridge, | |
155 | "fail_mode=%s" % fail_mode]) | |
156 | fail_mode_changed = True | |
157 | ||
da7198b4 DT |
158 | # Determine local mgmt MAC address if host being added to secure |
159 | # pool so we can add default flows to allow management traffic | |
160 | if new_controller and fail_mode_changed and pool_fail_mode == "secure": | |
161 | oc = vswitchCfgQuery(["get", "Bridge", bridge, "other-config"]) | |
162 | m = re.match('.*hwaddr="([0-9a-fA-F:].*)".*', oc) | |
163 | if m and m.group(1) in pool_mgmt_macs.keys(): | |
164 | mgmt_bridge = bridge | |
165 | host_mgmt_mac = m.group(1) | |
166 | host_mgmt_device = pool_mgmt_macs[host_mgmt_mac] | |
167 | ||
168 | if host_mgmt_mac is not None and mgmt_bridge is not None and \ | |
169 | host_mgmt_device is not None: | |
170 | tp = "idle_timeout=0,priority=0" | |
171 | port = vswitchCfgQuery(["get", "interface", host_mgmt_device, "ofport"]) | |
172 | addFlow(mgmt_bridge, "%s,in_port=%s,arp,nw_proto=1,actions=local" % \ | |
173 | (tp, port)) | |
174 | addFlow(mgmt_bridge, "%s,in_port=local,arp,dl_src=%s,actions=%s" % \ | |
175 | (tp, host_mgmt_mac, port)) | |
176 | addFlow(mgmt_bridge, "%s,in_port=%s,dl_dst=%s,actions=local" % \ | |
177 | (tp, port, host_mgmt_mac)) | |
178 | addFlow(mgmt_bridge, "%s,in_port=local,dl_src=%s,actions=%s" % \ | |
179 | (tp, host_mgmt_mac, port)) | |
180 | ||
da54975c AE |
181 | if dib_changed: |
182 | ret_str += "Updated in-band management. " | |
939e5a1b | 183 | if fail_mode_changed: |
2dd26837 | 184 | ret_str += "Updated fail_mode. " |
939e5a1b EJ |
185 | |
186 | if ret_str != '': | |
187 | return ret_str | |
064af421 | 188 | else: |
5990cd46 | 189 | return "No change to configuration" |
83497018 | 190 | |
064af421 | 191 | def vswitchCurrentController(): |
9cef6345 | 192 | controller = vswitchCfgQuery(["get-manager"]) |
064af421 BP |
193 | if controller == "": |
194 | return controller | |
195 | if len(controller) < 4 or controller[0:4] != "ssl:": | |
064af421 BP |
196 | return controller |
197 | else: | |
5a9e7099 | 198 | return controller.split(':')[1] |
064af421 BP |
199 | |
200 | def removeControllerCfg(): | |
9cef6345 | 201 | vswitchCfgMod(["--", "del-manager", |
f95c85e2 | 202 | "--", "del-ssl"]) |
83497018 | 203 | |
064af421 | 204 | def setControllerCfg(controller): |
88b56f29 BP |
205 | # /etc/xensource/xapi-ssl.pem is mentioned twice below because it |
206 | # contains both the private key and the certificate. | |
9cef6345 | 207 | vswitchCfgMod(["--", "del-manager", |
f95c85e2 JP |
208 | "--", "del-ssl", |
209 | "--", "--bootstrap", "set-ssl", | |
210 | "/etc/xensource/xapi-ssl.pem", | |
90056dc0 | 211 | "/etc/xensource/xapi-ssl.pem", |
bc391960 | 212 | cacert_filename, |
9cef6345 | 213 | "--", "set-manager", 'ssl:' + controller + ':6632']) |
064af421 | 214 | |
7e67d10d | 215 | def vswitchCfgQuery(action_args): |
c1a543a8 | 216 | cmd = [vsctl, "--timeout=5", "-vANY:console:off"] + action_args |
064af421 BP |
217 | output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate() |
218 | if len(output) == 0 or output[0] == None: | |
219 | output = "" | |
220 | else: | |
221 | output = output[0].strip() | |
222 | return output | |
223 | ||
224 | def vswitchCfgMod(action_args): | |
c1a543a8 | 225 | cmd = [vsctl, "--timeout=5", "-vANY:console:off"] + action_args |
064af421 BP |
226 | exitcode = subprocess.call(cmd) |
227 | if exitcode != 0: | |
064af421 BP |
228 | raise XenAPIPlugin.Failure("VSWITCH_CONFIG_MOD_FAILURE", |
229 | [ str(exitcode) , str(action_args) ]) | |
7730bd8f JP |
230 | |
231 | def emergency_reset(session, args): | |
6b7b9d34 | 232 | cmd = [vsctl, "--timeout=5", "emer-reset"] |
59a81d82 JP |
233 | exitcode = subprocess.call(cmd) |
234 | if exitcode != 0: | |
235 | raise XenAPIPlugin.Failure("VSWITCH_EMER_RESET_FAILURE", | |
236 | [ str(exitcode) ]) | |
237 | ||
238 | return "Successfully reset configuration" | |
da7198b4 DT |
239 | |
240 | def addFlow(switch, flow): | |
241 | cmd = [ofctl, "add-flow", switch, flow] | |
242 | exitcode = subprocess.call(cmd) | |
243 | if exitcode != 0: | |
244 | raise XenAPIPlugin.Failure("VSWITCH_ADD_FLOW_FAILURE", | |
245 | [ str(exitcode) , str(switch), str(flow) ]) | |
064af421 | 246 | |
064af421 | 247 | if __name__ == "__main__": |
7730bd8f JP |
248 | XenAPIPlugin.dispatch({"update": update, |
249 | "emergency_reset": emergency_reset}) |