| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| """Utilities functions |
| """ |
| |
| import marvin |
| import os |
| import re |
| import time |
| import logging |
| import string |
| import random |
| import imaplib |
| import email |
| import socket |
| import urllib.parse |
| import datetime |
| from marvin.cloudstackAPI import cloudstackAPIClient, listHosts, listRouters |
| from platform import system |
| from marvin.cloudstackException import GetDetailExceptionInfo |
| from marvin.sshClient import SshClient |
| from marvin.codes import ( |
| SUCCESS, |
| FAIL, |
| PASS, |
| MATCH_NOT_FOUND, |
| INVALID_INPUT, |
| EMPTY_LIST, |
| FAILED) |
| |
| def _configure_ssh_credentials(hypervisor): |
| ssh_command = "ssh -i ~/.ssh/id_rsa.cloud -ostricthostkeychecking=no " |
| |
| if (str(hypervisor).lower() == 'vmware' |
| or str(hypervisor).lower() == 'hyperv'): |
| ssh_command = "ssh -i ~cloud/.ssh/id_rsa -ostricthostkeychecking=no " |
| |
| return ssh_command |
| |
| |
| def _configure_timeout(hypervisor): |
| timeout = 5 |
| |
| # Increase hop into router |
| if str(hypervisor).lower() == 'hyperv': |
| timeout = 12 |
| |
| return timeout |
| |
| |
| def _execute_ssh_command(hostip, port, username, password, ssh_command, timeout=5): |
| #SSH to the machine |
| ssh = SshClient(hostip, port, username, password) |
| # Ensure the SSH login is successful |
| while True: |
| res = ssh.execute(ssh_command) |
| if len(res) == 0: |
| return res |
| elif "Connection refused".lower() in res[0].lower(): |
| pass |
| elif res[0] != "Host key verification failed.": |
| break |
| elif timeout == 0: |
| break |
| |
| time.sleep(5) |
| timeout = timeout - 1 |
| return res |
| |
| def restart_mgmt_server(server): |
| """Restarts the management server""" |
| |
| try: |
| # Get the SSH client |
| ssh = is_server_ssh_ready( |
| server["ipaddress"], |
| server["port"], |
| server["username"], |
| server["password"], |
| ) |
| result = ssh.execute("/etc/init.d/cloud-management restart") |
| res = str(result) |
| # Server Stop - OK |
| # Server Start - OK |
| if res.count("OK") != 2: |
| raise ("ErrorInReboot!") |
| except Exception as e: |
| raise e |
| return |
| |
| |
| def fetch_latest_mail(services, from_mail): |
| """Fetch mail""" |
| |
| # Login to mail server to verify email |
| mail = imaplib.IMAP4_SSL(services["server"]) |
| mail.login( |
| services["email"], |
| services["password"] |
| ) |
| mail.list() |
| mail.select(services["folder"]) |
| date = (datetime.date.today() - datetime.timedelta(1)).strftime("%d-%b-%Y") |
| |
| result, data = mail.uid( |
| 'search', |
| None, |
| '(SENTSINCE {date} HEADER FROM "{mail}")'.format( |
| date=date, |
| mail=from_mail |
| ) |
| ) |
| # Return False if email is not present |
| if data == []: |
| return False |
| |
| latest_email_uid = data[0].split()[-1] |
| result, data = mail.uid('fetch', latest_email_uid, '(RFC822)') |
| raw_email = data[0][1] |
| email_message = email.message_from_string(raw_email) |
| result = get_first_text_block(email_message) |
| return result |
| |
| |
| def get_first_text_block(email_message_instance): |
| """fetches first text block from the mail""" |
| maintype = email_message_instance.get_content_maintype() |
| if maintype == 'multipart': |
| for part in email_message_instance.get_payload(): |
| if part.get_content_maintype() == 'text': |
| return part.get_payload() |
| elif maintype == 'text': |
| return email_message_instance.get_payload() |
| |
| |
| def random_gen(id=None, size=6, chars=string.ascii_uppercase + string.digits): |
| """Generate Random Strings of variable length""" |
| randomstr = ''.join(random.choice(chars) for x in range(size)) |
| if id: |
| return ''.join([id, '-', randomstr]) |
| return randomstr |
| |
| |
| def cleanup_resources(api_client, resources): |
| """ |
| Delete resources (created during tests) |
| |
| TODO move to marvin.cloudstackTestCase.cloudstackTestCase as it is really part of all test_runs |
| """ |
| for obj in resources: |
| obj.delete(api_client) |
| |
| def is_server_ssh_ready(ipaddress, port, username, password, retries=20, retryinterv=30, timeout=10.0, keyPairFileLocation=None): |
| ''' |
| @Name: is_server_ssh_ready |
| @Input: timeout: tcp connection timeout flag, |
| others information need to be added |
| @Output:object for SshClient |
| Name of the function is little misnomer and is not |
| verifying anything as such mentioned |
| ''' |
| |
| try: |
| ssh = SshClient( |
| host=ipaddress, |
| port=port, |
| user=username, |
| passwd=password, |
| keyPairFiles=keyPairFileLocation, |
| retries=retries, |
| delay=retryinterv, |
| timeout=timeout) |
| except Exception as e: |
| raise Exception("SSH connection has Failed. Waited %ss. Error is %s" % (retries * retryinterv, str(e))) |
| else: |
| return ssh |
| |
| |
| def format_volume_to_ext3(ssh_client, device="/dev/sda"): |
| """Format attached storage to ext3 fs""" |
| cmds = [ |
| "echo -e 'n\np\n1\n\n\nw' | fdisk %s" % device, |
| "mkfs.ext3 %s1" % device, |
| ] |
| for c in cmds: |
| ssh_client.execute(c) |
| |
| |
| def fetch_api_client(config_file='datacenterCfg'): |
| """Fetch the Cloudstack API Client""" |
| config = marvin.configGenerator.get_setup_config(config_file) |
| mgt = config.mgtSvr[0] |
| testClientLogger = logging.getLogger("testClient") |
| asyncTimeout = 3600 |
| return cloudstackAPIClient.CloudStackAPIClient( |
| marvin.cloudstackConnection.cloudConnection( |
| mgt, |
| asyncTimeout, |
| testClientLogger |
| ) |
| ) |
| |
| def get_host_credentials(config, hostip): |
| """Get login information for a host `hostip` (ipv4) from marvin's `config` |
| |
| @return the tuple username, password for the host else raise keyerror""" |
| for zone in config.zones: |
| for pod in zone.pods: |
| for cluster in pod.clusters: |
| for host in cluster.hosts: |
| if str(host.url).startswith('http'): |
| hostname = urllib.parse.urlsplit(str(host.url)).netloc |
| else: |
| hostname = str(host.url) |
| try: |
| if socket.getfqdn(hostip) == socket.getfqdn(hostname): |
| return host.username, host.password |
| except socket.error as e: |
| raise Exception("Unresolvable host %s error is %s" % (hostip, e)) |
| raise KeyError("Please provide the marvin configuration file with credentials to your hosts") |
| |
| def execute_command_in_host(hostip, port, username, password, command, hypervisor=None): |
| timeout = _configure_timeout(hypervisor) |
| result = _execute_ssh_command(hostip, port, username, password, command) |
| return result |
| |
| def get_process_status(hostip, port, username, password, linklocalip, command, hypervisor=None): |
| """Double hop and returns a command execution result""" |
| |
| ssh_command = _configure_ssh_credentials(hypervisor) |
| |
| ssh_command = ssh_command +\ |
| "-oUserKnownHostsFile=/dev/null -p 3922 %s %s" % ( |
| linklocalip, |
| command) |
| timeout = _configure_timeout(hypervisor) |
| |
| result = _execute_ssh_command(hostip, port, username, password, ssh_command) |
| return result |
| |
| |
| def isAlmostEqual(first_digit, second_digit, range=0): |
| digits_equal_within_range = False |
| |
| try: |
| if ((first_digit - range) < second_digit < (first_digit + range)): |
| digits_equal_within_range = True |
| except Exception as e: |
| raise e |
| return digits_equal_within_range |
| |
| |
| def xsplit(txt, seps): |
| """ |
| Split a string in `txt` by list of delimiters in `seps` |
| @param txt: string to split |
| @param seps: list of separators |
| @return: list of split units |
| """ |
| default_sep = seps[0] |
| for sep in seps[1:]: # we skip seps[0] because that's the default separator |
| txt = txt.replace(sep, default_sep) |
| return [i.strip() for i in txt.split(default_sep)] |
| |
| def get_hypervisor_type(apiclient): |
| |
| """Return the hypervisor type of the hosts in setup""" |
| |
| cmd = listHosts.listHostsCmd() |
| cmd.type = 'Routing' |
| cmd.listall = True |
| hosts = apiclient.listHosts(cmd) |
| hosts_list_validation_result = validateList(hosts) |
| assert hosts_list_validation_result[0] == PASS, "host list validation failed" |
| return hosts_list_validation_result[1].hypervisor |
| |
| def get_hypervisor_version(apiclient): |
| |
| """Return the hypervisor type of the hosts in setup""" |
| |
| cmd = listHosts.listHostsCmd() |
| cmd.type = 'Routing' |
| cmd.listall = True |
| hosts = apiclient.listHosts(cmd) |
| hosts_list_validation_result = validateList(hosts) |
| assert hosts_list_validation_result[0] == PASS, "host list validation failed" |
| return hosts_list_validation_result[1].hypervisorversion |
| |
| def is_snapshot_on_nfs(apiclient, dbconn, config, zoneid, snapshotid): |
| """ |
| Checks whether a snapshot with id (not UUID) `snapshotid` is present on the nfs storage |
| |
| @param apiclient: api client connection |
| @param @dbconn: connection to the cloudstack db |
| @param config: marvin configuration file |
| @param zoneid: uuid of the zone on which the secondary nfs storage pool is mounted |
| @param snapshotid: uuid of the snapshot |
| @return: True if snapshot is found, False otherwise |
| """ |
| # snapshot extension to be appended to the snapshot path obtained from db |
| snapshot_extensions = {"vmware": ".ovf", |
| "kvm": "", |
| "xenserver": "", |
| "simulator":""} |
| |
| qresultset = dbconn.execute( |
| "select id from snapshots where uuid = '%s';" \ |
| % str(snapshotid) |
| ) |
| if len(qresultset) == 0: |
| raise Exception( |
| "No snapshot found in cloudstack with id %s" % snapshotid) |
| |
| |
| snapshotid = qresultset[0][0] |
| qresultset = dbconn.execute( |
| "select install_path,store_id from snapshot_store_ref where snapshot_id='%s' and store_role='Image';" % snapshotid |
| ) |
| |
| assert isinstance(qresultset, list), "Invalid db query response for snapshot %s" % snapshotid |
| |
| if len(qresultset) == 0: |
| #Snapshot does not exist |
| return False |
| |
| from .base import ImageStore |
| #pass store_id to get the exact storage pool where snapshot is stored |
| secondaryStores = ImageStore.list(apiclient, zoneid=zoneid, id=int(qresultset[0][1])) |
| |
| assert isinstance(secondaryStores, list), "Not a valid response for listImageStores" |
| assert len(secondaryStores) != 0, "No image stores found in zone %s" % zoneid |
| |
| secondaryStore = secondaryStores[0] |
| |
| if str(secondaryStore.providername).lower() != "nfs": |
| raise Exception( |
| "is_snapshot_on_nfs works only against nfs secondary storage. found %s" % str(secondaryStore.providername)) |
| |
| hypervisor = get_hypervisor_type(apiclient) |
| # append snapshot extension based on hypervisor, to the snapshot path |
| snapshotPath = str(qresultset[0][0]) + snapshot_extensions[str(hypervisor).lower()] |
| |
| nfsurl = secondaryStore.url |
| parse_url = urllib.parse.urlsplit(nfsurl, scheme='nfs') |
| host, path = str(parse_url.netloc), str(parse_url.path) |
| |
| if not config.mgtSvr: |
| raise Exception("Your marvin configuration does not contain mgmt server credentials") |
| mgtSvr, user, passwd = config.mgtSvr[0].mgtSvrIp, config.mgtSvr[0].user, config.mgtSvr[0].passwd |
| |
| try: |
| ssh_client = SshClient( |
| mgtSvr, |
| 22, |
| user, |
| passwd |
| ) |
| |
| pathSeparator = "" #used to form host:dir format |
| if not host.endswith(':'): |
| pathSeparator= ":" |
| |
| cmds = [ |
| |
| "mkdir -p %s /mnt/tmp", |
| "mount -t %s %s%s%s /mnt/tmp" % ( |
| 'nfs', |
| host, |
| pathSeparator, |
| path, |
| ), |
| "test -f %s && echo 'snapshot exists'" % ( |
| os.path.join("/mnt/tmp", snapshotPath) |
| ), |
| ] |
| |
| for c in cmds: |
| result = ssh_client.execute(c) |
| |
| # Unmount the Sec Storage |
| cmds = [ |
| "cd", |
| "umount /mnt/tmp", |
| ] |
| for c in cmds: |
| ssh_client.execute(c) |
| except Exception as e: |
| raise Exception("SSH failed for management server: %s - %s" % |
| (config.mgtSvr[0].mgtSvrIp, e)) |
| return 'snapshot exists' in result |
| |
| def validateList(inp): |
| """ |
| @name: validateList |
| @Description: 1. A utility function to validate |
| whether the input passed is a list |
| 2. The list is empty or not |
| 3. If it is list and not empty, return PASS and first element |
| 4. If not reason for FAIL |
| @Input: Input to be validated |
| @output: List, containing [ Result,FirstElement,Reason ] |
| Ist Argument('Result') : FAIL : If it is not a list |
| If it is list but empty |
| PASS : If it is list and not empty |
| IInd Argument('FirstElement'): If it is list and not empty, |
| then first element |
| in it, default to None |
| IIIrd Argument( 'Reason' ): Reason for failure ( FAIL ), |
| default to None. |
| INVALID_INPUT |
| EMPTY_LIST |
| """ |
| ret = [FAIL, None, None] |
| if inp is None: |
| ret[2] = INVALID_INPUT |
| return ret |
| if not isinstance(inp, list): |
| ret[2] = INVALID_INPUT |
| return ret |
| if len(inp) == 0: |
| ret[2] = EMPTY_LIST |
| return ret |
| return [PASS, inp[0], None] |
| |
| def verifyElementInList(inp, toverify, responsevar=None, pos=0): |
| ''' |
| @name: verifyElementInList |
| @Description: |
| 1. A utility function to validate |
| whether the input passed is a list. |
| The list is empty or not. |
| If it is list and not empty, verify |
| whether a given element is there in that list or not |
| at a given pos |
| @Input: |
| I : Input to be verified whether its a list or not |
| II : Element to verify whether it exists in the list |
| III : variable name in response object to verify |
| default to None, if None, we will verify for the complete |
| first element EX: state of response object object |
| IV : Position in the list at which the input element to verify |
| default to 0 |
| @output: List, containing [ Result,Reason ] |
| Ist Argument('Result') : FAIL : If it is not a list |
| If it is list but empty |
| PASS : If it is list and not empty |
| and matching element was found |
| IIrd Argument( 'Reason' ): Reason for failure ( FAIL ), |
| default to None. |
| INVALID_INPUT |
| EMPTY_LIST |
| MATCH_NOT_FOUND |
| ''' |
| if toverify is None or toverify == '' \ |
| or pos is None or pos < -1 or pos == '': |
| return [FAIL, INVALID_INPUT] |
| out = validateList(inp) |
| if out[0] == FAIL: |
| return [FAIL, out[2]] |
| if len(inp) > pos: |
| if responsevar is None: |
| if inp[pos] == toverify: |
| return [PASS, None] |
| else: |
| if responsevar in inp[pos].__dict__ and getattr(inp[pos], responsevar) == toverify: |
| return [PASS, None] |
| else: |
| return [FAIL, MATCH_NOT_FOUND] |
| else: |
| return [FAIL, MATCH_NOT_FOUND] |
| |
| def checkVolumeSize(ssh_handle=None, |
| volume_name="/dev/sda", |
| cmd_inp="/sbin/fdisk -l | grep Disk", |
| size_to_verify=0): |
| ''' |
| @Name : getDiskUsage |
| @Desc : provides facility to verify the volume size against the size to verify |
| @Input: 1. ssh_handle : machine against which to execute the disk size cmd |
| 2. volume_name : The name of the volume against which to verify the size |
| 3. cmd_inp : Input command used to verify the size |
| 4. size_to_verify: size against which to compare. |
| @Output: Returns FAILED in case of an issue, else SUCCESS |
| ''' |
| try: |
| if ssh_handle is None or cmd_inp is None or volume_name is None: |
| return INVALID_INPUT |
| |
| cmd = cmd_inp |
| ''' |
| Retrieve the cmd output |
| ''' |
| if system().lower() != "windows": |
| fdisk_output = ssh_handle.runCommand(cmd_inp) |
| if fdisk_output["status"] != SUCCESS: |
| return FAILED |
| for line in fdisk_output["stdout"]: |
| if volume_name in line: |
| # Get the bytes from the output |
| # Disk /dev/xvdb: 1 GiB, 1073741824 bytes, 2097152 sectors |
| m = re.match('.*?(\d+) bytes.*', line) |
| if m and str(m.group(1)) == str(size_to_verify): |
| return [SUCCESS,str(m.group(1))] |
| return [FAILED,"Volume Not Found"] |
| except Exception as e: |
| print("\n Exception Occurred under getDiskUsage: " \ |
| "%s" %GetDetailExceptionInfo(e)) |
| return [FAILED,GetDetailExceptionInfo(e)] |
| |
| |
| def verifyRouterState(apiclient, routerid, allowedstates): |
| """List the router and verify that its state is in allowed states |
| @output: List, containing [Result, Reason] |
| Ist Argument ('Result'): FAIL: If router state is not |
| in allowed states |
| PASS: If router state is in |
| allowed states""" |
| |
| try: |
| cmd = listRouters.listRoutersCmd() |
| cmd.id = routerid |
| cmd.listall = True |
| routers = apiclient.listRouters(cmd) |
| except Exception as e: |
| return [FAIL, e] |
| listvalidationresult = validateList(routers) |
| if listvalidationresult[0] == FAIL: |
| return [FAIL, listvalidationresult[2]] |
| if routers[0].state.lower() not in allowedstates: |
| return [FAIL, "state of the router should be in %s but is %s" % |
| (allowedstates, routers[0].state)] |
| return [PASS, None] |
| |
| |
| def wait_until(retry_interval=2, no_of_times=2, callback=None, *callback_args): |
| """ Utility method to try out the callback method at most no_of_times with a interval of retry_interval, |
| Will return immediately if callback returns True. The callback method should be written to return a list of values first being a boolean """ |
| |
| if callback is None: |
| raise ("Bad value for callback method !") |
| |
| wait_result = False |
| for i in range(0,no_of_times): |
| time.sleep(retry_interval) |
| wait_result, return_val = callback(*callback_args) |
| if not(isinstance(wait_result, bool)): |
| raise ("Bad parameter returned from callback !") |
| if wait_result : |
| break |
| |
| return wait_result, return_val |