a little nicer
[nodemanager.git] / plugins / privatebridge.py
1 """
2 Private Bridge configurator.
3 """
4
5 import select
6 import subprocess
7 import time
8 import tools
9
10 import logger
11
12 priority = 9
13
14 class OvsException (Exception) :
15     def __init__(self, message="no message"):
16         self.message = message
17     def __repr__(self):
18         return self.message
19
20 def start():
21     logger.log('private bridge plugin starting up...')
22
23 def log_call_read(command, timeout=logger.default_timeout_minutes*60, poll=1):
24     message = " ".join(command)
25     logger.log("log_call: running command %s" % message)
26     logger.verbose("log_call: timeout=%r s" % timeout)
27     logger.verbose("log_call: poll=%r s" % poll)
28     trigger = time.time()+timeout
29     try:
30         child = subprocess.Popen(
31             command, bufsize=1,
32             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
33             close_fds=True,
34             universal_newlines=True)
35
36         stdout = ""
37         while True:
38             # see if anything can be read within the poll interval
39             (r, w, x) = select.select([child.stdout], [], [], poll)
40             if r:
41                 stdout = stdout + child.stdout.read(1)
42             # is process over ?
43             returncode=child.poll()
44             # yes
45             if returncode != None:
46                 stdout = stdout + child.stdout.read()
47                 # child is done and return 0
48                 if returncode == 0:
49                     logger.log("log_call:end command (%s) completed" % message)
50                     if stdout != "":
51                         logger.log("log_call:stdout: %s" % stdout)
52                     return (returncode, stdout)
53                 # child has failed
54                 else:
55                     log("log_call:end command (%s) returned with code %d"
56                         %(message, returncode))
57                     return (returncode, stdout)
58             # no : still within timeout ?
59             if time.time() >= trigger:
60                 child.terminate()
61                 logger.log("log_call:end terminating command (%s) - exceeded timeout %d s"
62                            %(message, timeout))
63                 return (-2, None)
64                 break
65     except Exception as e:
66         logger.log_exc("failed to run command %s -> %s" % (message, e))
67
68     return (-1, None)
69
70 ### Thierry - 23 Sept 2014
71 # regardless of this being shipped on lxc-only or on all nodes,
72 # it is safer to check for the availability of the ovs-vsctl command and just print
73 # out a warning when it's not there, instead of a nasty traceback
74 def ovs_available ():
75     "return True if ovs-vsctl can be run"
76     try:
77         child = subprocess.Popen (['ovs-vsctl', '--help'])
78         child.communicate()
79         return True
80     except:
81         return False
82
83 def ovs_vsctl(args):
84     return log_call_read(["ovs-vsctl"] + args)
85
86 def ovs_listbridge():
87     (returncode, stdout) = ovs_vsctl(["list-br"])
88     if (returncode != 0): raise OvsException("list-br")
89     return stdout.split()
90
91 def ovs_addbridge(name):
92     (returncode, stdout) = ovs_vsctl(["add-br", name])
93     if (returncode != 0): raise OvsException("add-br")
94
95 def ovs_listports(name):
96     (returncode, stdout) = ovs_vsctl(["list-ports", name])
97     if (returncode != 0): raise OvsException("list-ports")
98     return stdout.split()
99
100 def ovs_delbridge(name):
101     (returncode, stdout) = ovs_vsctl(["del-br", name])
102     if (returncode != 0): raise OvsException("del-br")
103
104 def ovs_addport(name, portname, type, remoteip, key):
105     args = ["add-port", name, portname, "--", "set", "interface", portname, "type="+type]
106     if remoteip:
107         args = args + ["options:remote_ip=" + remoteip]
108     if key:
109         args = args + ["options:key=" + str(key)]
110
111     returncode, stdout = ovs_vsctl(args)
112     if (returncode != 0):
113         raise OvsException("add-port")
114
115 def ovs_delport(name, portname):
116     (returncode, stdout) = ovs_vsctl(["del-port", name, portname])
117     if (returncode != 0): raise OvsException("del-port")
118
119 def ensure_slicebridge_created(name, addr):
120     bridges = ovs_listbridge()
121     logger.log("privatebridge: current bridges = " + ",".join(bridges))
122     if name in bridges:
123         return
124
125     ovs_addbridge(name)
126
127     logger.log_call(["ifconfig", name, addr, "netmask", "255.0.0.0"])
128
129 def ensure_slicebridge_neighbors(name, sliver_id, neighbors):
130     ports = ovs_listports(name)
131
132     want_ports = []
133     for neighbor in neighbors:
134         (neighbor_node_id, neighbor_ip) = neighbor.split("/")
135         neighbor_node_id = int(neighbor_node_id)
136         portname = "gre%d-%d" % (sliver_id, neighbor_node_id)
137
138         want_ports.append(portname)
139
140         if not portname in ports:
141             ovs_addport(name, portname, "gre", neighbor_ip, sliver_id)
142
143     for portname in ports:
144         if portname.startswith("gre") and (portname not in want_ports):
145             ovs_delport(name, portname)
146
147 def configure_slicebridge(sliver, attributes):
148     sliver_name = sliver['name']
149     sliver_id = sliver['slice_id']
150
151     slice_bridge_name = attributes["slice_bridge_name"]
152
153     slice_bridge_addr = attributes.get("slice_bridge_addr", None)
154     if not slice_bridge_addr:
155         logger.log("privatebridge: no slice_bridge_addr for %s" % sliver_name)
156         return
157
158     slice_bridge_neighbors = attributes.get("slice_bridge_neighbors", None)
159     if not slice_bridge_neighbors:
160         logger.log("privatebridge: no slice_bridge_neighbors for %s" % sliver_name)
161         return
162
163     slice_bridge_neighbors = [x.strip() for x in slice_bridge_neighbors.split(",")]
164
165     ensure_slicebridge_created(slice_bridge_name, slice_bridge_addr)
166     ensure_slicebridge_neighbors(slice_bridge_name, sliver_id, slice_bridge_neighbors)
167
168 def GetSlivers(data, conf = None, plc = None):
169
170     if not ovs_available():
171         logger.log ("privatebridge: warning, ovs-vsctl not found - exiting")
172         return
173
174     node_id = tools.node_id()
175
176     if 'slivers' not in data:
177         logger.log_missing_data("privatebridge.GetSlivers", 'slivers')
178         return
179
180     valid_bridges = []
181     for sliver in data['slivers']:
182         sliver_name = sliver['name']
183
184         # build a dict of attributes, because it's more convenient
185         attributes = {}
186         for attribute in sliver['attributes']:
187             attributes[attribute['tagname']] = attribute['value']
188
189         bridge_name = attributes.get('slice_bridge_name', None)
190         if bridge_name:
191             configure_slicebridge(sliver, attributes)
192             valid_bridges.append(bridge_name)
193
194     # now, delete the bridges that we don't want
195     bridges = ovs_listbridge()
196     for bridge_name in bridges:
197         if not bridge_name.startswith("br-slice-"):
198             # ignore ones we didn't create
199             continue
200
201         if bridge_name in valid_bridges:
202             # ignore ones we want to keep
203             continue
204
205         logger.log("privatebridge: deleting unused bridge %s" % bridge_name)
206
207         ovs_delbridge(bridge_name)