2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 Nicira Networks, Inc.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
17
# @author: Somik Behera, Nicira Networks, Inc.
18
# @author: Brad Hall, Nicira Networks, Inc.
19
# @author: Dan Wendlandt, Nicira Networks, Inc.
28
from optparse import OptionParser
29
from subprocess import *
31
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
34
def __init__(self, port_name, ofport, vif_id, vif_mac, switch):
35
self.port_name = port_name
38
self.vif_mac = vif_mac
41
return "iface-id=" + self.vif_id + ", vif_mac=" + \
42
self.vif_mac + ", port_name=" + self.port_name + \
43
", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name
46
def __init__(self, br_name):
47
self.br_name = br_name
49
def run_cmd(self, args):
50
# LOG.debug("## running command: " + " ".join(args))
51
return Popen(args, stdout=PIPE).communicate()[0]
53
def run_vsctl(self, args):
54
full_args = ["ovs-vsctl" ] + args
55
return self.run_cmd(full_args)
57
def reset_bridge(self):
58
self.run_vsctl([ "--" , "--if-exists", "del-br", self.br_name])
59
self.run_vsctl(["add-br", self.br_name])
61
def delete_port(self, port_name):
62
self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name,
65
def set_db_attribute(self, table_name, record, column, value):
66
args = [ "set", table_name, record, "%s=%s" % (column,value) ]
69
def clear_db_attribute(self, table_name,record, column):
70
args = [ "clear", table_name, record, column ]
73
def run_ofctl(self, cmd, args):
74
full_args = ["ovs-ofctl", cmd, self.br_name ] + args
75
return self.run_cmd(full_args)
77
def remove_all_flows(self):
78
self.run_ofctl("del-flows", [])
80
def get_port_ofport(self, port_name):
81
return self.db_get_val("Interface", port_name, "ofport")
83
def add_flow(self,**dict):
84
if "actions" not in dict:
85
raise Exception("must specify one or more actions")
86
if "priority" not in dict:
87
dict["priority"] = "0"
89
flow_str = "priority=%s" % dict["priority"]
91
flow_str += "," + dict["match"]
92
flow_str += ",actions=%s" % (dict["actions"])
93
self.run_ofctl("add-flow", [ flow_str ] )
95
def delete_flows(self,**dict):
97
if "priority" in dict:
98
all_args.append("priority=%s" % dict["priority"])
100
all_args.append(dict["match"])
101
if "actions" in dict:
102
all_args.append("actions=%s" % (dict["actions"]))
103
flow_str = ",".join(all_args)
104
self.run_ofctl("del-flows", [ flow_str ] )
106
def db_get_map(self, table, record, column):
107
str = self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
108
return self.db_str_to_map(str)
110
def db_get_val(self, table, record, column):
111
return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
113
def db_str_to_map(self, full_str):
114
list = full_str.strip("{}").split(", ")
117
if e.find("=") == -1:
120
ret[arr[0]] = arr[1].strip("\"")
123
def get_port_name_list(self):
124
res = self.run_vsctl([ "list-ports", self.br_name])
125
return res.split("\n")[0:-1]
127
def get_port_stats(self, port_name):
128
return self.db_get_map("Interface", port_name, "statistics")
130
# returns a VIF object for each VIF port
131
def get_vif_ports(self):
133
port_names = self.get_port_name_list()
134
for name in port_names:
135
external_ids = self.db_get_map("Interface",name,"external_ids")
136
if "iface-id" in external_ids and "attached-mac" in external_ids:
137
ofport = self.db_get_val("Interface",name,"ofport")
138
p = VifPort(name, ofport, external_ids["iface-id"],
139
external_ids["attached-mac"], self)
142
# iface-id might not be set. See if we can figure it out and
144
external_ids = self.db_get_map("Interface",name,"external_ids")
145
if "attached-mac" not in external_ids:
147
vif_uuid = external_ids.get("xs-vif-uuid", "")
148
if len(vif_uuid) == 0:
150
LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid)
151
res = os.popen("xe vif-param-get param-name=other-config uuid=%s | grep nicira-iface-id | awk '{print $2}'" % vif_uuid).readline()
155
external_ids["iface-id"] = res
156
LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res))
157
self.set_db_attribute("Interface", name,
158
"external-ids:iface-id", res)
159
ofport = self.db_get_val("Interface",name,"ofport")
160
p = VifPort(name, ofport, external_ids["iface-id"],
161
external_ids["attached-mac"], self)
166
def __init__(self, integ_br):
167
self.setup_integration_br(integ_br)
169
def port_bound(self, port, vlan_id):
170
self.int_br.set_db_attribute("Port", port.port_name,"tag",
173
def port_unbound(self, port, still_exists):
175
self.int_br.clear_db_attribute("Port", port.port_name,"tag")
177
def setup_integration_br(self, integ_br):
178
self.int_br = OVSBridge(integ_br)
179
self.int_br.remove_all_flows()
180
# drop all traffic on the 'dead vlan'
181
self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop")
182
# switch all other traffic using L2 learning
183
self.int_br.add_flow(priority=1, actions="normal")
184
# FIXME send broadcast everywhere, regardless of tenant
185
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff", actions="normal")
187
def daemon_loop(self, conn):
188
self.local_vlan_map = {}
189
old_local_bindings = {}
193
cursor = conn.cursor()
194
cursor.execute("SELECT * FROM network_bindings")
195
rows = cursor.fetchall()
199
all_bindings[r[2]] = r[1]
201
cursor = conn.cursor()
202
cursor.execute("SELECT * FROM vlan_bindings")
203
rows = cursor.fetchall()
207
vlan_bindings[r[1]] = r[0]
210
new_local_bindings = {}
211
vif_ports = self.int_br.get_vif_ports()
213
new_vif_ports[p.vif_id] = p
214
if p.vif_id in all_bindings:
215
new_local_bindings[p.vif_id] = all_bindings[p.vif_id]
217
# no binding, put him on the 'dead vlan'
218
self.int_br.set_db_attribute("Port", p.port_name, "tag",
220
old_b = old_local_bindings.get(p.vif_id,None)
221
new_b = new_local_bindings.get(p.vif_id,None)
223
if old_b is not None:
224
LOG.info("Removing binding to net-id = %s for %s"
226
self.port_unbound(p, True)
227
if new_b is not None:
228
LOG.info("Adding binding to net-id = %s for %s" \
230
# If we don't have a binding we have to stick it on
232
vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
234
self.port_bound(p, vlan_id)
235
for vif_id in old_vif_ports.keys():
236
if vif_id not in new_vif_ports:
237
LOG.info("Port Disappeared: %s" % vif_id)
238
if vif_id in old_local_bindings:
239
old_b = old_local_bindings[vif_id]
240
self.port_unbound(old_vif_ports[vif_id], False)
242
old_vif_ports = new_vif_ports
243
old_local_bindings = new_local_bindings
244
self.int_br.run_cmd(["bash",
245
"/etc/xapi.d/plugins/set_external_ids.sh"])
248
if __name__ == "__main__":
249
usagestr = "%prog [OPTIONS] <config file>"
250
parser = OptionParser(usage=usagestr)
251
parser.add_option("-v", "--verbose", dest="verbose",
252
action="store_true", default=False, help="turn on verbose logging")
254
options, args = parser.parse_args()
257
LOG.basicConfig(level=LOG.DEBUG)
259
LOG.basicConfig(level=LOG.WARN)
265
config_file = args[0]
266
config = ConfigParser.ConfigParser()
268
config.read(config_file)
270
LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
273
integ_br = config.get("OVS", "integration-bridge")
275
db_name = config.get("DATABASE", "name")
276
db_user = config.get("DATABASE", "user")
277
db_pass = config.get("DATABASE", "pass")
278
db_host = config.get("DATABASE", "host")
281
LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
282
conn = MySQLdb.connect(host=db_host, user=db_user,
283
passwd=db_pass, db=db_name)
284
plugin = OVSNaaSPlugin(integ_br)
285
plugin.daemon_loop(conn)