2 #############################################################################
3 #Copyright 2017 Parker Berberian and others #
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 #
9 # http://www.apache.org/licenses/LICENSE-2.0 #
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 #############################################################################
25 import xml.dom.minidom
29 from database import HostDataBase, BookingDataBase
30 from api.vpn import VPN
36 This class defines some useful functions that may be needed
37 throughout the provisioning and deployment stage.
38 The utility object is carried through most of the deployment process.
40 def __init__(self, host_ip, hostname, conf):
43 host_ip is the ip of the target host
44 hostname is the FOG hostname of the host
45 conf is the parsed config file
48 self.hostname = hostname
49 root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
50 self.scripts = os.path.join(root_dir, "hostScripts/")
51 self.remoteDir = "/root/hostScripts/"
53 self.logger = logging.getLogger(hostname)
55 def execRemoteScript(self, script, args=[]):
57 executes the given script on the
58 remote host with the given args.
59 script must be found in laas/hostScripts
61 cmd = [self.remoteDir+script]
66 def waitForBoot(self):
68 Continually pings the host, waiting for it to boot
71 while (not self.pingHost()) and i < 30:
74 self.logger.error("Host %s has not booted", self.host)
79 returns true if the host responds to two pings.
80 Sometimes, while a host is pxe booting, a host will
81 respond to one ping but quickly go back offline.
83 if self.pingHost() and self.pingHost():
89 returns true if the host responds to a ping
93 cmd = "ping -c 1 "+self.host
95 nul = open(os.devnull, 'w')
96 while i < 10 and response != 0:
97 response = subprocess.call(cmd, stdout=nul, stderr=nul)
103 def copyDir(self, localDir, remoteDir):
105 uses scp to copy localDir to remoteDir on the
108 cmd = "mkdir -p "+remoteDir
109 self.sshExec(cmd.split(" "))
110 cmd = "scp -o StrictHostKeyChecking=no -r "
111 cmd += localDir+" root@"+self.host+":/root"
113 nul = open(os.devnull, 'w')
114 subprocess.call(cmd, stdout=nul, stderr=nul)
116 def copyScripts(self):
118 Copies the hostScrpts dir to the remote host.
120 self.copyDir(self.scripts, self.remoteDir)
122 def sshExec(self, args):
124 executes args as an ssh
125 command on the remote host.
127 cmd = ['ssh', 'root@'+self.host]
130 nul = open(os.devnull, 'w')
131 return subprocess.call(cmd, stdout=nul, stderr=nul)
133 def resetKnownHosts(self):
135 edits your known hosts file to remove the previous entry of host
136 Sometimes, the flashing process gives the remote host a new
137 signature, and ssh complains about it.
140 sshFile = open('/root/.ssh/known_hosts', 'r')
141 lines = sshFile.read()
143 lines = lines.split('\n')
144 sshFile = open('/root/.ssh/known_hosts', 'w')
146 if self.host not in line:
147 sshFile.write(line+'\n')
150 def restartHost(self):
152 restarts the remote host
154 cmd = ['shutdown', '-r', 'now']
158 def randoString(length):
160 this is an adapted version of the code found here:
161 https://stackoverflow.com/questions/2257441/
162 random-string-generation-with-upper-case-letters-and-digits-in-python
163 generates a random alphanumeric string of length length.
166 chars = string.ascii_uppercase + string.digits
167 for x in range(length):
168 randStr += random.SystemRandom().choice(chars)
171 def changePassword(self):
173 Sets the root password to a random string and returns it
175 paswd = self.randoString(15)
176 command = "printf "+paswd+" | passwd --stdin root"
177 self.sshExec(command.split(' '))
180 def markHostDeployed(self):
182 Tells the database that this host has finished its deployment
184 db = HostDataBase(self.conf['database'])
185 db.makeHostDeployed(self.hostname)
188 def make_vpn_user(self):
190 Creates a vpn user and associates it with this booking
192 config = yaml.safe_load(open(self.conf['vpn_config']))
194 # name = dashboard.getUserName()
195 u, p, uid = myVpn.makeNewUser() # may pass name arg if wanted
196 self.logger.info("%s", "created new vpn user")
197 self.logger.info("username: %s", u)
198 self.logger.info("password: %s", p)
199 self.logger.info("vpn user uid: %s", uid)
200 self.add_vpn_user(uid)
202 def add_vpn_user(self, uid):
204 Adds the dn of the vpn user to the database
205 so that we can clean it once the booking ends
207 db = BookingDataBase(self.conf['database'])
208 # converts from hostname to pharos resource id
209 inventory = yaml.safe_load(open(self.conf['inventory']))
211 for resource_id in inventory.keys():
212 if inventory[resource_id] == self.hostname:
213 host_id = resource_id
215 db.setVPN(host_id, uid)
217 def finishDeployment(self):
219 Last method call once a host is finished being deployed.
220 It notifies the database and changes the password to
223 self.markHostDeployed()
225 passwd = self.changePassword()
226 self.logger.info("host %s provisioning done", self.hostname)
227 self.logger.info("You may access the host at %s", self.host)
228 self.logger.info("The password is %s", passwd)
229 notice = "You should change all passwords for security"
230 self.logger.warning('%s', notice)
233 def restartRemoteHost(host_ip):
235 This method assumes that you already have ssh access to the target
237 nul = open(os.devnull, 'w')
238 ret_code = subprocess.call([
239 'ssh', '-o', 'StrictHostKeyChecking=no',
241 'shutdown', '-r', 'now'],
242 stdout=nul, stderr=nul)
247 def getName(xmlString):
249 Gets the name value from xml. for example:
250 <name>Parker</name> returns Parker
252 xmlDoc = xml.dom.minidom.parseString(xmlString)
253 nameNode = xmlDoc.documentElement.getElementsByTagName('name')
254 name = str(nameNode[0].firstChild.nodeValue)
258 def getXMLFiles(directory):
260 searches directory non-recursively and
261 returns a list of all xml files
263 contents = os.listdir(directory)
265 for item in contents:
266 if os.path.isfile(os.path.join(directory, item)):
267 fileContents.append(os.path.join(directory, item))
269 for item in fileContents:
270 if 'xml' in os.path.basename(item):
271 xmlFiles.append(item)
275 def createLogger(name, log_dir=LOGGING_DIR):
277 Initializes the logger if it does not yet exist, and returns it.
278 Because of how python logging works, calling logging.getLogger()
279 with the same name always returns a reference to the same log file.
280 So we can call this method from anywhere with the hostname as
281 the name arguement and it will return the log file for that host.
282 The formatting includes the level of importance and the time stamp
285 if log_dir != LOGGING_DIR:
286 LOGGING_DIR = log_dir
287 log = logging.getLogger(name)
288 if len(log.handlers) > 0: # if this logger is already initialized
291 han = logging.FileHandler(os.path.join(log_dir, name+".log"))
293 log_format = '[%(levelname)s] %(asctime)s [#] %(message)s'
294 formatter = logging.Formatter(fmt=log_format)
295 han.setFormatter(formatter)
300 def getIPfromMAC(macAddr, logFile, remote=None):
302 searches through the dhcp logs for the given mac
303 and returns the associated ip. Will retrieve the
304 logFile from a remote host if remote is given.
305 if given, remote should be an ip address or hostname that
308 if remote is not None:
309 logFile = Utilities.retrieveFile(remote, logFile)
310 ip = Utilities.getIPfromLog(macAddr, logFile)
311 if remote is not None:
316 def retrieveFile(host, remote_loc, local_loc=os.getcwd()):
318 Retrieves file from host and puts it in the current directory
319 unless local_loc is given.
321 subprocess.call(['scp', 'root@'+host+':'+remote_loc, local_loc])
322 return os.path.join(local_loc, os.path.basename(remote_loc))
325 def getIPfromLog(macAddr, logFile):
327 Helper method for getIPfromMAC.
328 uses regex to find the ip address in the
332 messagesFile = open(logFile, "r")
333 allLines = messagesFile.readlines()
337 for line in allLines:
338 if macAddr in line and "DHCPACK" in line:
339 importantLines.append(line)
340 ipRegex = r'(\d+\.\d+\.\d+\.\d+)'
342 for line in importantLines:
343 IPs.append(re.findall(ipRegex, line))
344 if len(IPs) > 0 and len(IPs[-1]) > 0: