From b5227199c1155f5c16f7635da96a6617337a3e87 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Mon, 21 Aug 2017 09:03:53 -0400 Subject: Adds the Free and Open-source Ghost (FOG) JIRA: N/A Adds a handler which will talk with the REST api running on the FOG server. This allows the pod_manager to ghost images onto hosts in order to prep them for deployment and clean them afterwards. Change-Id: Ic00e992874ca3371b6d6e8ac2450a1ef0c765e67 Signed-off-by: Parker Berberian --- tools/laas-fog/source/api/__init__.py | 17 ++ tools/laas-fog/source/api/fog.py | 288 ++++++++++++++++++++++++++++++++++ 2 files changed, 305 insertions(+) create mode 100644 tools/laas-fog/source/api/__init__.py create mode 100644 tools/laas-fog/source/api/fog.py diff --git a/tools/laas-fog/source/api/__init__.py b/tools/laas-fog/source/api/__init__.py new file mode 100644 index 00000000..7bb515b7 --- /dev/null +++ b/tools/laas-fog/source/api/__init__.py @@ -0,0 +1,17 @@ +""" +############################################################################# +#Copyright 2017 Parker Berberian and others # +# # +#Licensed under the Apache License, Version 2.0 (the "License"); # +#you may not use this file except in compliance with the License. # +#You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +#Unless required by applicable law or agreed to in writing, software # +#distributed under the License is distributed on an "AS IS" BASIS, # +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +#See the License for the specific language governing permissions and # +#limitations under the License. # +############################################################################# +""" diff --git a/tools/laas-fog/source/api/fog.py b/tools/laas-fog/source/api/fog.py new file mode 100644 index 00000000..62874039 --- /dev/null +++ b/tools/laas-fog/source/api/fog.py @@ -0,0 +1,288 @@ +""" +############################################################################# +#Copyright 2017 Parker Berberian and others # +# # +#Licensed under the Apache License, Version 2.0 (the "License"); # +#you may not use this file except in compliance with the License. # +#You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +#Unless required by applicable law or agreed to in writing, software # +#distributed under the License is distributed on an "AS IS" BASIS, # +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +#See the License for the specific language governing permissions and # +#limitations under the License. # +############################################################################# +""" + +import requests +import sys +import time + + +class FOG_Handler: + """ + This class talks with the REST web api for the FOG server. + + TODO: convert prints to logs and remove uneeded pass's + """ + + def __init__(self, baseURL, fogKey=None, userKey=None): + """ + init function + baseURL should be http://fog.ip.or.hostname/fog/ + fogKey and userKey can optionally be supplied here or later + They can be found in fog and provide authentication. + """ + self.baseURL = baseURL + self.fogKey = fogKey + self.userKey = userKey + self.header = {} + self.updateHeader() + + def setLogger(self, logger): + """ + saves the refference to the log object as + self.log + """ + self.log = logger + + def getUserKeyFromFile(self, path): + """ + reads the user api key from a file + """ + self.userKey = open(path).read() + self.updateHeader() + + def getFogKeyFromFile(self, path): + """ + reads the api key from a file + """ + self.fogKey = open(path).read() + self.updateHeader() + + def setUserKey(self, key): + """ + sets the user key + """ + self.userKey = key + self.updateHeader() + + def setFogKey(self, key): + """ + sets the fog key + """ + self.fogKey = key + self.updateHeader() + + def updateHeader(self): + """ + recreates the http header used to talk to the fog api + """ + self.header = {} + self.header['fog-api-token'] = self.fogKey + self.header['fog-user-token'] = self.userKey + + def setImage(self, host, imgNum): + """ + Sets the image to be used during ghosting to the image + with id imgNum. host can either be a hostname or number. + """ + try: + host = int(host) + except: + host = self.getHostNumber(host) + url = self.baseURL+"host/"+str(host) + host_conf = requests.get(url, headers=self.header).json() + host_conf['imageID'] = str(imgNum) + requests.put(url+"/edit", headers=self.header, json=host_conf) + + def delTask(self, hostNum): + """ + Tries to delete an existing task for the host + with hostNum as a host number + """ + try: + url = self.baseURL+'fog/host/'+str(hostNum)+'/cancel' + req = requests.delete(url, headers=self.header) + if req.status_code == 200: + self.log.info("%s", "successfully deleted image task") + except Exception: + self.log.exception("Failed to delete the imaging task!") + + def getHostMac(self, hostname): + """ + returns the primary mac address if the given host. + """ + try: + hostNum = int(self.getHostNumber(hostname)) + url = self.baseURL + "host/"+str(hostNum) + req = requests.get(url, headers=self.header) + macAddr = req.json()['primac'] + return macAddr + except Exception: + self.log.exception('%s', "Failed to connect to the FOG server") + + def getHostNumber(self, hostname): + """ + returns the host number of given host + """ + try: + req = requests.get(self.baseURL+"host", headers=self.header) + hostData = req.json() + if hostData is not None: + for hostDict in hostData['hosts']: + if hostname == hostDict['name']: + return hostDict['id'] + return -1 + except Exception: + self.log.exception('%s', "Failed to connect to the FOG server") + + def imageHost(self, hostName, recurse=False): + """ + Schedules an imaging task for the given host. + This automatically uses the "associated" disk image. + To support extra installers, I will need to create + a way to change what that image is before calling + this method. + """ + num = str(self.getHostNumber(hostName)) + url = self.baseURL+'host/'+num+'/task' + + try: + req = requests.post( + url, + headers=self.header, + json={"taskTypeID": 1} + ) + if req.status_code == 200: + self.log.info("%s", "Scheduled image task for host") + except Exception: + if recurse: # prevents infinite loop + self.log.exception("%s", "Failed to schedule task. Exiting") + sys.exit(1) + self.log.warning("%s", "Failed to schedule host imaging") + self.log.warning("%s", "Trying to delete existing image task") + self.delTask(num) + self.imageHost(num, recurse=True) + + def waitForHost(self, host): + """ + tracks the imaging task to completion. + """ + while True: + imageTask = self.getImagingTask(host) + if imageTask is None: + self.log.info("%s", "Imaging complete") + return + state = int(imageTask['stateID']) + if state == 1: + self.log.info("%s", "Waiting for host to check in") + self.waitForTaskToActive(host) + continue + if state == 3: + self.waitForTaskToStart(host) + self.waitForImaging(host) + continue + time.sleep(8) + + def waitForImaging(self, host): + """ + Once the host begins being imaged, this tracks progress. + """ + # print "Host has begun the imaging process\n" + while True: + task = self.getImagingTask(host) + if task is None: + return + per = str(task['percent']) + self.log.info("%s percent done imaging", per) + time.sleep(15) + + def waitForTaskToActive(self, host): + """ + Waits for the host to reboot and pxe boot + into FOG + """ + while True: + try: + task = self.getImagingTask(host) + except: + pass + state = int(task['stateID']) + if state == 1: + time.sleep(4) + else: + return + + def waitForTaskToStart(self, host): + """ + waits for the task to start and imaging to begin. + """ + while True: + try: + per = str(self.getImagingTask(host)['percent']) + except: + pass + if per.strip() == '': + time.sleep(1) + else: + return + + def getImagingTask(self, host): + """ + Sorts through all current tasks to find the image task + associated with the given host. + """ + try: + taskList = requests.get( + self.baseURL+'task/current', + headers=self.header) + taskList = taskList.json()['tasks'] + imageTask = None + for task in taskList: + hostname = str(task['host']['name']) + if hostname == host and int(task['typeID']) == 1: + imageTask = task + return imageTask + except Exception: + self.log.exception("%s", "Failed to talk to FOG server") + sys.exit(1) + + def getHosts(self): + """ + returns a list of all hosts + """ + req = requests.get(self.baseURL+"host", headers=self.header) + return req.json()['hosts'] + + def getHostsinGroup(self, groupName): + """ + returns a list of all hosts in groupName + """ + groupID = None + groups = requests.get(self.baseURL+"group", headers=self.header) + groups = groups.json()['groups'] + for group in groups: + if groupName.lower() in group['name'].lower(): + groupID = group['id'] + if groupID is None: + return + hostIDs = [] + associations = requests.get( + self.baseURL+"groupassociation", + headers=self.header + ) + associations = associations.json()['groupassociations'] + for association in associations: + if association['groupID'] == groupID: + hostIDs.append(association['hostID']) + + hosts = [] + for hostID in hostIDs: + hosts.append(requests.get( + self.baseURL+"host/"+str(hostID), + headers=self.header + ).json()) + return hosts -- cgit 1.2.3-korg