def __manage_power(self, vm_name, action="poweroff"): """ Powers a particual virtual machine on/off forcefully. :param vm_name: Name of a virtual machine :type vm_name: str :param action: action (poweroff, poweron) :type action: str """ try: content = self.SESSION.RetrieveContent() vm = self.__get_obj(content, [vim.VirtualMachine], vm_name) if action.lower() == "poweroff": #get down with the vickness task = vm.PowerOff() else: #fire it up task = vm.PowerOn() except AttributeError as err: raise SessionException( "Unable to manage power state: '{}'".format(err)) except ValueError as err: raise SessionException( "Unable to manage power state: '{}'".format(err))
def __api_request(self, method, sub_url, payload=""): """ Sends a HTTP request to the Nagios/Icinga API. This function requires a valid HTTP method and a sub-URL (such as /cgi-bin/status.cgi). Optionally, you can also specify payload (for POST). There are also alias functions available. :param method: HTTP request method (GET, POST) :type method: str :param sub_url: relative path (e.g. /cgi-bin/status.cgi) :type sub_url: str :param payload: payload for POST requests :type payload: str .. seealso:: __api_get() .. seealso:: __api_post() """ #send request to API self.LOGGER.debug("%s request to URL '%s', payload='%s'", method.upper(), sub_url, payload) try: if method.lower() not in ["get", "post"]: #going home raise SessionException( "Illegal method '{}' specified".format(method)) #execute request if method.lower() == "post": #POST result = self.session.post("{}{}".format(self.url, sub_url), headers=self.HEADERS, data=payload, verify=self.verify) else: #GET result = self.session.get("{}{}".format(self.url, sub_url), headers=self.HEADERS, verify=self.verify) #this really breaks shit #self.LOGGER.debug("HTML output: %s", result.text) if "error" in result.text.lower(): tree = html.fromstring(result.text) data = tree.xpath("//div[@class='errorMessage']/text()") raise SessionException("CGI error: {}".format(data[0])) if result.status_code in [401, 403]: raise SessionException("Unauthorized") elif result.status_code != 200: raise SessionException( "{}: HTTP operation not successful".format( result.status_code)) else: #return result if method.lower() == "get": return result.text else: return True except ValueError as err: self.LOGGER.error(err) raise
def __api_request(self, method, sub_url, payload=""): """ Sends a HTTP request to the Nagios/Icinga API. This function requires a valid HTTP method and a sub-URL (such as /cgi-bin/status.cgi). Optionally, you can also specify payload (for POST). There are also alias functions available. :param method: HTTP request method (GET, POST) :type method: str :param sub_url: relative path (e.g. /cgi-bin/status.cgi) :type sub_url: str :param payload: payload for POST requests :type payload: str """ #send request to API try: if method.lower() not in ["get", "post"]: #going home raise SessionException( "Illegal method '{}' specified".format(method)) self.LOGGER.debug("%s request to %s (payload: %s)", method.upper(), sub_url, payload) #execute request if method.lower() == "post": #POST result = self.SESSION.post("{}{}".format(self.URL, sub_url), headers=self.HEADERS, data=payload, verify=self.VERIFY_SSL) else: #GET result = self.SESSION.get("{}{}".format(self.URL, sub_url), headers=self.HEADERS, verify=self.VERIFY_SSL) if result.status_code == 404: raise EmptySetException( "HTTP resource not found: {}".format(sub_url)) elif result.status_code != 200: raise SessionException( "{}: HTTP operation not successful".format( result.status_code)) else: #return result self.LOGGER.debug(result.text) return result except ValueError as err: self.LOGGER.error(err) raise SessionException(err)
def get_name_by_id(self, object_id, api_object): """ Returns a Foreman object's name by its ID. param object_id: Foreman object ID type object_id: int param api_object: Foreman object type (e.g. host, environment) type api_object: str """ valid_objects = [ "hostgroup", "location", "organization", "environment", "host", "user" ] try: if api_object.lower() not in valid_objects: #invalid type raise ValueError("Unable to lookup name by invalid field" " type '{}'".format(api_object)) else: #get ID by name result_obj = json.loads( self.api_get("/{}s/{}".format(api_object, object_id))) if result_obj["id"] == object_id: self.LOGGER.debug("I think I found %s #%s...", api_object, object_id) if api_object.lower() == "user": return "{} {}".format(result_obj["firstname"], result_obj["lastname"]) else: return result_obj["name"] except ValueError as err: self.LOGGER.error(err) raise SessionException(err)
def __init__(self, log_level, uri, username, password): """ Constructor, creating the class. It requires specifying a URI and a username and password for communicating with the hypervisor. The constructor will throw an exception if an invalid libvirt URI was specified. After initialization, a connection is established automatically. :param log_level: log level :type log_level: logging :param uri: libvirt URI :type uri: str :param username: API username :type username: str :param password: corresponding password :type password: str """ #set logging self.LOGGER.setLevel(log_level) #validate and set URI if self.validate_uri(uri): self.URI = uri else: raise SessionException("Invalid URI string specified!") #set connection details and connect self.USERNAME = username self.PASSWORD = password self.__connect()
def restart_vm(self, vm_name, force=False): """ Restarts a particular VM (default: soft reboot using guest tools). :param vm_name: Name of a virtual machine :type vm_name: str :param force: Flag whether a hard reboot is requested :type force: bool """ try: target_vm = self.SESSION.lookupByName(vm_name) if force: #kill it with fire target_vm.reboot(1) else: #killing me softly target_vm.reboot(0) except libvirt.libvirtError as err: if "unsupported flags" in err.message.lower(): #trying hypervisor default target_vm.reboot(0) self.LOGGER.error( "Forcing reboot impossible, trying hypervisor default") else: raise SessionException( "Unable to restart VM: '{}'".format(err))
def get_vm_ips(self): """ Returns a list of VMs and their IPs available through the current connection. """ try: #get all VMs vms = self.SESSION.listDefinedDomains() result = [] #scan _all_ the VMs for vm in vms: #get VM and lookup hostname target_vm = self.SESSION.lookupByName(vm) target_hostname = target_vm.hostname() #lookup IP #TODO: IPv6 only? target_ip = socket.gethostbyname(target_hostname) result.append({"hostname": target_hostname, "ip": target_ip}) return result except libvirt.libvirtError as err: if "not supported by" in err.message.lower(): raise UnsupportedRequestException(err) else: raise SessionException( "Unable to get VM IP information: '{}'".format(err))
def get_hostparam_id_by_name(self, host, param_name): """ Returns a Foreman host parameter's internal ID by its name. :param host: Foreman host object ID :type host: int :param param_name: host parameter name :type param_name: str """ #TODO: Move to get_id_by_name try: result_obj = json.loads( self.api_get("/hosts/{}/parameters".format(host))) #TODO: nicer way than looping? numpy? #TODO allow/return multiple IDs to reduce overhead? for entry in result_obj["results"]: if entry["name"].lower() == param_name.lower(): self.LOGGER.debug( "Found relevant parameter '%s' with ID #%s", entry["name"], entry["id"]) return entry["id"] except ValueError as err: self.LOGGER.error(err) raise SessionException(err)
def get_vm_ips(self, hide_empty=True, ipv6_only=False): """ Returns a list of VMs and their IPs available through the current connection. :param hide_empty: hide VMs without network information :type hide_empty: bool :param ipv6_only: use IPv6 addresses only :type ipv6_only: bool """ try: #get _all_ the VMs content = self.SESSION.RetrieveContent() #result = {} result = [] #create view cotaining VM objects object_view = content.viewManager.CreateContainerView( content.rootFolder, [vim.VirtualMachine], True) for obj in object_view.view: if not hide_empty or obj.summary.guest.ipAddress != None: #try to find the best IP self.LOGGER.debug("Trying to find best IP for VM '%s'", obj.name) if ipv6_only: is_valid_address = is_ipv6 self.LOGGER.debug("Filtering for IPv6") else: is_valid_address = is_ipv4 self.LOGGER.debug("Filtering for IPv4") target_ip = obj.summary.guest.ipAddress self.LOGGER.debug("Primary guest address: '%s'", target_ip) if not is_valid_address(target_ip): # other NICs for nic in obj.guest.net: for address in nic.ipConfig.ipAddress: if is_valid_address(address.ipAddress): target_ip = address.ipAddress self.LOGGER.debug("NIC address: '%s'", target_ip) break if is_valid_address(address.ipAddress): break self.LOGGER.debug("Set IP address to '%s'", target_ip) #Adding result result.append({ "object_name": obj.config.name, "hostname": obj.summary.guest.hostName, "ip": target_ip }) return result except Exception as err: self.LOGGER.error("Unable to get VM IP information: '%s'", err) raise SessionException(err)
def powerstate_vm(self, vm_name): """ Returns the power state of a particular virtual machine. :param vm_name: Name of a virtual machine :type vm_name: str """ try: content = self.SESSION.RetrieveContent() vm = self.__get_obj(content, [vim.VirtualMachine], vm_name) if vm.runtime.powerState == vim.VirtualMachinePowerState.poweredOn: return "poweredOn" elif vm.runtime.powerState == vim.VirtualMachinePowerState.poweredOff: return "poweredOff" except AttributeError as err: raise SessionException( "Unable to get power state: '{}'".format(err)) except ValueError as err: raise SessionException( "Unable to get power state: '{}'".format(err))
def get_id_by_name(self, name, api_object): """ Returns a Foreman object's internal ID by its name. :param name: Foreman object name :type name: str :param api_object: Foreman object type (e.g. host, environment) :type api_object: str """ valid_objects = [ "hostgroup", "location", "organization", "environment", "host" ] filter_object = { "hostgroup": "title", "location": "name", "host": "name", "organization": "title", "environment": "name" } try: if api_object.lower() not in valid_objects: #invalid type raise ValueError("Unable to lookup name by invalid field" " type '{}'".format(api_object)) else: #get ID by name result_obj = json.loads(self.api_get( "/{}s".format(api_object))) #TODO: nicer way than looping? numpy? Direct URL? for entry in result_obj["results"]: if entry[ filter_object[api_object]].lower() == name.lower(): self.LOGGER.debug("%s %s seems to have ID #%s", api_object, name, entry["id"]) return entry["id"] #not found raise SessionException("Object not found") except ValueError as err: self.LOGGER.error(err) raise SessionException(err)
def __connect(self): """This function establishes a connection to the hypervisor.""" #create weirdo auth dict auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_PASSPHRASE], self.retrieve_credentials, None] #authenticate try: self.SESSION = libvirt.openAuth(self.URI, auth, 0) if self.SESSION == None: raise SessionException( "Unable to establish connection to hypervisor!") except libvirt.libvirtError as err: raise InvalidCredentialsException("Invalid credentials")
def get_host_params(self, host): """ Returns all parameters for a particular host. :param host: Forenam host name :type host: str """ try: result_obj = json.loads( self.api_get("/hosts/{}/parameters".format(host))) return result_obj["results"] except ValueError as err: self.LOGGER.error(err) raise SessionException(err)
def __connect(self): """ This function establishes a connection to Spacewalk. """ #set api session and key try: self.api_session = Server(self.url) self.api_key = self.api_session.auth.login(self.username, self.password) except Fault as err: if err.faultCode == 2950: raise InvalidCredentialsException( "Wrong credentials supplied: '%s'", err.faultString) else: raise SessionException( "Generic remote communication error: '%s'", err.faultString)
def get_vm_hosts(self): """ Returns a list of VMs and their hypervisors available through the current connection. """ try: #get _all_ the VMs content = self.SESSION.RetrieveContent() result = {} #create view cotaining VM objects object_view = content.viewManager.CreateContainerView( content.rootFolder, [vim.VirtualMachine], True) for obj in object_view.view: result[obj.config.name] = {"hypervisor": obj.runtime.host.name} return result except ValueError as err: self.LOGGER.error("Unable to get VM hypervisor information: '%s'", err) raise SessionException(err)
def __manage_snapshot(self, vm_name, snapshot_title, snapshot_text, action="create"): """ Creates/removes a snapshot for a particular virtual machine. This requires specifying a VM, comment title and text. There are also two alias functions. :param vm_name: Name of a virtual machine :type vm_name: str :param snapshot_title: Snapshot title :type snapshot_title: str :param snapshot_text: Snapshot text :type snapshot_text: str :param remove_snapshot: Removes a snapshot if set to True :type remove_snapshot: bool """ try: target_vm = self.SESSION.lookupByName(vm_name) if action.lower() == "remove": #remove snapshot target_snap = target_vm.snapshotLookupByName(snapshot_title, 0) return target_snap.delete(0) elif action.lower() == "revert": #revert snapshot target_snap = target_vm.snapshotLookupByName(snapshot_title, 0) return target_vm.revertToSnapshot(target_snap) else: #create snapshot snap_xml = """<domainsnapshot><name>{}</name><description>{} "</description></domainsnapshot>""".format( snapshot_title, snapshot_text) return target_vm.snapshotCreateXML(snap_xml, 0) except libvirt.libvirtError as err: raise SessionException("Unable to {} snapshot: '{}'".format( action.lower(), err))
def restart_vm(self, vm_name, force=False): """ Restarts a particular VM (default: soft reboot using guest tools). :param vm_name: Name of a virtual machine :type vm_name: str :param force: Flag whether a hard reboot is requested :type force: bool """ try: #get VM content = self.SESSION.RetrieveContent() vm = self.__get_obj(content, [vim.VirtualMachine], vm_name) if force: #kill it with fire vm.ResetVM_Task() else: #killing me softly vm.RebootGuest() except: raise SessionException("Unable to restart VM: '{}'".format( sys.exc_info()[0]))
def has_snapshot(self, vm_name, snapshot_title): """ Returns whether a particular virtual machine is currently protected by a snapshot. This requires specifying a VM name. :param vm_name: Name of a virtual machine :type vm_name: str :param snapshot_title: Snapshot title :type snapshot_title: str """ try: #find VM and get all snapshots target_vm = self.SESSION.lookupByName(vm_name) target_snapshots = target_vm.snapshotListNames(0) if snapshot_title in target_snapshots: return True except libvirt.libvirtError as err: if "no domain with name" in err.message.lower(): #snapshot not found raise EmptySetException("No snapshots found") else: self.LOGGER.error( "Unable to determine snapshot: '{}'".format(err)) raise SessionException(err)
def __api_request(self, method, sub_url, payload="", hits=1337, page=1): """ Sends a HTTP request to the Foreman API. This function requires a valid HTTP method and a sub-URL (such as /hosts). Optionally, you can also specify payload (for POST, DELETE, PUT) and hits/page and a page number (when retrieving data using GET). There are also alias functions available. :param method: HTTP request method (GET, POST, DELETE, PUT) :type method: str :param sub_url: relative path within the API tree (e.g. /hosts) :type sub_url: str :param payload: payload for POST/PUT requests :type payload: str :param hits: numbers of hits/page for GET requests (must be set sadly) :type hits: int :param page: number of page/results to display (must be set sadly) :type page: int .. todo:: Find a nicer way to display all hits, we shouldn't use 1337 hits/page .. seealso:: api_get() .. seealso:: api_post() .. seealso:: api_put() .. seealso:: api_delete() """ #send request to API try: if method.lower() not in ["get", "post", "delete", "put"]: #going home raise SessionException( "Illegal method '{}' specified".format(method)) self.LOGGER.debug("%s request to %s%s (payload: %s)", method.upper(), self.URL, sub_url, str(payload)) #setting headers my_headers = self.HEADERS if method.lower() != "get": #add special headers for non-GETs my_headers["Content-Type"] = "application/json" my_headers["Accept"] = "application/json,version=2" #send request if method.lower() == "put": #PUT result = self.SESSION.put("{}{}".format(self.URL, sub_url), data=payload, headers=my_headers, verify=self.VERIFY) elif method.lower() == "delete": #DELETE result = self.SESSION.delete("{}{}".format(self.URL, sub_url), data=payload, headers=my_headers, verify=self.VERIFY) elif method.lower() == "post": #POST result = self.SESSION.post("{}{}".format(self.URL, sub_url), data=payload, headers=my_headers, verify=self.VERIFY) else: #GET result = self.SESSION.get("{}{}?per_page={}&page={}".format( self.URL, sub_url, hits, page), headers=self.HEADERS, verify=self.VERIFY) if "unable to authenticate" in result.text.lower(): raise InvalidCredentialsException("Unable to authenticate") if result.status_code not in [200, 201, 202]: raise SessionException( "{}: HTTP operation not successful {}".format( result.status_code, result.text)) else: #return result if method.lower() == "get": return result.text else: return True except ValueError as err: self.LOGGER.error(err) raise SessionException(err) pass
def __manage_snapshot(self, vm_name, snapshot_title, snapshot_text, action="create"): """ Creates/removes a snapshot for a particular virtual machine. This requires specifying a VM, comment title and text. There are also two alias functions. :param vm_name: Name of a virtual machine :type vm_name: str :param snapshot_title: Snapshot title :type snapshot_title: str :param snapshot_text: Snapshot text :type snapshot_text: str :param action: action (create, remove) :type remove_snapshot: str """ #make sure to quiesce and not dump memory #TODO: maybe we should supply an option for this? dump_memory = False quiesce = True try: content = self.SESSION.RetrieveContent() vm = self.__get_obj(content, [vim.VirtualMachine], vm_name) if action.lower() != "create": #get _all_ the snapshots snapshots = self.__get_snapshots(vm_name) for snapshot in snapshots: childs = snapshot.childSnapshotList if snapshot.name == snapshot_title: if action.lower() == "remove": #remove snapshot snapshot.snapshot.RemoveSnapshot_Task(True) else: #revert snapshot snapshot.snapshot.RevertToSnapshot_Task() if childs: #also iterate through childs for child in childs: if child.name == snapshot_title: if action.lower() == "remove": #remove snapshot child.snapshot.RemoveSnapshot_Task(True) else: #revert snapshot child.snapshot.RevertToSnapshot_Task() else: #only create snapshot if not already existing try: if not self.has_snapshot(vm_name, snapshot_title): task = vm.CreateSnapshot(snapshot_title, snapshot_text, dump_memory, quiesce) else: raise SnapshotExistsException( "Snapshot '{}' for VM '{}' already exists!".format( snapshot_title, vm_name)) except EmptySetException as err: task = vm.CreateSnapshot(snapshot_title, snapshot_text, dump_memory, quiesce) except TypeError as err: raise SessionException( "Unable to manage snapshot: '{}'".format(err)) except ValueError as err: raise SessionException( "Unable to manage snapshot: '{}'".format(err)) except AttributeError as err: raise SessionException( "Unable to manage snapshot: '{}'".format(err))