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