summaryrefslogtreecommitdiffstats
path: root/tools/laas-fog/source/api/fog.py
blob: 62874039c53010bf91a8f7df56abbdbaae2f39c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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