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