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