summaryrefslogtreecommitdiffstats
path: root/tools/laas-fog/source/api
diff options
context:
space:
mode:
Diffstat (limited to 'tools/laas-fog/source/api')
-rw-r--r--tools/laas-fog/source/api/__init__.py17
-rw-r--r--tools/laas-fog/source/api/fog.py288
-rw-r--r--tools/laas-fog/source/api/fuel_api.py306
-rw-r--r--tools/laas-fog/source/api/libvirt_api.py331
-rw-r--r--tools/laas-fog/source/api/vpn.py235
5 files changed, 1177 insertions, 0 deletions
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
diff --git a/tools/laas-fog/source/api/fuel_api.py b/tools/laas-fog/source/api/fuel_api.py
new file mode 100644
index 00000000..01278000
--- /dev/null
+++ b/tools/laas-fog/source/api/fuel_api.py
@@ -0,0 +1,306 @@
+"""
+#############################################################################
+#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 time
+import sys
+
+
+class Fuel_api:
+
+ def __init__(self, url, logger, user="admin", password="admin"):
+ """
+ url is the url of the fog api in the form
+ http://ip.or.host.name:8000/
+ logger is a reference to the logger
+ the default creds for fuel is admin/admin
+ """
+ self.logger = logger
+ self.base = url
+ self.user = user
+ self.password = password
+ self.header = {"Content-Type": "application/json"}
+
+ def getKey(self):
+ """
+ authenticates with the user and password
+ to get a keystone key, used in the headers
+ from here on to talk to fuel.
+ """
+ url = self.base + 'keystone/v2.0/tokens/'
+ reqData = {"auth": {
+ "tenantName": self.user,
+ "passwordCredentials": {
+ "username": self.user,
+ "password": self.password
+ }
+ }}
+ self.logger.info("Retreiving keystone token from %s", url)
+ token = requests.post(url, headers=self.header, json=reqData)
+ self.logger.info("Received response code %d", token.status_code)
+ self.token = token.json()['access']['token']['id']
+ self.header['X-Auth-Token'] = self.token
+
+ def getNotifications(self):
+ """
+ returns the fuel notifications
+ """
+ url = self.base+'/api/notifications'
+ try:
+ req = requests.get(url, headers=self.header)
+ return req.json()
+
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def waitForBootstrap(self):
+ """
+ Waits for the bootstrap image to build.
+ """
+ while True:
+ time.sleep(30)
+ notes = self.getNotifications()
+ for note in notes:
+ if "bootstrap image building done" in note['message']:
+ return
+
+ def getNodes(self):
+ """
+ returns a list of all nodes booted into fuel
+ """
+ url = self.base+'api/nodes'
+ try:
+ req = requests.get(url, headers=self.header)
+ return req.json()
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def getID(self, mac):
+ """
+ gets the fuel id of node with given mac
+ """
+ for node in self.getNodes():
+ if node['mac'] == mac:
+ return node['id']
+
+ def getNetID(self, name, osid):
+ """
+ gets the id of the network with name
+ """
+ url = self.base+'api/clusters/'
+ url += str(osid)+'/network_configuration/neutron'
+ try:
+ req = requests.get(url, headers=self.header)
+ nets = req.json()['networks']
+ for net in nets:
+ if net['name'] == name:
+ return net['id']
+ return -1
+
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def createOpenstack(self):
+ """
+ defines a new openstack environment in fuel.
+ """
+ url = self.base+'api/clusters'
+ data = {
+ "nodes": [],
+ "tasks": [],
+ "name": "OpenStack",
+ "release_id": 2,
+ "net_segment_type": "vlan"
+ }
+ try:
+ req = requests.post(url, json=data, headers=self.header)
+ return req.json()['id']
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def simpleNetDict(self, osID):
+ """
+ returns a simple dict of network names and id numbers
+ """
+ nets = self.getNetworks(osID)
+ netDict = {}
+ targetNets = ['admin', 'public', 'storage', 'management']
+ for net in nets['networks']:
+ for tarNet in targetNets:
+ if tarNet in net['name']:
+ netDict[tarNet] = net['id']
+ return netDict
+
+ def getNetworks(self, osID):
+ """
+ Returns the pythonizezd json of the openstack networks
+ """
+ url = self.base + 'api/clusters/'
+ url += str(osID)+'/network_configuration/neutron/'
+ try:
+ req = requests.get(url, headers=self.header)
+ return req.json()
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def uploadNetworks(self, netJson, osID):
+ """
+ configures the networks of the openstack
+ environment with id osID based on netJson
+ """
+ url = self.base+'api/clusters/'
+ url += str(osID)+'/network_configuration/neutron'
+ try:
+ req = requests.put(url, headers=self.header, json=netJson)
+ return req.json()
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def addNodes(self, clusterID, nodes):
+ """
+ Adds the nodes into this openstack environment.
+ nodes is valid json
+ """
+ url = self.base + 'api/clusters/'+str(clusterID)+'/assignment'
+ try:
+ req = requests.post(url, headers=self.header, json=nodes)
+ return req.json()
+
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def getIfaces(self, nodeID):
+ """
+ returns the pythonized json describing the
+ interfaces of given node
+ """
+ url = self.base + 'api/nodes/'+str(nodeID)+'/interfaces'
+ try:
+ req = requests.get(url, headers=self.header)
+ return req.json()
+
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def setIfaces(self, nodeID, ifaceJson):
+ """
+ configures the interfaces of node with id nodeID
+ with ifaceJson
+ ifaceJson is valid json that fits fuel's schema for ifaces
+ """
+ url = self.base+'/api/nodes/'+str(nodeID)+'/interfaces'
+ try:
+ req = requests.put(url, headers=self.header, json=ifaceJson)
+ return req.json()
+
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def getTasks(self):
+ """
+ returns a list of all tasks
+ """
+ url = self.base+"/api/tasks/"
+ try:
+ req = requests.get(url, headers=self.header)
+ return req.json()
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def waitForTask(self, uuid):
+ """
+ Tracks the progress of task with uuid and
+ returns once the task finishes
+ """
+ progress = 0
+ while progress < 100:
+ for task in self.getTasks():
+ if task['uuid'] == uuid:
+ progress = task['progress']
+ self.logger.info("Task is %s percent done", str(progress))
+ time.sleep(20)
+ # Task may hang a minute at 100% without finishing
+ while True:
+ for task in self.getTasks():
+ if task['uuid'] == uuid and not task['status'] == "ready":
+ time.sleep(10)
+ elif task['uuid'] == uuid and task['status'] == "ready":
+ return
+
+ def getHorizonIP(self, osid):
+ """
+ returns the ip address of the horizon dashboard.
+ Horizon always takes the first ip after the public router's
+ """
+ url = self.base+'api/clusters/'
+ url += str(osid)+'/network_configuration/neutron/'
+ try:
+ req = requests.get(url, headers=self.header)
+ routerIP = req.json()['vips']['vrouter_pub']['ipaddr'].split('.')
+ routerIP[-1] = str(int(routerIP[-1])+1)
+ return '.'.join(routerIP)
+ except Exception:
+ self.logger.exception('%s', "Failed to talk to the Fuel api!")
+ sys.exit(1)
+
+ def deployOpenstack(self, clusterID):
+ """
+ Once openstack and the nodes are configured,
+ this method actually deploys openstack.
+ It takes a while.
+ """
+ # First, we need to provision the cluster
+ url = self.base+'/api/clusters/'+str(clusterID)+'/provision'
+ req = requests.put(url, headers=self.header)
+ if req.status_code < 300:
+ self.logger.info('%s', "Sent provisioning task")
+ else:
+ err = "failed to provision Openstack Environment"
+ self.logger.error('%s', err)
+ sys.exit(1)
+
+ taskUID = ''
+ tasks = self.getTasks()
+ for task in tasks:
+ if task['name'] == "provision" and task['cluster'] == clusterID:
+ taskUID = task['uuid']
+
+ self.waitForTask(taskUID)
+
+ # Then, we deploy cluster
+ url = self.base + '/api/clusters/'+str(clusterID)+'/deploy'
+ req = requests.put(url, headers=self.header)
+ if req.status_code < 300:
+ self.logger.info('%s', "Sent deployment task")
+ taskUID = ''
+ tasks = self.getTasks()
+ for task in tasks:
+ if 'deploy' in task['name'] and task['cluster'] == clusterID:
+ taskUID = task['uuid']
+ if len(taskUID) > 0:
+ self.waitForTask(taskUID)
diff --git a/tools/laas-fog/source/api/libvirt_api.py b/tools/laas-fog/source/api/libvirt_api.py
new file mode 100644
index 00000000..4e19736f
--- /dev/null
+++ b/tools/laas-fog/source/api/libvirt_api.py
@@ -0,0 +1,331 @@
+"""
+#############################################################################
+#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 libvirt
+import time
+import xml.dom
+import xml.dom.minidom
+from domain import Domain
+from network import Network
+from utilities import Utilities
+
+
+class Libvirt:
+ """
+ This class talks to the Libvirt api.
+ Given a config file, this class should create all networks and
+ domains.
+
+ TODO: convert prints to logging and remove uneeded pass statements
+ """
+
+ def __init__(self, hostAddr, net_conf=None, dom_conf=None):
+ """
+ init function
+ hostAddr is the ip address of the host
+ net_conf and dom_conf are the paths
+ to the config files
+ """
+ self.host = hostAddr
+ self.URI = "qemu+ssh://root@"+str(hostAddr)+"/system"
+ self.hypervisor = None
+ self.domains = []
+ self.networks = []
+ self.net_conf = net_conf
+ self.dom_conf = dom_conf
+
+ def setLogger(self, log):
+ """
+ Saves the logger in self.log
+ """
+ self.log = log
+
+ def bootMaster(self):
+ """
+ starts the previously defined master node
+ """
+ for dom in self.domains:
+ if 'master' in dom.name():
+ try:
+ dom.create()
+ except Exception:
+ pass
+
+ def bootSlaves(self):
+ """
+ boots every defined vm with 'slave' in its name
+ """
+ for dom in self.domains:
+ if 'slave' in dom.name():
+ try:
+ dom.create()
+ self.log.info("Booting %s", dom.name())
+ except Exception:
+ self.log.exception("%s", "failed to boot domain")
+ time.sleep(5)
+
+ def getMacs(self, domName):
+ """
+ returns a dictionary with a network name
+ mapped to the mac address of the domain on that net
+ """
+ try:
+ dom = self.hypervisor.lookupByName(domName)
+ xmlDesc = dom.XMLDesc(0)
+ parsedXML = xml.dom.minidom.parseString(xmlDesc)
+ interfacesXML = parsedXML.getElementsByTagName('interface')
+ netDict = {}
+ for iface in interfacesXML:
+ src = iface.getElementsByTagName('source')[0]
+ mac = iface.getElementsByTagName('mac')[0]
+ netDict[src] = mac
+ return netDict
+ except Exception:
+ self.log.exception("%s", "Domain not found")
+
+ def defineVM(self, xmlConfig):
+ """
+ Generic method to define a persistent vm with the
+ given config.
+ Assumes that self.hypervisor is already connected.
+ """
+ if self.checkForVM(xmlConfig):
+ vm = self.hypervisor.defineXML(xmlConfig)
+ if vm is None:
+ name = self.getName(xmlConfig)
+ self.log.error("Failed to define vm %s. exiting", name)
+ exit(1)
+ else:
+ self.log.info("Successfully created vm %s", vm.name())
+ pass
+ self.domains.append(vm)
+
+ def checkForVM(self, xmlConfig):
+ """
+ Checks if another vm with the same name exists
+ on the remote host already. If it does, it will
+ delete that vm
+ """
+ allGood = False
+ vms = self.hypervisor.listAllDomains(0)
+ names = []
+ for dom in vms:
+ names.append(dom.name())
+ vmName = Utilities.getName(xmlConfig)
+ if vmName in names:
+ self.log.warning("domain %s already exists", vmName)
+ self.log.warning("%s", "Atempting to delete it")
+ self.deleteVM(vmName)
+ allGood = True
+ else:
+ allGood = True
+ return allGood
+
+ def deleteVM(self, name):
+ """
+ removes the given vm from the remote host
+ """
+ try:
+ vm = self.hypervisor.lookupByName(name)
+ except:
+ return
+ active = vm.isActive()
+ persistent = vm.isPersistent()
+ if active:
+ try:
+ vm.destroy()
+ except:
+ self.log.exception("%s", "Failed to destroy vm")
+
+ if persistent:
+ try:
+ vm.undefine()
+ except:
+ self.log.exception("%s", "Failed to undefine domain")
+ pass
+
+ def openConnection(self):
+ """
+ opens a connection to the remote host
+ and stores it in self.hypervisor
+ """
+ self.log.info("Attempting to connect to libvirt at %s", self.host)
+ try:
+ hostHypervisor = libvirt.open(self.URI)
+ except:
+ self.log.warning(
+ "Failed to connect to %s. Trying again", self.host
+ )
+ time.sleep(5)
+ try:
+ hostHypervisor = libvirt.open(self.URI)
+ except:
+ self.log.exception("Cannot connect to %s. Exiting", self.host)
+ exit(1)
+
+ if hostHypervisor is None:
+ self.log.error("Failed to connect to %s. Exiting", self.host)
+ exit(1)
+ self.hypervisor = hostHypervisor
+
+ def restartVM(self, vm):
+ """
+ causes the given vm to reboot
+ """
+ dom = self.hypervisor.lookupByName(vm)
+ dom.destroy()
+ time.sleep(15)
+ dom.create()
+
+ def close(self):
+ """
+ Closes connection to remote hypervisor
+ """
+ self.log.info("Closing connection to the hypervisor %s", self.host)
+ self.hypervisor.close()
+
+ def defineAllDomains(self, path):
+ """
+ Defines a domain from all the xml files in a directory
+ """
+ files = Utilities.getXMLFiles(path)
+ definitions = []
+ for xml_desc in files:
+ definitions.append(xml_desc.read())
+
+ for definition in definitions:
+ self.defineVM(definition)
+
+ def createAllNetworks(self, path):
+ """
+ Creates a network from all xml files in a directory
+ """
+ files = Utilities.getXMLFiles(path)
+ definitions = []
+ for xml_desc in files:
+ definitions.append(Utilities.fileToString(xml_desc))
+
+ for definition in definitions:
+ self.createNet(definition)
+
+ def createNet(self, config):
+ """
+ creates the network on the remote host
+ config is the xml in string representation
+ that defines the network
+ """
+ if self.checkNet(config):
+ network = self.hypervisor.networkDefineXML(config)
+
+ if network is None:
+ name = self.getName(config)
+ self.log.warning("Failed to define network %s", name)
+ network.create()
+ if network.isActive() == 1:
+ net = network.name()
+ self.log.info("Successfully defined network %s", net)
+ self.networks.append(network)
+
+ def checkNet(self, config):
+ """
+ Checks if another net with the same name exists, and
+ deletes that network if one is found
+ """
+ allGood = False
+ netName = Utilities.getName(config)
+ if netName not in self.hypervisor.listNetworks():
+ return True
+ else: # net name is already used
+ self.log.warning(
+ "Network %s already exists. Trying to delete it", netName
+ )
+ network = self.hypervisor.networkLookupByName(netName)
+ self.deleteNet(network)
+ allGood = True
+ return allGood
+
+ def deleteNet(self, net):
+ """
+ removes the given network from the host
+ """
+ active = net.isActive()
+ persistent = net.isPersistent()
+ if active:
+ try:
+ net.destroy()
+ except:
+ self.log.warning("%s", "Failed to destroy network")
+
+ if persistent:
+ try:
+ net.undefine()
+ except:
+ self.log.warning("%s", "Failed to undefine network")
+
+ def go(self):
+ """
+ This method does all the work of this class,
+ Parsing the net and vm config files and creating
+ all the requested nets/domains
+ returns a list of all networks and a list of all domains
+ as Network and Domain objects
+ """
+ nets = self.makeNetworks(self.net_conf)
+ doms = self.makeDomains(self.dom_conf)
+ return doms, nets
+
+ def makeNetworks(self, conf):
+ """
+ Given a path to a config file, this method
+ parses the config and creates all requested networks,
+ and returns them in a list of Network objects
+ """
+ networks = []
+ definitions = Network.parseConfigFile(conf)
+ for definition in definitions:
+ network = Network(definition)
+ networks.append(network)
+ self.createNet(network.toXML())
+ return networks
+
+ def makeDomains(self, conf):
+ """
+ Given a path to a config file, this method
+ parses the config and creates all requested vm's,
+ and returns them in a list of Domain objects
+ """
+ domains = []
+ definitions = Domain.parseConfigFile(conf)
+ for definition in definitions:
+ domain = Domain(definition)
+ domains.append(domain)
+ self.defineVM(domain.toXML())
+ return domains
+
+ @staticmethod
+ def getName(xmlString):
+ """
+ given xml with a name tag, this returns the value of name
+ eg:
+ <name>Parker</name>
+ returns 'Parker'
+ """
+ xmlDoc = xml.dom.minidom.parseString(xmlString)
+ nameNode = xmlDoc.documentElement.getElementsByTagName('name')
+ name = str(nameNode[0].firstChild.nodeValue)
+ return name
diff --git a/tools/laas-fog/source/api/vpn.py b/tools/laas-fog/source/api/vpn.py
new file mode 100644
index 00000000..336a681d
--- /dev/null
+++ b/tools/laas-fog/source/api/vpn.py
@@ -0,0 +1,235 @@
+from abc import ABCMeta, abstractmethod
+import ldap
+import os
+import random
+from base64 import b64encode
+from database import BookingDataBase
+
+
+class VPN_BaseClass:
+ """
+ the vpn handler abstract class / interface
+
+ """
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def __init__(self, config):
+ """
+ config is the parsed vpn.yaml file
+ """
+ pass
+
+ @abstractmethod
+ def makeNewUser(self, user=None):
+ """
+ This method is called when a vpn user is needed.
+ This method should create a vpn user in whatever
+ runs the vpn in our infrastructure. returns the
+ credentials for the vpn user and some uid
+ that will be associated with the booking in the
+ database. This uid is used to track the vpn user and
+ to delete the user when there are no bookings associated
+ with that uid.
+ """
+ user = "username"
+ passwd = "password"
+ uid = "some way for you to identify this user in the database"
+ return user, passwd, uid
+
+ @abstractmethod
+ def removeOldUsers(self):
+ """
+ checks the list of all vpn users against a list of
+ vpn users associated with active bookings and removes
+ users who dont have an active booking
+
+ If you want your vpn accounts to be persistent,
+ you can just ignore this
+ """
+ pass
+
+
+names = [
+ 'frodo baggins', 'samwise gamgee', 'peregrin took', 'meriadoc brandybuck',
+ 'bilbo baggins', 'gandalf grey', 'aragorn dunadan', 'arwen evenstar',
+ 'saruman white', 'pippin took', 'merry brandybuck', 'legolas greenleaf',
+ 'gimli gloin', 'anakin skywalker', 'padme amidala', 'han solo',
+ 'jabba hut', 'mace windu', 'sount dooku', 'qui-gon jinn',
+ 'admiral ackbar', 'emperor palpatine'
+]
+
+
+class VPN:
+ """
+ This class communicates with the ldap server to manage vpn users.
+ This class extends the above ABC, and implements the makeNewUser,
+ removeOldUser, and __init__ abstract functions you must override to
+ extend the VPN_BaseClass
+ """
+
+ def __init__(self, config):
+ """
+ init takes the parsed vpn config file as an arguement.
+ automatically connects and authenticates on the ldap server
+ based on the configuration file
+ """
+ self.config = config
+ server = config['server']
+ self.uri = "ldap://"+server
+
+ self.conn = None
+ user = config['authentication']['user']
+ pswd = config['authentication']['pass']
+ if os.path.isfile(pswd):
+ pswd = open(pswd).read()
+ self.connect(user, pswd)
+
+ def connect(self, root_dn, root_pass):
+ """
+ Opens a connection to the server in the config file
+ and authenticates as the given user
+ """
+ self.conn = ldap.initialize(self.uri)
+ self.conn.simple_bind_s(root_dn, root_pass)
+
+ def addUser(self, full_name, passwd):
+ """
+ Adds a user to the ldap server. Creates the new user with the classes
+ and in the directory given in the config file.
+ full_name should be two tokens seperated by a space. The first token
+ will become the username
+ private helper function for the makeNewUser()
+ """
+ first = full_name.split(' ')[0]
+ last = full_name.split(' ')[1]
+ user_dir = self.config['directory']['user']
+ user_dir += ','+self.config['directory']['root']
+ dn = "uid=" + first + ',' + user_dir
+ record = [
+ ('objectclass', ['top', 'inetOrgPerson']),
+ ('uid', first),
+ ('cn', full_name),
+ ('sn', last),
+ ('userpassword', passwd),
+ ('ou', self.config['directory']['user'].split('=')[1])
+ ]
+ self.conn.add_s(dn, record)
+ return dn
+
+ def makeNewUser(self, name=None):
+ """
+ creates a new user in the ldap database, with the given name
+ if supplied. If no name is given, we will try to select from the
+ pre-written list above, and will resort to generating a random string
+ as a username if the preconfigured names are all taken.
+ Returns the username and password the user needs to authenticate, and
+ the dn that we can use to manage the user.
+ """
+ if name is None:
+ i = 0
+ while not self.checkName(name):
+ i += 1
+ if i == 20:
+ name = self.randoString(8)
+ name += ' '+self.randoString(8)
+ break # generates a random name to prevent infinite loop
+ name = self.genUserName()
+ passwd = self.randoString(15)
+ dn = self.addUser(name, passwd)
+ return name, passwd, dn
+
+ def checkName(self, name):
+ """
+ returns true if the name is available
+ """
+ if name is None:
+ return False
+ uid = name.split(' ')[0]
+ base = self.config['directory']['user'] + ','
+ base += self.config['directory']['root']
+ filtr = '(uid=' + uid + ')'
+ timeout = 5
+ ans = self.conn.search_st(
+ base,
+ ldap.SCOPE_SUBTREE,
+ filtr,
+ timeout=timeout
+ )
+ return len(ans) < 1
+
+ @staticmethod
+ def randoString(n):
+ """
+ uses /dev/urandom to generate a random string of length n
+ """
+ n = int(n)
+ # defines valid characters
+ alpha = 'abcdefghijklmnopqrstuvwxyz'
+ alpha_num = alpha
+ alpha_num += alpha.upper()
+ alpha_num += "0123456789"
+
+ # generates random string from /dev/urandom
+ rnd = b64encode(os.urandom(3*n)).decode('utf-8')
+ random_string = ''
+ for char in rnd:
+ if char in alpha_num:
+ random_string += char
+ return str(random_string[:n])
+
+ def genUserName(self):
+ """
+ grabs a random name from the list above
+ """
+ i = random.randint(0, len(names) - 1)
+ return names[i]
+
+ def deleteUser(self, dn):
+ self.conn.delete(dn)
+
+ def getAllUsers(self):
+ """
+ returns all the user dn's in the ldap database in a list
+ """
+ base = self.config['directory']['user'] + ','
+ base += self.config['directory']['root']
+ filtr = '(objectclass='+self.config['user']['objects'][-1]+')'
+ timeout = 10
+ ans = self.conn.search_st(
+ base,
+ ldap.SCOPE_SUBTREE,
+ filtr,
+ timeout=timeout
+ )
+ users = []
+ for user in ans:
+ users.append(user[0]) # adds the dn of each user
+ return users
+
+ def removeOldUsers(self):
+ """
+ removes users from the ldap server who dont have any active bookings.
+ will not delete a user if their uid's are named in the config
+ file as permanent users.
+ """
+ db = self.config['database']
+ # the dn of all users who have an active booking
+ active_users = BookingDataBase(db).getVPN()
+ all_users = self.getAllUsers()
+ for user in all_users:
+ # checks if they are a permanent user
+ if self.is_permanent_user(user):
+ continue
+ # deletes the user if they dont have an active booking
+ if user not in active_users:
+ self.deleteUser(user)
+
+ def is_permanent_user(self, dn):
+ for user in self.config['permanent_users']:
+ if (user in dn) or (dn in user):
+ return True
+ return False
+
+
+VPN_BaseClass.register(VPN)