FORGE: Added including script
[myslice.git] / forge / script / openvswitch.py
1 #!/usr/bin/env python
2
3 import sys
4 import argparse
5
6 import logging
7 import paramiko
8 import signal
9 import os
10 import time
11
12 # Import the class used (Slice, Host, Device, Link, HostLink)
13 from classDefinition import *
14
15 from exceptionDefinition import SSHConnectError
16
17 class TransformXml:
18         
19         def __init__(self, confFile = 'Conf.xml', linkFile = 'Link.xml', prefix = '', port = 22, keyFile = '~/.ssh/id_rsa'):
20                 self.__currentNodeConf__ = None
21                 self.__currentNodeLink__ = None
22                 self.__currentNodeRoute__= None
23                 self.__currentNodeService__ = None
24                 self.__sliceList__ = []
25                 self.__linkList__ = []
26                 
27                 self.readXml(prefix+confFile, prefix+linkFile)
28                 self.sshPort = port
29                 self.sshKey = keyFile
30                 self.xmlToSliceObject()
31                 self.xmlToLinkObject()
32         
33         def readXml(self, confPath, linkPath):
34                 from xml.dom.minidom import parse
35                 if not(os.path.isfile(confPath) and os.path.isfile(linkPath)):
36                         print "Error file "+confPath+" or "+linkPath+" does not exist"
37                         sys.exit(2)
38                 self.conf = parse(confPath)
39                 self.link = parse(linkPath)
40         
41         def getRootElementConf(self):
42                 if self.__currentNodeConf__ == None:
43                         self.__currentNodeConf__ = self.conf.documentElement
44                 return self.__currentNodeConf__
45         
46         def getRootElementLink(self):
47                 if self.__currentNodeLink__ == None:
48                         self.__currentNodeLink__ = self.link.documentElement
49                 return self.__currentNodeLink__
50         
51         def xmlToServiceObject(self, rootService, host, slice_name):
52                 i = 0
53                 newService = Service(slice_name, host.url, host.ssh)
54                 for service_i in rootService.getElementsByTagName("service"):
55                         try:
56                                  service = self.getText(service_i)
57                         except:
58                                 print "The tag service is missig"
59                         servicePart = service.partition(':')
60                         newService.services.append([servicePart[0], servicePart[2]])
61                 host.services = newService
62
63         def setServices(self):
64                 print "Now setting the service"
65                 for slice_i in self.__sliceList__:
66                         for host_i in slice_i.hosts:
67                                 host_i.services.setService()
68                         
69         def xmlToRouteObject(self, rootRoute, host, sliceName):
70                 for route_i in rootRoute.getElementsByTagName("route"):
71                         newRoute = Route(sliceName, host.url, host.ssh, host.id_host)
72                         try:
73                                 newRoute.subnet = self.getText(route_i.getElementsByTagName("subnet")[0])
74                                 newRoute.gateway = self.getText(route_i.getElementsByTagName("gateway")[0])
75                                 devId = self.getText(route_i.getElementsByTagName("device")[0])
76                         except:
77                                 print "The tag subnet / gateway or device is missing"
78                                 sys.exit(2)
79                         newRoute.device = self.getTapId(newRoute, devId, host)
80                         host.routes.append(newRoute)
81
82         def setRoutes(self):
83                 for slice_i in self.__sliceList__:
84                         for host_i in slice_i.hosts:
85                                 for route_i in host_i.routes:
86                                         route_i.setRoute()
87
88         def xmlToSliceObject(self):
89                 i = j = k = 0
90                 for slice_i in self.getRootElementConf().getElementsByTagName("slice"):
91                         i += 1
92                         newSlice = Slice()
93                         try:
94                                 newSlice.slice_name.value = self.getText(slice_i.getElementsByTagName("slice_name")[0])
95                         except :
96                                 print "The tag slice_name is missing"
97                                 sys.exit(2)
98                         for host_i in slice_i.getElementsByTagName("host"):
99                                 j+=1
100                                 newHost = Host()
101                                 try:
102                                         newHost.hostType.value = self.getText(host_i.getElementsByTagName("type")[0])
103                                         newHost.id_host = self.getText(host_i.getElementsByTagName("id")[0])
104                                         newHost.url.value = self.getText(host_i.getElementsByTagName("url")[0])
105                                 except Exception, why:
106                                         print "Tag id or url is missing"
107                                         print "Slice",newSlice.slice_name," Host ",newHost.url," Interface ",str(k)
108                                         print "Why -> ", why
109                                         sys.exit(2)
110                                 newHost.ssh = self.sshCheck(newHost.url.value, newSlice.slice_name.value)
111                                 for interface_i in host_i.getElementsByTagName("interface"):
112                                         k+=1
113                                         newDevice = Device()
114                                         try:
115                                                 newDevice.id_dev = self.getText(interface_i.getElementsByTagName("bridge_name")[0])
116                                                 newDevice.ip = self.getText(interface_i.getElementsByTagName("ip")[0])
117                                         except:
118                                                 print "Tag bridge_name or ip is missing"
119                                                 #sys.exit(2)
120                                         newHost.devices.append(newDevice)
121                                 try:
122                                         serviceRoot = host_i.getElementsByTagName("services")[0]
123                                         self.xmlToServiceObject(serviceRoot, newHost, newSlice.slice_name)
124                                 except:
125                                         newHost.services = Service("","","")
126                                 try:
127                                         routeRoot = host_i.getElementsByTagName("routes")[0]
128                                         self.xmlToRouteObject(routeRoot, newHost, newSlice.slice_name)
129                                 except:
130                                         print "No additionnal route for host "+str(newSlice.slice_name)+"@"+str(newHost.url)
131                                 newSlice.hosts.append(newHost)
132                         self.__sliceList__.append(newSlice)
133
134         def setSliceConf(self):
135                 for slice_i in self.__sliceList__:
136                         for host_i in slice_i.hosts:
137                                 if len(host_i.devices) > 0:
138                                         print "\tOn "+str(slice_i.slice_name)+"@"+str(host_i.url)
139                                         self.execute("sudo -S sliver-ovs start_db", host_i.ssh, display = True)
140                                         self.execute("sudo -S sliver-ovs start_switch", host_i.ssh, display = True)
141                                         for interface_i in host_i.devices:
142                                                 returnCode = -1
143                                                 i = 0
144                                                 while returnCode != 0:
145                                                         returnCode = self.execute("sudo -S sliver-ovs create_bridge "+interface_i.id_dev+" "+interface_i.ip,host_i.ssh)
146         
147                                                         if returnCode != 0:
148                                                                 i += 1
149                                                                 self.execute("sudo -S sliver-ovs del_bridge "+interface_i.id_dev, host_i.ssh)
150                                                                 if i > 10:
151                                                                         print "I have make ",i," iteration"
152                                                                 time.sleep(60)
153                                                         else:
154                                                                 print "I make",i,"iteration before successfully create the interface"
155         
156         def xmlToLinkObject(self):
157                 for link_i in self.getRootElementLink().getElementsByTagName("link"):
158                         newLink = Link()
159                         newHost1 = HostLink()
160                         newHost2 = HostLink()
161                         try:
162                                 host1 = link_i.getElementsByTagName("host1")[0]
163                                 host2 = link_i.getElementsByTagName("host2")[0]
164                         except:
165                                 print "Tag host1/host2 is missing"
166                                 sys.exit(2)
167                         try:
168                                 newHost1.slice_name = self.getText(host1.getElementsByTagName("slice")[0])
169                                 newHost1.id_host = self.getText(host1.getElementsByTagName("id")[0])
170                                 newHost1.bridge_name = self.getText(host1.getElementsByTagName("bridge_name")[0])
171                         except:
172                                 print "Tag slice, id or bridge_name is missing for host1"
173                                 sys.exit(2)
174                         try:
175                                 newHost2.slice_name = self.getText(host2.getElementsByTagName("slice")[0])
176                                 newHost2.id_host = self.getText(host2.getElementsByTagName("id")[0])
177                                 newHost2.bridge_name = self.getText(host2.getElementsByTagName("bridge_name")[0])
178                         except:
179                                 print "Tag slice, id or bridge_name is missing for host2"
180
181                         newHost1.ssh, newHost1.url = self.getSSHAccessUrl(newHost1.slice_name, newHost1.id_host )
182                         newHost2.ssh, newHost2.url = self.getSSHAccessUrl(newHost2.slice_name, newHost2.id_host )
183                         newLink.host1 = newHost1
184                         newLink.host2 = newHost2
185                         self.__linkList__.append(newLink)
186
187         def setLinks(self):
188                 import subprocess
189                 print "Creating Links"
190                 for link_i in self.__linkList__:
191                         host1 = link_i.host1
192                         host2 = link_i.host2
193                         link_name_host1 = host1.slice_name+"@"+str(host1.url)+"@"+host1.bridge_name
194                         link_name_host2 = host2.slice_name+"@"+str(host2.url)+"@"+host2.bridge_name
195                         link_name = link_name_host1+"---"+link_name_host2
196                         print "\tOn "+str(host1.slice_name)+"@"+str(host1.url)
197                         self.execute("sudo -S sliver-ovs create_port "+host1.bridge_name+" "+link_name, host1.ssh)
198                         print "\tOn "+str(host2.slice_name)+"@"+str(host2.url)
199                         self.execute("sudo -S sliver-ovs create_port "+host2.bridge_name+" "+link_name, host2.ssh)
200                         proc = subprocess.Popen(["host "+str(host1.url)+" | sed -n 's/^.*has address *//p'"], stdout=subprocess.PIPE, shell=True)
201                         (out, err) = proc.communicate()
202                         ip1 = out.replace('\n','')
203                         proc = subprocess.Popen(["host "+str(host2.url)+" | sed -n 's/^.*has address *//p'"], stdout=subprocess.PIPE, shell=True)
204                         (out, err) = proc.communicate()
205                         ip2 = out.replace('\n','')
206                         portUDP1 = self.execute("sudo -S sliver-ovs get_local_endpoint "+link_name, host1.ssh, retour=True).replace('\n','')
207                         portUDP2 = self.execute("sudo -S sliver-ovs get_local_endpoint "+link_name, host2.ssh, retour=True).replace('\n','')
208                         print "\tPort UDP1 = "+str(portUDP1)+" Port UDP2 = "+str(portUDP2)
209                         self.execute("sudo -S sliver-ovs set_remote_endpoint "+link_name+" "+ip2+" "+portUDP2, host1.ssh)
210                         self.execute("sudo -S sliver-ovs set_remote_endpoint "+link_name+" "+ip1+" "+portUDP1, host2.ssh)
211
212         def getSSHAccessUrl(self, slice_name, id_host):
213                 host_search = self.getHost(slice_name, id_host)
214                 return host_search.ssh, host_search.url
215
216         def getHost(self, slice_name, host_id):
217                 i = j = 0
218                 slice_search = self.__sliceList__[i]
219                 try:
220                         while slice_search.slice_name.value != slice_name:
221                                 i+=1
222                                 slice_search = self.__sliceList__[i]
223                         host_search = slice_search.hosts[j]
224                         while host_search.id_host != host_id:
225                                 j+=1
226                                 host_search = slice_search.hosts[j]
227                 except IndexError:
228                         print "The host in slice ->",slice_name,"and host id ->",host_id,"doesn't exist"
229                         print "\tAn IndexError occured"
230                         sys.exit(2)
231                 return host_search
232
233         def getUrl(self, slice_name, id_host):
234                 host_search = self.getHost(slice_name, id_host)
235                 return host_search.url
236
237         def getTapId(self, route, idDev, host):
238                 idUser = self.execute("id -u", route.host_ssh, retour = True).replace('\n','')
239                 i = 0
240                 dev_searched = host.devices[i]
241                 try:
242                         while dev_searched.id_dev != idDev:
243                                 i += 1
244                                 dev_searched = host_searched.devices[i]
245                 except IndexError:
246                         print "Error while setting the route"
247                         print "For slice ->",host_searched.slice_name," id host ->",host_searched.id_host," the following device does not exist ->",idDev
248                         sys.exit(2)
249                 return "tap"+idUser+"-"+str(i)
250
251         #TODO change the function for online mode
252         def sshCheck(self, host, slice_name):
253                 ssh = paramiko.SSHClient()
254                 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
255                 try:
256                         ssh.connect(host, username=slice_name, port=self.sshPort, key_filename=self.sshKey)
257                         pass
258                 # This error is raised if the ssh key has not already been installed on the node
259                 except paramiko.PasswordRequiredException, why:
260                         print "Password required exception"
261                         print "Error: ", why
262                         raise SSHConnectError(host, slice_name)
263                 except paramiko.SSHException:
264                         print "An SSH exception occured"
265                         raise SSHConnectError(host, slice_name)
266                 except Exception, why:
267                         print "An error occured for host "+str(host)+" on slice "+str(slice_name)
268                         print why
269                         raise SSHConnectError(host, slice_name)
270                 return ssh
271
272         def timeout(signum, frame):
273                 raise TimeOutException, "Command ran for too long"
274
275         def getText(self, node):
276                 return node.childNodes[0].nodeValue
277         
278         def execute(self, command, ssh, display=False, retour=False):
279                 print "# "+command
280                 stdin, stdout, stderr = ssh.exec_command(command)
281                 stdin.close()
282                 while not stdout.channel.exit_status_ready():
283                         time.sleep(2)
284                 err = stderr.read()
285                 if err != None:
286                         splitted = err.splitlines()
287                         if len(splitted) > 0:
288                                 print "\tError in execution"
289                                 for line in splitted:
290                                         print "\t>",line
291                 if display:
292                         for line in stdout.read().splitlines() :
293                                 print " > " + line
294                 elif retour:
295                         return stdout.read()
296                 return stdout.channel.recv_exit_status()
297         
298         def clearConf(self):
299                 print "Removing the topology configuration"
300                 for slice_i in self.__sliceList__:
301                         for host_i in slice_i.hosts:
302                                 self.sshCheck(host_i.url.value, slice_i.slice_name.value)
303                                 print "\tOn "+str(slice_i.slice_name)+"@"+str(host_i.url)+" clearing the conf :"
304                                 sshHost = host_i.ssh
305                                 for device_i in host_i.devices:
306                                         self.execute("sudo -S sliver-ovs del_bridge "+device_i.id_dev, sshHost, display=True)
307                                 self.execute("sudo -S sliver-ovs stop_switch", sshHost, display=True)
308                                 self.execute("sudo -S sliver-ovs stop_db", sshHost, display=True)
309                                 host_i.services.removeService()
310                                 
311         def printSlice(self):
312                 print "List of slice/host/interface"
313                 for slice_i in self.__sliceList__:
314                         print slice_i
315                         for host_i in slice_i.hosts:
316                                 print "\t"+str(host_i)
317                                 for dev_i in host_i.devices:
318                                         print "\t\t"+str(dev_i)
319         
320         def printLink(self):
321                 print "\nList of link"
322                 for link_i in self.__linkList__:
323                         print link_i
324         
325         def printRoute(self):
326                 print "\nList of additionnal route"
327                 for slice_i in self.__sliceList__:
328                         for host_i in slice_i.hosts:
329                                 for route_i in host_i.routes:
330                                         print route_i
331         
332         def printService(self):
333                 print "\nList of deployed service"
334                 for slice_i in self.__sliceList__:
335                         for host_i in slice_i.hosts:
336                                 serviceStr = str(host_i.services)
337                                 if serviceStr != '':
338                                         print host_i.services
339         
340         def getSliceList(self):
341                 return self.__sliceList__
342
343         @classmethod
344         def helpOption(self):
345                 print "You can use the following options:"
346                 print "\t setTopology : to create the topology"
347                 print "\t clearTopology : to clear the topology"
348                 print "\t printTopology : to print the topology"
349                 print "Optionnal:"
350                 print "\t $2 : configuration xml file"
351                 print "\t $3 : link xml file"
352                 print "\t $2 : prefix to find the xml file"
353                 print "\t If not given default are Conf.xml, Link.xml"
354
355 def keyFile(x):
356         print x
357         if not os.path.exists(x) and x!=os.path.expanduser('~/.ssh/id_rsa'):
358                 raise argparse.ArgumentError("{0} does not exist".format(x))
359         return x
360
361 if __name__ == "__main__":
362         parser = argparse.ArgumentParser()
363         #parser = argparse.ArgumentParser("This script allow you to deploy a configured topology on planetlab node, the topology has to be descript in xml file\nYou have to use one of the action option (-c -s -p)\nYou have to specify the input file that need to be used")
364         parser.add_argument("action", help="The action you want to perform choices are print, set or clear the topology", choices = ['print','set','clear'], default='print', nargs='?')
365         parser.add_argument("-k", "--key", help="The location of the ssh private key file (default value is ~/.ssh/id_rsa)", default=os.path.expanduser('~/.ssh/id_rsa'), type=keyFile)
366         parser.add_argument('-p', '--port', help='The port used for the ssh connection (default is 22)', default=22, type = int)
367
368         groupInput = parser.add_mutually_exclusive_group()
369         groupInput.add_argument("-f", "--file", help="The xml file that will be used, Link xml file then Configuration xml file", type=str, default='', nargs=2)
370         groupInput.add_argument("-d", "--directory", help="The directory used to find the xml file the script will look at prefix+Conf.xml and prefix+Link.xml", type=str, default='./', dest='prefix')
371
372         try:
373                 args = parser.parse_args()
374         except Exception, why:
375                 print "An error occured while parsing the argument"
376                 parser.print_help()
377                 print why
378                 sys.exit(2)
379
380         if args.file != '':
381                 x = TransformXml(confFile = args.file[0], linkFile = args.file[1], port = args.port, keyFile = args.key)
382         else:
383                 x = TransformXml(prefix = args.prefix, port = args.port, keyFile = args.key )
384
385         if args.action == "set" :
386                 x.setSliceConf()
387                 x.setLinks()
388                 x.setRoutes()
389                 x.setServices()
390         elif args.action == "clear" :
391                 x.clearConf()
392         elif args.action == "print" :
393                 x.printSlice()
394                 x.printLink()
395                 x.printRoute()
396                 x.printService()
397