xenserver: Change license of xsconsole plugin to GPLv2.
[sliver-openvswitch.git] / xenserver / usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py
1 # Copyright (c) 2007-2010 Citrix Systems Inc.
2 # Copyright (c) 2009,2010 Nicira Networks.
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; version 2 only.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along
14 # with this program; if not, write to the Free Software Foundation, Inc.,
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16
17 from XSConsoleLog import *
18
19 import os
20 import socket
21 import subprocess
22
23 vsctl="/usr/bin/ovs-vsctl"
24
25 if __name__ == "__main__":
26     raise Exception("This script is a plugin for xsconsole and cannot run independently")
27
28 from XSConsoleStandard import *
29
30 class VSwitchService:
31     service = {}
32
33     def __init__(self, name, processname=None):
34         self.name = name
35         self.processname = processname
36         if self.processname == None:
37             self.processname = name
38
39     def version(self):
40         try:
41             output = ShellPipe(["service", self.name, "version"]).Stdout()
42         except StandardError, e:
43             XSLogError("vswitch version retrieval error: " + str(e))
44             return "<unknown>"
45         for line in output:
46             if self.processname in line:
47                 return line.split()[-1]
48         return "<unknown>"
49
50     def status(self):
51         try:
52             output = ShellPipe(["service", self.name, "status"]).Stdout()
53         except StandardError, e:
54             XSLogError("vswitch status retrieval error: " + str(e))
55             return "<unknown>"
56         if len(output) == 0:
57             return "<unknown>"
58         for line in output:
59             if self.processname not in line:
60                 continue
61             elif "running" in line:
62                 return "Running"
63             elif "stop" in line:
64                 return "Stopped"
65             else:
66                 return "<unknown>"
67         return "<unknown>"
68
69     def restart(self):
70         try:
71             ShellPipe(["service", self.name, "restart"]).Call()
72         except StandardError, e:
73             XSLogError("vswitch restart error: " + str(e))
74
75     @classmethod
76     def Inst(cls, name, processname=None):
77         key = name
78         if processname != None:
79             key = key + "-" + processname
80         if name not in cls.service:
81             cls.service[key] = VSwitchService(name, processname)
82         return cls.service[key]
83
84 class VSwitchConfig:
85
86     @staticmethod
87     def Get(action):
88         try:
89             arg = [vsctl, "--timeout=30", "-vANY:console:emer"] + action.split()
90             output = ShellPipe(arg).Stdout()
91         except StandardError, e:
92             XSLogError("config retrieval error: " + str(e))
93             return "<unknown>"
94
95         if len(output) == 0:
96             output = ""
97         else:
98             output = output[0].strip()
99         return output
100
101
102 class VSwitchControllerDialogue(Dialogue):
103     def __init__(self):
104         Dialogue.__init__(self)
105         data=Data.Inst()
106
107         self.hostsInPool = 0
108         self.hostsUpdated = 0
109         self.xs_version = data.host.software_version.product_version('')
110         pool = data.GetPoolForThisHost()
111         if pool is not None:
112             if self.xs_version == "5.5.0":
113                 self.controller = pool.get("other_config", {}).get("vSwitchController", "")
114             else:
115                 self.controller = pool.get("vswitch_controller", "")
116         else:
117             self.controller = ""
118
119         choiceDefs = [
120             ChoiceDef(Lang("Set pool-wide controller"),
121                       lambda: self.getController()),
122             ChoiceDef(Lang("Delete pool-wide controller"),
123                       lambda: self.deleteController()),
124             ChoiceDef(Lang("Resync server controller config"),
125                       lambda: self.syncController()),
126 #             ChoiceDef(Lang("Restart ovs-vswitchd"),
127 #                       lambda: self.restartService("vswitch")),
128 #             ChoiceDef(Lang("Restart ovs-brcompatd"),
129 #                       lambda: self.restartService("vswitch-brcompatd"))
130             ]
131         self.menu = Menu(self, None, Lang("Configure Open vSwitch"), choiceDefs)
132
133         self.ChangeState("INITIAL")
134
135     def BuildPane(self):
136         pane = self.NewPane(DialoguePane(self.parent))
137         pane.TitleSet(Lang("Configure Open vSwitch"))
138         pane.AddBox()
139
140     def ChangeState(self, inState):
141         self.state = inState
142         self.BuildPane()
143         self.UpdateFields()
144
145     def UpdateFields(self):
146         self.Pane().ResetPosition()
147         getattr(self, "UpdateFields" + self.state)() # Dispatch method named 'UpdateFields'+self.state
148
149     def UpdateFieldsINITIAL(self):
150         pane = self.Pane()
151         pane.AddTitleField(Lang("Select an action"))
152         pane.AddMenuField(self.menu)
153         pane.AddKeyHelpField( { Lang("<Enter>") : Lang("OK"), Lang("<Esc>") : Lang("Cancel") } )
154
155     def UpdateFieldsGETCONTROLLER(self):
156         pane = self.Pane()
157         pane.ResetFields()
158
159         pane.AddTitleField(Lang("Enter IP address of controller"))
160         pane.AddInputField(Lang("Address", 16), self.controller, "address")
161         pane.AddKeyHelpField( { Lang("<Enter>") : Lang("OK"), Lang("<Esc>") : Lang("Exit") } )
162         if pane.CurrentInput() is None:
163             pane.InputIndexSet(0)
164
165     def HandleKey(self, inKey):
166         handled = False
167         if hasattr(self, "HandleKey" + self.state):
168             handled = getattr(self, "HandleKey" + self.state)(inKey)
169         if not handled and inKey == 'KEY_ESCAPE':
170             Layout.Inst().PopDialogue()
171             handled = True
172         return handled
173
174     def HandleKeyINITIAL(self, inKey):
175         return self.menu.HandleKey(inKey)
176
177     def HandleKeyGETCONTROLLER(self, inKey):
178         pane = self.Pane()
179         if pane.CurrentInput() is None:
180             pane.InputIndexSet(0)
181         if inKey == 'KEY_ENTER':
182             inputValues = pane.GetFieldValues()
183             self.controller = inputValues['address']
184             Layout.Inst().PopDialogue()
185
186             # Make sure the controller is specified as a valid dotted quad
187             try:
188                 socket.inet_aton(self.controller)
189             except socket.error:
190                 Layout.Inst().PushDialogue(InfoDialogue(Lang("Please enter in dotted quad format")))
191                 return True
192
193             Layout.Inst().TransientBanner(Lang("Setting controller..."))
194             try:
195                 self.SetController(self.controller)
196                 Layout.Inst().PushDialogue(InfoDialogue(Lang("Setting controller successful")))
197             except Exception, e:
198                 Layout.Inst().PushDialogue(InfoDialogue(Lang("Setting controller failed")))
199
200             self.ChangeState("INITIAL")
201             return True
202         else:
203             return pane.CurrentInput().HandleKey(inKey)
204
205     def restartService(self, name):
206         s = VSwitchService.Inst(name)
207         s.restart()
208         Layout.Inst().PopDialogue()
209
210     def getController(self):
211         self.ChangeState("GETCONTROLLER")
212         self.Pane().InputIndexSet(0)
213
214     def deleteController(self):
215         self.controller = ""
216         Layout.Inst().PopDialogue()
217         Layout.Inst().TransientBanner(Lang("Deleting controller..."))
218         try:
219             self.SetController(None)
220             Layout.Inst().PushDialogue(InfoDialogue(Lang("Controller deletion successful")))
221         except Exception, e:
222             Layout.Inst().PushDialogue(InfoDialogue(Lang("Controller deletion failed")))
223
224     def syncController(self):
225         Layout.Inst().PopDialogue()
226         Layout.Inst().TransientBanner(Lang("Resyncing controller setting..."))
227         try:
228             Task.Sync(lambda s: self._updateThisServer(s))
229             Layout.Inst().PushDialogue(InfoDialogue(Lang("Resyncing controller config successful")))
230         except Exception, e:
231             Layout.Inst().PushDialogue(InfoDialogue(Lang("Resyncing controller config failed")))
232
233     def SetController(self, ip):
234         self.hostsInPool = 0
235         self.hostsUpdated = 0
236         Task.Sync(lambda s: self._modifyPoolConfig(s, ip or ""))
237         # Should be done asynchronously, maybe with an external script?
238         Task.Sync(lambda s: self._updateActiveServers(s))
239
240     def _modifyPoolConfig(self, session, value):
241         """Modify pool configuration.
242
243         If value == "" then delete configuration, otherwise set to value.
244         """
245         pools = session.xenapi.pool.get_all()
246         # We assume there is only ever one pool...
247         if len(pools) == 0:
248             XSLogFatal(Lang("No pool found for host."))
249             return
250         if len(pools) > 1:
251             XSLogFatal(Lang("More than one pool for host."))
252             return
253         if self.xs_version == "5.5.0":
254             key = "vSwitchController"
255             session.xenapi.pool.remove_from_other_config(pools[0], key)
256             if value != None:
257                 session.xenapi.pool.add_to_other_config(pools[0], key, value)
258         else:
259             session.xenapi.pool.set_vswitch_controller(value)
260         Data.Inst().Update()
261
262     def _updateActiveServers(self, session):
263         hosts = session.xenapi.host.get_all()
264         self.hostsUpdated = 0
265         self.hostsInPool = len(hosts)
266         self.UpdateFields()
267         for host in hosts:
268             Layout.Inst().TransientBanner("Updating host %d out of %d" 
269                     % (self.hostsUpdated + 1, self.hostsInPool))
270             session.xenapi.host.call_plugin(host, "openvswitch-cfg-update", "update", {})
271             self.hostsUpdated = self.hostsUpdated + 1
272
273     def _updateThisServer(self, session):
274         data = Data.Inst()
275         host = data.host.opaqueref()
276         session.xenapi.host.call_plugin(host, "openvswitch-cfg-update", "update", {})
277
278
279 class XSFeatureVSwitch:
280
281     @classmethod
282     def StatusUpdateHandler(cls, inPane):
283         data = Data.Inst()
284         xs_version = data.host.software_version.product_version('')
285
286         inPane.AddTitleField(Lang("Open vSwitch"))
287
288         inPane.NewLine()
289
290         inPane.AddStatusField(Lang("Version", 20),
291                               VSwitchService.Inst("openvswitch", "ovs-vswitchd").version())
292
293         inPane.NewLine()
294
295         pool = data.GetPoolForThisHost()
296         if pool is not None:
297             if (xs_version == "5.5.0"):
298                 dbController = pool.get("other_config", {}).get("vSwitchController", "")
299             else:
300                 dbController = pool.get("vswitch_controller", "")
301         else:
302             dbController = ""
303
304         if dbController == "":
305             dbController = Lang("<None>")
306         inPane.AddStatusField(Lang("Controller (config)", 20), dbController)
307         controller = VSwitchConfig.Get("get Open_vSwitch . managers")
308         controller = controller.strip('[]"')
309
310         if controller == "":
311             controller = Lang("<None>")
312         elif controller[0:4] == "ssl:":
313             controller = controller.split(':')[1]
314         inPane.AddStatusField(Lang("Controller (in-use)", 20), controller)
315
316         inPane.NewLine()
317         inPane.AddStatusField(Lang("ovs-vswitchd status", 20),
318                               VSwitchService.Inst("openvswitch", "ovs-vswitchd").status())
319         inPane.AddStatusField(Lang("ovsdb-server status", 20),
320                               VSwitchService.Inst("openvswitch", "ovsdb-server").status())
321
322         # Only XenServer 5.5.0 runs ovs-brcompatd
323         if (xs_version == "5.5.0"):
324             inPane.AddStatusField(Lang("ovs-brcompatd status", 20),
325                    VSwitchService.Inst("openvswitch", "ovs-brcompatd").status())
326
327         inPane.AddKeyHelpField( {
328             Lang("<Enter>") : Lang("Reconfigure"),
329             Lang("<F5>") : Lang("Refresh")
330         })
331
332     @classmethod
333     def ActivateHandler(cls):
334         DialogueUtils.AuthenticatedOnly(lambda: Layout.Inst().PushDialogue(VSwitchControllerDialogue()))
335
336     def Register(self):
337         Importer.RegisterNamedPlugIn(
338             self,
339             'VSwitch', # Key of this plugin for replacement, etc.
340             {
341                 'menuname' : 'MENU_NETWORK',
342                 'menupriority' : 800,
343                 'menutext' : Lang('Open vSwitch') ,
344                 'statusupdatehandler' : self.StatusUpdateHandler,
345                 'activatehandler' : self.ActivateHandler
346             }
347         )
348
349 # Register this plugin when module is imported, IFF vswitchd is running
350 if os.path.exists('/var/run/openvswitch/ovs-vswitchd.pid'):
351     XSFeatureVSwitch().Register()