585a89469f85e25448abed95acb1439e3710d8d3
[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 keep_table_columns(schema, table_name, columns):
196     table = schema.tables.get(table_name)
197     if not table:
198         raise error.Error("schema has no %s table" % table_name)
199
200     new_columns = {}
201     for column_name in columns:
202         column = table.columns.get(column_name)
203         if not column:
204             raise error.Error("%s table schema lacks %s column"
205                               % (table_name, column_name))
206         new_columns[column_name] = column
207     table.columns = new_columns
208     return table
209
210
211 def prune_schema(schema):
212     new_tables = {}
213     new_tables["Bridge"] = keep_table_columns(
214         schema, "Bridge", ("name", "external_ids", "other_config",
215                            "fail_mode"))
216     new_tables["Interface"] = keep_table_columns(
217         schema, "Interface", ("name", "external_ids"))
218     schema.tables = new_tables
219
220
221 def handler(signum, _):
222     global force_run
223     if (signum == signal.SIGHUP):
224         force_run = True
225
226
227 def main():
228     global force_run
229
230     parser = argparse.ArgumentParser()
231     parser.add_argument("database", metavar="DATABASE",
232             help="A socket on which ovsdb-server is listening.")
233     parser.add_argument("--root-prefix", metavar="DIR", default='',
234                         help="Use DIR as alternate root directory"
235                         " (for testing).")
236
237     ovs.vlog.add_args(parser)
238     ovs.daemon.add_args(parser)
239     args = parser.parse_args()
240     ovs.vlog.handle_args(args)
241     ovs.daemon.handle_args(args)
242
243     remote = args.database
244     schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR
245     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
246     prune_schema(schema)
247     idl = ovs.db.idl.Idl(remote, schema)
248
249     ovs.daemon.daemonize()
250
251     # This daemon is usually started before XAPI, but to complete our
252     # tasks, we need it.  Wait here until it's up.
253     cookie_file = args.root_prefix + "/var/run/xapi_init_complete.cookie"
254     while not os.path.exists(cookie_file):
255         time.sleep(1)
256
257     signal.signal(signal.SIGHUP, handler)
258
259     bridges = {}                # Map from bridge name to nicira-bridge-id
260     iface_ids = {}              # Map from xs-vif-uuid to iface-id
261     vm_ids = {}                 # Map from xs-vm-uuid to vm-id
262     seqno = idl.change_seqno    # Sequence number when we last processed the db
263     while True:
264         idl.run()
265         if not force_run and seqno == idl.change_seqno:
266             poller = ovs.poller.Poller()
267             idl.wait(poller)
268             poller.block()
269             continue
270
271         if force_run:
272             vlog.info("Forced to re-run as the result of a SIGHUP")
273             bridges = {}
274             iface_ids = {}
275             vm_ids = {}
276             force_run = False
277         seqno = idl.change_seqno
278
279         txn = ovs.db.idl.Transaction(idl)
280
281         new_bridges = {}
282         for row in idl.tables["Bridge"].rows.itervalues():
283             if row.name in bridges:
284                 nbd = bridges[row.name]
285             else:
286                 # New bridge.
287                 update_fail_mode(row)
288                 update_in_band_mgmt(row)
289                 nbd = get_bridge_id(row.name)
290
291             bridge_id = nbd
292             if bridge_id is None:
293                 bridge_id = row.external_ids.get("xs-network-uuids")
294
295             if bridge_id is not None:
296                 set_external_id(row, "bridge-id", bridge_id.split(";")[0])
297
298             new_bridges[row.name] = nbd
299         bridges = new_bridges
300
301         iface_by_name = {}
302         for row in idl.tables["Interface"].rows.itervalues():
303             iface_by_name[row.name] = row
304
305         new_iface_ids = {}
306         new_vm_ids = {}
307         for row in idl.tables["Interface"].rows.itervalues():
308             # Match up paired vif and tap devices.
309             if row.name.startswith("vif"):
310                 vif = row
311                 tap = iface_by_name.get("tap%s" % row.name[3:])
312             elif row.name.startswith("tap"):
313                 tap = row
314                 vif = iface_by_name.get("vif%s" % row.name[3:])
315             else:
316                 tap = vif = None
317
318             # Several tap external-ids need to be copied from the vif.
319             if row == tap and vif:
320                 keys = ["attached-mac",
321                         "xs-network-uuid",
322                         "xs-vif-uuid",
323                         "xs-vm-uuid"]
324                 for k in keys:
325                     set_external_id(row, k, vif.external_ids.get(k))
326
327             # Map from xs-vif-uuid to iface-id.
328             #
329             # (A tap's xs-vif-uuid comes from its vif.  That falls out
330             # naturally from the copy loop above.)
331             xvu = row.external_ids.get("xs-vif-uuid")
332             if xvu:
333                 iface_id = (new_iface_ids.get(xvu)
334                             or iface_ids.get(xvu)
335                             or get_iface_id(row.name, xvu))
336                 new_iface_ids[xvu] = iface_id
337             else:
338                 # No xs-vif-uuid therefore no iface-id.
339                 iface_id = None
340             set_external_id(row, "iface-id", iface_id)
341
342             # Map from xs-vm-uuid to vm-id.
343             xvmu = row.external_ids.get("xs-vm-uuid")
344             if xvmu:
345                 vm_id = (new_vm_ids.get(xvmu)
346                          or vm_ids.get(xvmu)
347                          or get_vm_id(row.name, xvmu))
348                 new_vm_ids[xvmu] = vm_id
349             else:
350                 vm_id = None
351             set_external_id(row, "vm-id", vm_id)
352
353             # When there's a vif and a tap, the tap is active (used for
354             # traffic).  When there's just a vif, the vif is active.
355             #
356             # A tap on its own shouldn't happen, and we don't know
357             # anything about other kinds of devices, so we don't use
358             # an iface-status for those devices at all.
359             if vif and tap:
360                 set_external_id(tap, "iface-status", "active")
361                 set_external_id(vif, "iface-status", "inactive")
362             elif vif:
363                 set_external_id(vif, "iface-status", "active")
364             else:
365                 set_external_id(row, "iface-status", None)
366         iface_ids = new_iface_ids
367         vm_ids = new_vm_ids
368
369         txn.commit_block()
370
371
372 if __name__ == '__main__':
373     try:
374         main()
375     except SystemExit:
376         # Let system.exit() calls complete normally
377         raise
378     except:
379         vlog.exception("traceback")
380         sys.exit(ovs.daemon.RESTART_EXIT_CODE)