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