meta-flow: Correctly set destination MAC in mf_set_flow_value().
[sliver-openvswitch.git] / xenserver / usr_share_openvswitch_scripts_ovs-xapi-sync
1 #!/usr/bin/python
2 # Copyright (c) 2009, 2010, 2011, 2012 Nicira Networks
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16
17 # A daemon to monitor the external_ids columns of the Bridge and
18 # Interface OVSDB tables for changes that require interrogating XAPI.
19 # Its responsibilities include:
20 #
21 #   - Set the "bridge-id" key in the Bridge table.
22 #   - Set the "iface-id" key in the Interface table.
23 #   - Set the fail-mode on internal bridges.
24
25 import argparse
26 import os
27 import signal
28 import subprocess
29 import sys
30 import time
31
32 import XenAPI
33
34 import ovs.dirs
35 from ovs.db import error
36 from ovs.db import types
37 import ovs.util
38 import ovs.daemon
39 import ovs.db.idl
40
41 root_prefix = ''                # Prefix for absolute file names, for testing.
42 vlog = ovs.vlog.Vlog("ovs-xapi-sync")
43 vsctl = "/usr/bin/ovs-vsctl"
44 session = None
45 force_run = False
46
47
48 # Set up a session to interact with XAPI.
49 #
50 # On system start-up, OVS comes up before XAPI, so we can't log into the
51 # session until later.  Try to do this on-demand, since we won't
52 # actually do anything interesting until XAPI is up.
53 def init_session():
54     global session
55     if session is not None:
56         return True
57
58     try:
59         session = XenAPI.xapi_local()
60         session.xenapi.login_with_password("", "")
61     except XenAPI.Failure, e:
62         session = None
63         vlog.warn("Couldn't login to XAPI (%s)" % e)
64         return False
65
66     return True
67
68
69 def get_network_by_bridge(br_name):
70     if not init_session():
71         vlog.warn("Failed to get bridge id %s because"
72                 " XAPI session could not be initialized" % br_name)
73         return None
74
75     for n in session.xenapi.network.get_all():
76         rec = session.xenapi.network.get_record(n)
77         if rec['bridge'] == br_name:
78             return rec
79
80     return None
81
82
83 # By default, the "bridge-id" external id in the Bridge table is the
84 # same as "xs-network-uuids".  This may be overridden by defining a
85 # "nicira-bridge-id" key in the "other_config" field of the network
86 # record of XAPI.  If nicira-bridge-id is undefined returns default.
87 # On error returns None.
88 def get_bridge_id(br_name, default=None):
89     rec = get_network_by_bridge(br_name)
90     if rec:
91         return rec['other_config'].get('nicira-bridge-id', default)
92     return None
93
94
95 # By default, the "iface-id" external id in the Interface table is the
96 # same as "xs-vif-uuid".  This may be overridden by defining a
97 # "nicira-iface-id" key in the "other_config" field of the VIF
98 # record of XAPI.
99 def get_iface_id(if_name, xs_vif_uuid):
100     if not if_name.startswith("vif") and not if_name.startswith("tap"):
101         # Treat whatever was passed into 'xs_vif_uuid' as a default
102         # value for non-VIFs.
103         return xs_vif_uuid
104
105     if not init_session():
106         vlog.warn("Failed to get interface id %s because"
107                 " XAPI session could not be initialized" % if_name)
108         return xs_vif_uuid
109
110     try:
111         vif = session.xenapi.VIF.get_by_uuid(xs_vif_uuid)
112         rec = session.xenapi.VIF.get_record(vif)
113         return rec['other_config'].get('nicira-iface-id', xs_vif_uuid)
114     except XenAPI.Failure:
115         vlog.warn("Could not find XAPI entry for VIF %s" % if_name)
116         return xs_vif_uuid
117
118
119 def call_vsctl(args):
120     cmd = [vsctl, "--timeout=30", "-vANY:console:off"] + args
121     exitcode = subprocess.call(cmd)
122     if exitcode != 0:
123         vlog.warn("Couldn't call ovs-vsctl")
124
125
126 def set_or_delete(d, key, value):
127     if value is None:
128         if key in d:
129             del d[key]
130             return True
131     else:
132         if d.get(key) != value:
133             d[key] = value
134             return True
135     return False
136
137
138 def set_external_id(row, key, value):
139     external_ids = row.external_ids
140     if set_or_delete(external_ids, key, value):
141         row.external_ids = external_ids
142
143
144 # XenServer does not call interface-reconfigure on internal networks,
145 # which is where the fail-mode would normally be set.
146 def update_fail_mode(row):
147     rec = get_network_by_bridge(row.name)
148     if not rec:
149         return
150
151     fail_mode = rec['other_config'].get('vswitch-controller-fail-mode')
152
153     if not fail_mode:
154         pools = session.xenapi.pool.get_all()
155         if len(pools) == 1:
156             prec = session.xenapi.pool.get_record(pools[0])
157             fail_mode = prec['other_config'].get(
158                     'vswitch-controller-fail-mode')
159
160     if fail_mode not in ['standalone', 'secure']:
161         fail_mode = 'standalone'
162
163     if row.fail_mode != fail_mode:
164         row.fail_mode = fail_mode
165
166
167 def update_in_band_mgmt(row):
168     rec = get_network_by_bridge(row.name)
169     if not rec:
170         return
171
172     dib = rec['other_config'].get('vswitch-disable-in-band')
173
174     other_config = row.other_config
175     if dib and dib not in ['true', 'false']:
176         vlog.warn('"%s" isn\'t a valid setting for '
177                 "other_config:disable-in-band on %s" % (dib, row.name))
178     elif set_or_delete(other_config, 'disable-in-band', dib):
179         row.other_config = other_config
180
181
182 def update_bridge_id(row):
183     id_ = get_bridge_id(row.name, row.external_ids.get("xs-network-uuids"))
184     if not id_:
185         return
186
187     set_external_id(row, "bridge-id", id_.split(";")[0])
188
189
190 def keep_table_columns(schema, table_name, columns):
191     table = schema.tables.get(table_name)
192     if not table:
193         raise error.Error("schema has no %s table" % table_name)
194
195     new_columns = {}
196     for column_name in columns:
197         column = table.columns.get(column_name)
198         if not column:
199             raise error.Error("%s table schema lacks %s column"
200                               % (table_name, column_name))
201         new_columns[column_name] = column
202     table.columns = new_columns
203     return table
204
205
206 def prune_schema(schema):
207     new_tables = {}
208     new_tables["Bridge"] = keep_table_columns(
209         schema, "Bridge", ("name", "external_ids", "other_config",
210                            "fail_mode"))
211     new_tables["Interface"] = keep_table_columns(
212         schema, "Interface", ("name", "external_ids"))
213     schema.tables = new_tables
214
215
216 def handler(signum, _):
217     global force_run
218     if (signum == signal.SIGHUP):
219         force_run = True
220
221
222 def main():
223     global force_run
224
225     parser = argparse.ArgumentParser()
226     parser.add_argument("database", metavar="DATABASE",
227             help="A socket on which ovsdb-server is listening.")
228     parser.add_argument("--root-prefix", metavar="DIR",
229                         help="Use DIR as alternate root directory"
230                         " (for testing).")
231
232     ovs.vlog.add_args(parser)
233     ovs.daemon.add_args(parser)
234     args = parser.parse_args()
235     ovs.vlog.handle_args(args)
236     ovs.daemon.handle_args(args)
237
238     global root_prefix
239     if args.root_prefix:
240         root_prefix = args.root_prefix
241
242     remote = args.database
243     schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
244     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
245     prune_schema(schema)
246     idl = ovs.db.idl.Idl(remote, schema)
247
248     ovs.daemon.daemonize()
249
250     # This daemon is usually started before XAPI, but to complete our
251     # tasks, we need it.  Wait here until it's up.
252     cookie_file = root_prefix + "/var/run/xapi_init_complete.cookie"
253     while not os.path.exists(cookie_file):
254         time.sleep(1)
255
256     signal.signal(signal.SIGHUP, handler)
257
258     bridges = {}                # Map from bridge name to xs_network_uuids
259     iface_ids = {}              # Map from xs-vif-uuid to iface-id
260     seqno = idl.change_seqno    # Sequence number when we last processed the db
261     while True:
262         idl.run()
263         if not force_run and seqno == idl.change_seqno:
264             poller = ovs.poller.Poller()
265             idl.wait(poller)
266             poller.block()
267             continue
268
269         if force_run:
270             vlog.info("Forced to re-run as the result of a SIGHUP")
271             bridges = {}
272             iface_ids = {}
273             force_run = False
274         seqno = idl.change_seqno
275
276         txn = ovs.db.idl.Transaction(idl)
277
278         new_bridges = {}
279         for row in idl.tables["Bridge"].rows.itervalues():
280             old_xnu = bridges.get(row.name)
281             new_xnu = row.external_ids.get("xs-network-uuids", "")
282             if old_xnu is None:
283                 # New bridge.
284                 update_fail_mode(row)
285                 update_in_band_mgmt(row)
286
287             update_bridge_id(row)
288             new_bridges[row.name] = new_xnu
289         bridges = new_bridges
290
291         iface_by_name = {}
292         for row in idl.tables["Interface"].rows.itervalues():
293             iface_by_name[row.name] = row
294
295         new_iface_ids = {}
296         for row in idl.tables["Interface"].rows.itervalues():
297             # Match up paired vif and tap devices.
298             if row.name.startswith("vif"):
299                 vif = row
300                 tap = iface_by_name.get("tap%s" % row.name[3:])
301             elif row.name.startswith("tap"):
302                 tap = row
303                 vif = iface_by_name.get("vif%s" % row.name[3:])
304             else:
305                 tap = vif = None
306
307             # Several tap external-ids need to be copied from the vif.
308             if row == tap and vif:
309                 keys = ["attached-mac",
310                         "xs-network-uuid",
311                         "xs-vif-uuid",
312                         "xs-vm-uuid"]
313                 for k in keys:
314                     set_external_id(row, k, vif.external_ids.get(k))
315
316             # Map from xs-vif-uuid to iface-id.
317             #
318             # (A tap's xs-vif-uuid comes from its vif.  That falls out
319             # naturally from the copy loop above.)
320             xvu = row.external_ids.get("xs-vif-uuid")
321             if xvu:
322                 iface_id = (new_iface_ids.get(xvu)
323                             or iface_ids.get(xvu)
324                             or get_iface_id(row.name, xvu))
325                 new_iface_ids[xvu] = iface_id
326             else:
327                 # No xs-vif-uuid therefore no iface-id.
328                 iface_id = None
329             set_external_id(row, "iface-id", iface_id)
330
331             # When there's a vif and a tap, the tap is active (used for
332             # traffic).  When there's just a vif, the vif is active.
333             #
334             # A tap on its own shouldn't happen, and we don't know
335             # anything about other kinds of devices, so we don't use
336             # an iface-status for those devices at all.
337             if vif and tap:
338                 set_external_id(tap, "iface-status", "active")
339                 set_external_id(vif, "iface-status", "inactive")
340             elif vif:
341                 set_external_id(vif, "iface-status", "active")
342             else:
343                 set_external_id(row, "iface-status", None)
344         iface_ids = new_iface_ids
345
346         txn.commit_block()
347
348
349 if __name__ == '__main__':
350     try:
351         main()
352     except SystemExit:
353         # Let system.exit() calls complete normally
354         raise
355     except:
356         vlog.exception("traceback")
357         sys.exit(ovs.daemon.RESTART_EXIT_CODE)