~bgh/neutron/bugfixes

« back to all changes in this revision

Viewing changes to quantum/plugins/openvswitch/agent/ovs_quantum_agent.py

  • Committer: Somik Behera
  • Date: 2011-06-07 06:07:05 UTC
  • mfrom: (11.1.1 quantum)
  • Revision ID: somik@nicira.com-20110607060705-3rydd5g191de2mss
 Merged Brad's ovsplugin code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
3
# Copyright 2011 Nicira Networks, Inc.
 
4
# All Rights Reserved.
 
5
#
 
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
 
9
#
 
10
#         http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
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
 
16
#    under the License.
 
17
# @author: Somik Behera, Nicira Networks, Inc.
 
18
# @author: Brad Hall, Nicira Networks, Inc.
 
19
# @author: Dan Wendlandt, Nicira Networks, Inc.
 
20
 
 
21
import ConfigParser
 
22
import logging as LOG
 
23
import MySQLdb
 
24
import os
 
25
import sys
 
26
import time
 
27
 
 
28
from optparse import OptionParser
 
29
from subprocess import *
 
30
 
 
31
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
 
32
# attributes set).
 
33
class VifPort:
 
34
    def __init__(self, port_name, ofport, vif_id, vif_mac, switch):
 
35
        self.port_name = port_name
 
36
        self.ofport = ofport
 
37
        self.vif_id = vif_id
 
38
        self.vif_mac = vif_mac
 
39
        self.switch = switch
 
40
    def __str__(self):
 
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
 
44
 
 
45
class OVSBridge:
 
46
    def __init__(self, br_name):
 
47
        self.br_name = br_name
 
48
 
 
49
    def run_cmd(self, args):
 
50
        # LOG.debug("## running command: " + " ".join(args))
 
51
        return Popen(args, stdout=PIPE).communicate()[0]
 
52
 
 
53
    def run_vsctl(self, args):
 
54
        full_args = ["ovs-vsctl" ] + args
 
55
        return self.run_cmd(full_args)
 
56
 
 
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])
 
60
 
 
61
    def delete_port(self, port_name):
 
62
        self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name,
 
63
          port_name])
 
64
 
 
65
    def set_db_attribute(self, table_name, record, column, value):
 
66
        args = [ "set", table_name, record, "%s=%s" % (column,value) ]
 
67
        self.run_vsctl(args)
 
68
 
 
69
    def clear_db_attribute(self, table_name,record, column):
 
70
        args = [ "clear", table_name, record, column ]
 
71
        self.run_vsctl(args)
 
72
 
 
73
    def run_ofctl(self, cmd, args):
 
74
        full_args = ["ovs-ofctl", cmd, self.br_name ] + args
 
75
        return self.run_cmd(full_args)
 
76
 
 
77
    def remove_all_flows(self):
 
78
        self.run_ofctl("del-flows", [])
 
79
 
 
80
    def get_port_ofport(self, port_name):
 
81
        return self.db_get_val("Interface", port_name, "ofport")
 
82
 
 
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"
 
88
 
 
89
        flow_str = "priority=%s" % dict["priority"]
 
90
        if "match" in dict:
 
91
            flow_str += "," + dict["match"]
 
92
        flow_str += ",actions=%s" % (dict["actions"])
 
93
        self.run_ofctl("add-flow", [ flow_str ] )
 
94
 
 
95
    def delete_flows(self,**dict):
 
96
        all_args = []
 
97
        if "priority" in dict:
 
98
            all_args.append("priority=%s" % dict["priority"])
 
99
        if "match" in dict:
 
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 ] )
 
105
 
 
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)
 
109
 
 
110
    def db_get_val(self, table, record, column):
 
111
        return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
 
112
 
 
113
    def db_str_to_map(self, full_str):
 
114
        list = full_str.strip("{}").split(", ")
 
115
        ret = {}
 
116
        for e in list:
 
117
            if e.find("=") == -1:
 
118
                continue
 
119
            arr = e.split("=")
 
120
            ret[arr[0]] = arr[1].strip("\"")
 
121
        return ret
 
122
 
 
123
    def get_port_name_list(self):
 
124
        res = self.run_vsctl([ "list-ports", self.br_name])
 
125
        return res.split("\n")[0:-1]
 
126
 
 
127
    def get_port_stats(self, port_name):
 
128
        return self.db_get_map("Interface", port_name, "statistics")
 
129
 
 
130
    # returns a VIF object for each VIF port
 
131
    def get_vif_ports(self):
 
132
        edge_ports = []
 
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)
 
140
                edge_ports.append(p)
 
141
            else:
 
142
                # iface-id might not be set.  See if we can figure it out and
 
143
                # set it here.
 
144
                external_ids = self.db_get_map("Interface",name,"external_ids")
 
145
                if "attached-mac" not in external_ids:
 
146
                    continue
 
147
                vif_uuid = external_ids.get("xs-vif-uuid", "")
 
148
                if len(vif_uuid) == 0:
 
149
                    continue
 
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()
 
152
                res = res.strip()
 
153
                if len(res) == 0:
 
154
                    continue
 
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)
 
162
                edge_ports.append(p)
 
163
        return edge_ports
 
164
 
 
165
class OVSNaaSPlugin:
 
166
    def __init__(self, integ_br):
 
167
        self.setup_integration_br(integ_br)
 
168
 
 
169
    def port_bound(self, port, vlan_id):
 
170
        self.int_br.set_db_attribute("Port", port.port_name,"tag",
 
171
          str(vlan_id))
 
172
 
 
173
    def port_unbound(self, port, still_exists):
 
174
        if still_exists:
 
175
            self.int_br.clear_db_attribute("Port", port.port_name,"tag")
 
176
 
 
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")
 
186
 
 
187
    def daemon_loop(self, conn):
 
188
        self.local_vlan_map = {}
 
189
        old_local_bindings = {}
 
190
        old_vif_ports = {}
 
191
 
 
192
        while True:
 
193
            cursor = conn.cursor()
 
194
            cursor.execute("SELECT * FROM network_bindings")
 
195
            rows = cursor.fetchall()
 
196
            cursor.close()
 
197
            all_bindings = {}
 
198
            for r in rows:
 
199
                all_bindings[r[2]] = r[1]
 
200
 
 
201
            cursor = conn.cursor()
 
202
            cursor.execute("SELECT * FROM vlan_bindings")
 
203
            rows = cursor.fetchall()
 
204
            cursor.close()
 
205
            vlan_bindings = {}
 
206
            for r in rows:
 
207
                vlan_bindings[r[1]] = r[0]
 
208
 
 
209
            new_vif_ports = {}
 
210
            new_local_bindings = {}
 
211
            vif_ports = self.int_br.get_vif_ports()
 
212
            for p in 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]
 
216
                else:
 
217
                    # no binding, put him on the 'dead vlan'
 
218
                    self.int_br.set_db_attribute("Port", p.port_name, "tag",
 
219
                      "4095")
 
220
                old_b = old_local_bindings.get(p.vif_id,None)
 
221
                new_b = new_local_bindings.get(p.vif_id,None)
 
222
                if old_b != new_b:
 
223
                    if old_b is not None:
 
224
                        LOG.info("Removing binding to net-id = %s for %s"
 
225
                          % (old_b, str(p)))
 
226
                        self.port_unbound(p, True)
 
227
                    if new_b is not None:
 
228
                        LOG.info("Adding binding to net-id = %s for %s" \
 
229
                          % (new_b, str(p)))
 
230
                        # If we don't have a binding we have to stick it on
 
231
                        # the dead vlan
 
232
                        vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
 
233
                          "4095")
 
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)
 
241
 
 
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"])
 
246
            time.sleep(2)
 
247
 
 
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")
 
253
 
 
254
    options, args = parser.parse_args()
 
255
 
 
256
    if options.verbose:
 
257
        LOG.basicConfig(level=LOG.DEBUG)
 
258
    else:
 
259
        LOG.basicConfig(level=LOG.WARN)
 
260
 
 
261
    if len(args) != 1:
 
262
        parser.print_help()
 
263
        sys.exit(1)
 
264
 
 
265
    config_file = args[0]
 
266
    config = ConfigParser.ConfigParser()
 
267
    try:
 
268
        config.read(config_file)
 
269
    except Exception, e:
 
270
        LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
 
271
          str(e)))
 
272
 
 
273
    integ_br = config.get("OVS", "integration-bridge")
 
274
 
 
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")
 
279
    conn = None
 
280
    try:
 
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)
 
286
    finally:
 
287
        if conn:
 
288
            conn.close()
 
289
 
 
290
    sys.exit(0)