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