various further fixes for python3
[nodemanager.git] / plugins / privatebridge.py
1 #!/usr/bin/env python3
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(
35             command, bufsize=1,
36             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
37             close_fds=True,
38             universal_newlines=True)
39
40         stdout = ""
41         while True:
42             # see if anything can be read within the poll interval
43             (r, w, x)=select.select([child.stdout], [], [], poll)
44             if r: stdout = stdout + child.stdout.read(1)
45             # is process over ?
46             returncode=child.poll()
47             # yes
48             if returncode != None:
49                 stdout = stdout + child.stdout.read()
50                 # child is done and return 0
51                 if returncode == 0:
52                     logger.log("log_call:end command (%s) completed" % message)
53                     if stdout != "":
54                         logger.log("log_call:stdout: %s" % stdout)
55                     return (returncode, stdout)
56                 # child has failed
57                 else:
58                     log("log_call:end command (%s) returned with code %d" %(message, returncode))
59                     return (returncode, stdout)
60             # no : still within timeout ?
61             if time.time() >= trigger:
62                 child.terminate()
63                 logger.log("log_call:end terminating command (%s) - exceeded timeout %d s"%(message, timeout))
64                 return (-2, None)
65                 break
66     except Exception as e:
67         logger.log_exc("failed to run command %s -> %s" % (message, e))
68
69     return (-1, None)
70
71 ### Thierry - 23 Sept 2014
72 # regardless of this being shipped on lxc-only or on all nodes,
73 # it is safer to check for the availability of the ovs-vsctl command and just print
74 # out a warning when it's not there, instead of a nasty traceback
75 def ovs_available ():
76     "return True if ovs-vsctl can be run"
77     try:
78         child = subprocess.Popen (['ovs-vsctl', '--help'])
79         child.communicate()
80         return True
81     except:
82         return False
83
84 def ovs_vsctl(args):
85     return log_call_read(["ovs-vsctl"] + args)
86
87 def ovs_listbridge():
88     (returncode, stdout) = ovs_vsctl(["list-br"])
89     if (returncode != 0): raise OvsException("list-br")
90     return stdout.split()
91
92 def ovs_addbridge(name):
93     (returncode, stdout) = ovs_vsctl(["add-br", name])
94     if (returncode != 0): raise OvsException("add-br")
95
96 def ovs_listports(name):
97     (returncode, stdout) = ovs_vsctl(["list-ports", name])
98     if (returncode != 0): raise OvsException("list-ports")
99     return stdout.split()
100
101 def ovs_delbridge(name):
102     (returncode, stdout) = ovs_vsctl(["del-br", name])
103     if (returncode != 0): raise OvsException("del-br")
104
105 def ovs_addport(name, portname, type, remoteip, key):
106     args = ["add-port", name, portname, "--", "set", "interface", portname, "type="+type]
107     if remoteip:
108         args = args + ["options:remote_ip=" + remoteip]
109     if key:
110         args = args + ["options:key=" + str(key)]
111
112     (returncode, stdout) = ovs_vsctl(args)
113     if (returncode != 0): 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)