~registry/neutron/github

« back to all changes in this revision

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

  • Committer: Brad Hall
  • Date: 2011-11-28 18:33:52 UTC
  • Revision ID: git-v1:6a08320031d03913981c444cce97c7ccd08c8169
Second round of packaging changes

This change condenses the directory structure to something more similar to
what we had before while producing similar packages.

It also introduces version.py which allows us to get the version from git tags
(or a fallback version if not available).

Fixes lp bug 889336
Fixes lp bug 888795

Change-Id: I86136bd9dbabb5eb1f8366ed665ed9b54f695124

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
 
 
32
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
 
33
# attributes set).
 
34
class VifPort:
 
35
    def __init__(self, port_name, ofport, vif_id, vif_mac, switch):
 
36
        self.port_name = port_name
 
37
        self.ofport = ofport
 
38
        self.vif_id = vif_id
 
39
        self.vif_mac = vif_mac
 
40
        self.switch = switch
 
41
 
 
42
    def __str__(self):
 
43
        return "iface-id=" + self.vif_id + ", vif_mac=" + \
 
44
          self.vif_mac + ", port_name=" + self.port_name + \
 
45
          ", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name
 
46
 
 
47
 
 
48
class OVSBridge:
 
49
    def __init__(self, br_name):
 
50
        self.br_name = br_name
 
51
 
 
52
    def run_cmd(self, args):
 
53
        # LOG.debug("## running command: " + " ".join(args))
 
54
        return Popen(args, stdout=PIPE).communicate()[0]
 
55
 
 
56
    def run_vsctl(self, args):
 
57
        full_args = ["ovs-vsctl"] + args
 
58
        return self.run_cmd(full_args)
 
59
 
 
60
    def reset_bridge(self):
 
61
        self.run_vsctl(["--", "--if-exists", "del-br", self.br_name])
 
62
        self.run_vsctl(["add-br", self.br_name])
 
63
 
 
64
    def delete_port(self, port_name):
 
65
        self.run_vsctl(["--", "--if-exists", "del-port", self.br_name,
 
66
          port_name])
 
67
 
 
68
    def set_db_attribute(self, table_name, record, column, value):
 
69
        args = ["set", table_name, record, "%s=%s" % (column, value)]
 
70
        self.run_vsctl(args)
 
71
 
 
72
    def clear_db_attribute(self, table_name, record, column):
 
73
        args = ["clear", table_name, record, column]
 
74
        self.run_vsctl(args)
 
75
 
 
76
    def run_ofctl(self, cmd, args):
 
77
        full_args = ["ovs-ofctl", cmd, self.br_name] + args
 
78
        return self.run_cmd(full_args)
 
79
 
 
80
    def remove_all_flows(self):
 
81
        self.run_ofctl("del-flows", [])
 
82
 
 
83
    def get_port_ofport(self, port_name):
 
84
        return self.db_get_val("Interface", port_name, "ofport")
 
85
 
 
86
    def add_flow(self, **dict):
 
87
        if "actions" not in dict:
 
88
            raise Exception("must specify one or more actions")
 
89
        if "priority" not in dict:
 
90
            dict["priority"] = "0"
 
91
 
 
92
        flow_str = "priority=%s" % dict["priority"]
 
93
        if "match" in dict:
 
94
            flow_str += "," + dict["match"]
 
95
        flow_str += ",actions=%s" % (dict["actions"])
 
96
        self.run_ofctl("add-flow", [flow_str])
 
97
 
 
98
    def delete_flows(self, **dict):
 
99
        all_args = []
 
100
        if "priority" in dict:
 
101
            all_args.append("priority=%s" % dict["priority"])
 
102
        if "match" in dict:
 
103
            all_args.append(dict["match"])
 
104
        if "actions" in dict:
 
105
            all_args.append("actions=%s" % (dict["actions"]))
 
106
        flow_str = ",".join(all_args)
 
107
        self.run_ofctl("del-flows", [flow_str])
 
108
 
 
109
    def db_get_map(self, table, record, column):
 
110
        str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
 
111
        return self.db_str_to_map(str)
 
112
 
 
113
    def db_get_val(self, table, record, column):
 
114
        return self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
 
115
 
 
116
    def db_str_to_map(self, full_str):
 
117
        list = full_str.strip("{}").split(", ")
 
118
        ret = {}
 
119
        for e in list:
 
120
            if e.find("=") == -1:
 
121
                continue
 
122
            arr = e.split("=")
 
123
            ret[arr[0]] = arr[1].strip("\"")
 
124
        return ret
 
125
 
 
126
    def get_port_name_list(self):
 
127
        res = self.run_vsctl(["list-ports", self.br_name])
 
128
        return res.split("\n")[0:-1]
 
129
 
 
130
    def get_port_stats(self, port_name):
 
131
        return self.db_get_map("Interface", port_name, "statistics")
 
132
 
 
133
    def get_xapi_iface_id(self, xs_vif_uuid):
 
134
        return self.run_cmd(
 
135
                        ["xe",
 
136
                        "vif-param-get",
 
137
                        "param-name=other-config",
 
138
                        "param-key=nicira-iface-id",
 
139
                        "uuid=%s" % xs_vif_uuid]).strip()
 
140
 
 
141
    # returns a VIF object for each VIF port
 
142
    def get_vif_ports(self):
 
143
        edge_ports = []
 
144
        port_names = self.get_port_name_list()
 
145
        for name in port_names:
 
146
            external_ids = self.db_get_map("Interface", name, "external_ids")
 
147
            ofport = self.db_get_val("Interface", name, "ofport")
 
148
            if "iface-id" in external_ids and "attached-mac" in external_ids:
 
149
                p = VifPort(name, ofport, external_ids["iface-id"],
 
150
                            external_ids["attached-mac"], self)
 
151
                edge_ports.append(p)
 
152
            elif "xs-vif-uuid" in external_ids and \
 
153
                 "attached-mac" in external_ids:
 
154
                # if this is a xenserver and iface-id is not automatically
 
155
                # synced to OVS from XAPI, we grab it from XAPI directly
 
156
                iface_id = self.get_xapi_iface_id(external_ids["xs-vif-uuid"])
 
157
                p = VifPort(name, ofport, iface_id,
 
158
                            external_ids["attached-mac"], self)
 
159
                edge_ports.append(p)
 
160
 
 
161
        return edge_ports
 
162
 
 
163
 
 
164
class OVSQuantumAgent:
 
165
 
 
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
        self.int_br.delete_flows(match="in_port=%s" % port.ofport)
 
173
 
 
174
    def port_unbound(self, port, still_exists):
 
175
        if still_exists:
 
176
            self.int_br.clear_db_attribute("Port", port.port_name, "tag")
 
177
 
 
178
    def setup_integration_br(self, integ_br):
 
179
        self.int_br = OVSBridge(integ_br)
 
180
        self.int_br.remove_all_flows()
 
181
        # switch all traffic using L2 learning
 
182
        self.int_br.add_flow(priority=1, actions="normal")
 
183
 
 
184
    def daemon_loop(self, conn):
 
185
        self.local_vlan_map = {}
 
186
        old_local_bindings = {}
 
187
        old_vif_ports = {}
 
188
 
 
189
        while True:
 
190
            cursor = conn.cursor()
 
191
            cursor.execute("SELECT * FROM ports where state = 'ACTIVE'")
 
192
            rows = cursor.fetchall()
 
193
            cursor.close()
 
194
            all_bindings = {}
 
195
            for r in rows:
 
196
                all_bindings[r[2]] = r[1]
 
197
 
 
198
            cursor = conn.cursor()
 
199
            cursor.execute("SELECT * FROM vlan_bindings")
 
200
            rows = cursor.fetchall()
 
201
            cursor.close()
 
202
            vlan_bindings = {}
 
203
            for r in rows:
 
204
                vlan_bindings[r[1]] = r[0]
 
205
 
 
206
            new_vif_ports = {}
 
207
            new_local_bindings = {}
 
208
            vif_ports = self.int_br.get_vif_ports()
 
209
            for p in vif_ports:
 
210
                new_vif_ports[p.vif_id] = p
 
211
                if p.vif_id in all_bindings:
 
212
                    new_local_bindings[p.vif_id] = all_bindings[p.vif_id]
 
213
                else:
 
214
                    # no binding, put him on the 'dead vlan'
 
215
                    self.int_br.set_db_attribute("Port", p.port_name, "tag",
 
216
                              "4095")
 
217
                    self.int_br.add_flow(priority=2,
 
218
                           match="in_port=%s" % p.ofport, actions="drop")
 
219
 
 
220
                old_b = old_local_bindings.get(p.vif_id, None)
 
221
                new_b = new_local_bindings.get(p.vif_id, None)
 
222
 
 
223
                if old_b != new_b:
 
224
                    if old_b is not None:
 
225
                        LOG.info("Removing binding to net-id = %s for %s"
 
226
                          % (old_b, str(p)))
 
227
                        self.port_unbound(p, True)
 
228
                    if new_b is not None:
 
229
                        # If we don't have a binding we have to stick it on
 
230
                        # the dead vlan
 
231
                        vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
 
232
                          "4095")
 
233
                        self.port_bound(p, vlan_id)
 
234
                        LOG.info("Adding binding to net-id = %s " \
 
235
                             "for %s on vlan %s" % (new_b, str(p), vlan_id))
 
236
            for vif_id in old_vif_ports.keys():
 
237
                if vif_id not in new_vif_ports:
 
238
                    LOG.info("Port Disappeared: %s" % vif_id)
 
239
                    if vif_id in old_local_bindings:
 
240
                        old_b = old_local_bindings[vif_id]
 
241
                        self.port_unbound(old_vif_ports[vif_id], False)
 
242
 
 
243
            old_vif_ports = new_vif_ports
 
244
            old_local_bindings = new_local_bindings
 
245
            time.sleep(2)
 
246
 
 
247
if __name__ == "__main__":
 
248
    usagestr = "%prog [OPTIONS] <config file>"
 
249
    parser = OptionParser(usage=usagestr)
 
250
    parser.add_option("-v", "--verbose", dest="verbose",
 
251
      action="store_true", default=False, help="turn on verbose logging")
 
252
 
 
253
    options, args = parser.parse_args()
 
254
 
 
255
    if options.verbose:
 
256
        LOG.basicConfig(level=LOG.DEBUG)
 
257
    else:
 
258
        LOG.basicConfig(level=LOG.WARN)
 
259
 
 
260
    if len(args) != 1:
 
261
        parser.print_help()
 
262
        sys.exit(1)
 
263
 
 
264
    config_file = args[0]
 
265
    config = ConfigParser.ConfigParser()
 
266
    try:
 
267
        config.read(config_file)
 
268
    except Exception, e:
 
269
        LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
 
270
          str(e)))
 
271
 
 
272
    integ_br = config.get("OVS", "integration-bridge")
 
273
 
 
274
    db_name = config.get("DATABASE", "name")
 
275
    db_user = config.get("DATABASE", "user")
 
276
    db_pass = config.get("DATABASE", "pass")
 
277
    db_host = config.get("DATABASE", "host")
 
278
    conn = None
 
279
    try:
 
280
        LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
 
281
        conn = MySQLdb.connect(host=db_host, user=db_user,
 
282
          passwd=db_pass, db=db_name)
 
283
        plugin = OVSQuantumAgent(integ_br)
 
284
        plugin.daemon_loop(conn)
 
285
    finally:
 
286
        if conn:
 
287
            conn.close()
 
288
 
 
289
    sys.exit(0)