Merge "Adds Libvirt Handler"
[pharos.git] / tools / laas-fog / source / api / libvirt_api.py
1 """
2 #############################################################################
3 #Copyright 2017 Parker Berberian and others                                 #
4 #                                                                           #
5 #Licensed under the Apache License, Version 2.0 (the "License");            #
6 #you may not use this file except in compliance with the License.           #
7 #You may obtain a copy of the License at                                    #
8 #                                                                           #
9 #    http://www.apache.org/licenses/LICENSE-2.0                             #
10 #                                                                           #
11 #Unless required by applicable law or agreed to in writing, software        #
12 #distributed under the License is distributed on an "AS IS" BASIS,          #
13 #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
14 #See the License for the specific language governing permissions and        #
15 #limitations under the License.                                             #
16 #############################################################################
17 """
18
19 import libvirt
20 import time
21 import xml.dom
22 import xml.dom.minidom
23 from domain import Domain
24 from network import Network
25 from utilities import Utilities
26
27
28 class Libvirt:
29     """
30     This class talks to the Libvirt api.
31     Given a config file, this class should create all networks and
32     domains.
33
34     TODO: convert prints to logging and remove uneeded pass statements
35     """
36
37     def __init__(self, hostAddr, net_conf=None, dom_conf=None):
38         """
39         init function
40         hostAddr is the ip address of the host
41         net_conf and dom_conf are the paths
42         to the config files
43         """
44         self.host = hostAddr
45         self.URI = "qemu+ssh://root@"+str(hostAddr)+"/system"
46         self.hypervisor = None
47         self.domains = []
48         self.networks = []
49         self.net_conf = net_conf
50         self.dom_conf = dom_conf
51
52     def setLogger(self, log):
53         """
54         Saves the logger in self.log
55         """
56         self.log = log
57
58     def bootMaster(self):
59         """
60         starts the previously defined master node
61         """
62         for dom in self.domains:
63             if 'master' in dom.name():
64                 try:
65                     dom.create()
66                 except Exception:
67                     pass
68
69     def bootSlaves(self):
70         """
71         boots every defined vm with 'slave' in its name
72         """
73         for dom in self.domains:
74             if 'slave' in dom.name():
75                 try:
76                     dom.create()
77                     self.log.info("Booting %s", dom.name())
78                 except Exception:
79                     self.log.exception("%s", "failed to boot domain")
80                 time.sleep(5)
81
82     def getMacs(self, domName):
83         """
84         returns a dictionary with a network name
85         mapped to the mac address of the domain on that net
86         """
87         try:
88             dom = self.hypervisor.lookupByName(domName)
89             xmlDesc = dom.XMLDesc(0)
90             parsedXML = xml.dom.minidom.parseString(xmlDesc)
91             interfacesXML = parsedXML.getElementsByTagName('interface')
92             netDict = {}
93             for iface in interfacesXML:
94                 src = iface.getElementsByTagName('source')[0]
95                 mac = iface.getElementsByTagName('mac')[0]
96                 netDict[src] = mac
97             return netDict
98         except Exception:
99             self.log.exception("%s", "Domain not found")
100
101     def defineVM(self, xmlConfig):
102         """
103         Generic method to define a persistent vm with the
104         given config.
105         Assumes that self.hypervisor is already connected.
106         """
107         if self.checkForVM(xmlConfig):
108             vm = self.hypervisor.defineXML(xmlConfig)
109             if vm is None:
110                 name = self.getName(xmlConfig)
111                 self.log.error("Failed to define vm %s. exiting", name)
112                 exit(1)
113             else:
114                 self.log.info("Successfully created vm %s", vm.name())
115                 pass
116             self.domains.append(vm)
117
118     def checkForVM(self, xmlConfig):
119         """
120         Checks if another vm with the same name exists
121         on the remote host already. If it does, it will
122         delete that vm
123         """
124         allGood = False
125         vms = self.hypervisor.listAllDomains(0)
126         names = []
127         for dom in vms:
128             names.append(dom.name())
129         vmName = Utilities.getName(xmlConfig)
130         if vmName in names:
131             self.log.warning("domain %s already exists", vmName)
132             self.log.warning("%s", "Atempting to delete it")
133             self.deleteVM(vmName)
134             allGood = True
135         else:
136             allGood = True
137         return allGood
138
139     def deleteVM(self, name):
140         """
141         removes the given vm from the remote host
142         """
143         try:
144             vm = self.hypervisor.lookupByName(name)
145         except:
146             return
147         active = vm.isActive()
148         persistent = vm.isPersistent()
149         if active:
150             try:
151                 vm.destroy()
152             except:
153                 self.log.exception("%s", "Failed to destroy vm")
154
155         if persistent:
156             try:
157                 vm.undefine()
158             except:
159                 self.log.exception("%s", "Failed to undefine domain")
160                 pass
161
162     def openConnection(self):
163         """
164         opens a connection to the remote host
165         and stores it in self.hypervisor
166         """
167         self.log.info("Attempting to connect to libvirt at %s", self.host)
168         try:
169             hostHypervisor = libvirt.open(self.URI)
170         except:
171             self.log.warning(
172                     "Failed to connect to %s. Trying again", self.host
173                     )
174             time.sleep(5)
175             try:
176                 hostHypervisor = libvirt.open(self.URI)
177             except:
178                 self.log.exception("Cannot connect to %s. Exiting", self.host)
179                 exit(1)
180
181         if hostHypervisor is None:
182             self.log.error("Failed to connect to %s. Exiting", self.host)
183             exit(1)
184         self.hypervisor = hostHypervisor
185
186     def restartVM(self, vm):
187         """
188         causes the given vm to reboot
189         """
190         dom = self.hypervisor.lookupByName(vm)
191         dom.destroy()
192         time.sleep(15)
193         dom.create()
194
195     def close(self):
196         """
197         Closes connection to remote hypervisor
198         """
199         self.log.info("Closing connection to the hypervisor %s", self.host)
200         self.hypervisor.close()
201
202     def defineAllDomains(self, path):
203         """
204         Defines a domain from all the xml files in a directory
205         """
206         files = Utilities.getXMLFiles(path)
207         definitions = []
208         for xml_desc in files:
209             definitions.append(xml_desc.read())
210
211         for definition in definitions:
212             self.defineVM(definition)
213
214     def createAllNetworks(self, path):
215         """
216         Creates a network from all xml files in a directory
217         """
218         files = Utilities.getXMLFiles(path)
219         definitions = []
220         for xml_desc in files:
221             definitions.append(Utilities.fileToString(xml_desc))
222
223         for definition in definitions:
224             self.createNet(definition)
225
226     def createNet(self, config):
227         """
228         creates the network on the remote host
229         config is the xml in string representation
230         that defines the network
231         """
232         if self.checkNet(config):
233             network = self.hypervisor.networkDefineXML(config)
234
235             if network is None:
236                 name = self.getName(config)
237                 self.log.warning("Failed to define network %s", name)
238             network.create()
239             if network.isActive() == 1:
240                 net = network.name()
241                 self.log.info("Successfully defined network %s", net)
242             self.networks.append(network)
243
244     def checkNet(self, config):
245         """
246         Checks if another net with the same name exists, and
247         deletes that network if one is found
248         """
249         allGood = False
250         netName = Utilities.getName(config)
251         if netName not in self.hypervisor.listNetworks():
252             return True
253         else:  # net name is already used
254             self.log.warning(
255                     "Network %s already exists. Trying to delete it", netName
256                     )
257             network = self.hypervisor.networkLookupByName(netName)
258             self.deleteNet(network)
259             allGood = True
260         return allGood
261
262     def deleteNet(self, net):
263         """
264         removes the given network from the host
265         """
266         active = net.isActive()
267         persistent = net.isPersistent()
268         if active:
269             try:
270                 net.destroy()
271             except:
272                 self.log.warning("%s", "Failed to destroy network")
273
274         if persistent:
275             try:
276                 net.undefine()
277             except:
278                 self.log.warning("%s", "Failed to undefine network")
279
280     def go(self):
281         """
282         This method does all the work of this class,
283         Parsing the net and vm config files and creating
284         all the requested nets/domains
285         returns a list of all networks and a list of all domains
286         as Network and Domain objects
287         """
288         nets = self.makeNetworks(self.net_conf)
289         doms = self.makeDomains(self.dom_conf)
290         return doms, nets
291
292     def makeNetworks(self, conf):
293         """
294         Given a path to a  config file, this method
295         parses the config and creates all requested networks,
296         and returns them in a list of Network objects
297         """
298         networks = []
299         definitions = Network.parseConfigFile(conf)
300         for definition in definitions:
301             network = Network(definition)
302             networks.append(network)
303             self.createNet(network.toXML())
304         return networks
305
306     def makeDomains(self, conf):
307         """
308         Given a path to a config file, this method
309         parses the config and creates all requested vm's,
310         and returns them in a list of Domain objects
311         """
312         domains = []
313         definitions = Domain.parseConfigFile(conf)
314         for definition in definitions:
315             domain = Domain(definition)
316             domains.append(domain)
317             self.defineVM(domain.toXML())
318         return domains
319
320     @staticmethod
321     def getName(xmlString):
322         """
323         given xml with a name tag, this returns the value of name
324         eg:
325             <name>Parker</name>
326         returns 'Parker'
327         """
328         xmlDoc = xml.dom.minidom.parseString(xmlString)
329         nameNode = xmlDoc.documentElement.getElementsByTagName('name')
330         name = str(nameNode[0].firstChild.nodeValue)
331         return name