http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/serviceoffering.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/serviceoffering.py b/tools/marvin/marvin/lib/cloudstack/serviceoffering.py new file mode 100644 index 0000000..5191134 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/serviceoffering.py @@ -0,0 +1,99 @@ +# 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. + +from marvin.cloudstackAPI import createServiceOffering,deleteServiceOffering,listServiceOfferings +class ServiceOffering: + + """Manage service offerings cycle""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, domainid=None, **kwargs): + """Create Service offering""" + cmd = createServiceOffering.createServiceOfferingCmd() + cmd.cpunumber = services["cpunumber"] + cmd.cpuspeed = services["cpuspeed"] + cmd.displaytext = services["displaytext"] + cmd.memory = services["memory"] + cmd.name = services["name"] + if "storagetype" in services: + cmd.storagetype = services["storagetype"] + + if "systemvmtype" in services: + cmd.systemvmtype = services['systemvmtype'] + + if "issystem" in services: + cmd.issystem = services['issystem'] + + if "tags" in services: + cmd.tags = services["tags"] + + if "hosttags" in services: + cmd.hosttags = services["hosttags"] + + if "deploymentplanner" in services: + cmd.deploymentplanner = services["deploymentplanner"] + + if "serviceofferingdetails" in services: + count = 1 + for i in services["serviceofferingdetails"]: + for key, value in i.items(): + setattr(cmd, "serviceofferingdetails[%d].key" % count, key) + setattr(cmd, "serviceofferingdetails[%d].value" % count, value) + count = count + 1 + + if "isvolatile" in services: + cmd.isvolatile = services["isvolatile"] + + if "miniops" in services: + cmd.miniops = services["miniops"] + + if "maxiops" in services: + cmd.maxiops = services["maxiops"] + + if "customizediops" in services: + cmd.customizediops = services["customizediops"] + + if "offerha" in services: + cmd.offerha = services["offerha"] + + # Service Offering private to that domain + if domainid: + cmd.domainid = domainid + + [setattr(cmd, k, v) for k, v in kwargs.items()] + return ServiceOffering(apiclient.createServiceOffering(cmd).__dict__) + + def delete(self, apiclient): + """Delete Service offering""" + cmd = deleteServiceOffering.deleteServiceOfferingCmd() + cmd.id = self.id + apiclient.deleteServiceOffering(cmd) + return + + @classmethod + def list(cls, apiclient, **kwargs): + """Lists all available service offerings.""" + + cmd = listServiceOfferings.listServiceOfferingsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listServiceOfferings(cmd)) +
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/snapshot.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/snapshot.py b/tools/marvin/marvin/lib/cloudstack/snapshot.py new file mode 100644 index 0000000..b3f10b9 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/snapshot.py @@ -0,0 +1,125 @@ +# 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. + +import time + +from marvin.cloudstackAPI import createSnapshot,deleteSnapshot,listSnapshots,listSnapshotPolicies,createSnapshotPolicy,deleteSnapshotPolicies +from marvin.lib.cloudstack.utils import validateList +from marvin.codes import BACKED_UP,BACKING_UP,FAIL,PASS + + +class Snapshot: + """Manage Snapshot Lifecycle + """ + '''Class level variables''' + # Variables denoting possible Snapshot states - start + BACKED_UP = BACKED_UP + BACKING_UP = BACKING_UP + # Variables denoting possible Snapshot states - end + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, volume_id, account=None, + domainid=None, projectid=None): + """Create Snapshot""" + cmd = createSnapshot.createSnapshotCmd() + cmd.volumeid = volume_id + if account: + cmd.account = account + if domainid: + cmd.domainid = domainid + if projectid: + cmd.projectid = projectid + return Snapshot(apiclient.createSnapshot(cmd).__dict__) + + def delete(self, apiclient): + """Delete Snapshot""" + cmd = deleteSnapshot.deleteSnapshotCmd() + cmd.id = self.id + apiclient.deleteSnapshot(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all snapshots matching criteria""" + + cmd = listSnapshots.listSnapshotsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listSnapshots(cmd)) + + def validateState(self, apiclient, snapshotstate, timeout=600): + """Check if snapshot is in required state + returnValue: List[Result, Reason] + @Result: PASS if snapshot is in required state, + else FAIL + @Reason: Reason for failure in case Result is FAIL + """ + isSnapshotInRequiredState = False + try: + while timeout >= 0: + snapshots = Snapshot.list(apiclient, id=self.id) + assert validateList(snapshots)[0] == PASS, "snapshots list\ + validation failed" + if str(snapshots[0].state).lower() == snapshotstate: + isSnapshotInRequiredState = True + break + timeout -= 60 + time.sleep(60) + #end while + if isSnapshotInRequiredState: + return[PASS, None] + else: + raise Exception("Snapshot not in required state") + except Exception as e: + return [FAIL, e] + +class SnapshotPolicy: + """Manage snapshot policies""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, volumeid, services): + """Create Snapshot policy""" + cmd = createSnapshotPolicy.createSnapshotPolicyCmd() + cmd.intervaltype = services["intervaltype"] + cmd.maxsnaps = services["maxsnaps"] + cmd.schedule = services["schedule"] + cmd.timezone = services["timezone"] + cmd.volumeid = volumeid + return SnapshotPolicy(apiclient.createSnapshotPolicy(cmd).__dict__) + + def delete(self, apiclient): + """Delete Snapshot policy""" + cmd = deleteSnapshotPolicies.deleteSnapshotPoliciesCmd() + cmd.id = self.id + apiclient.deleteSnapshotPolicies(cmd) + return + + @classmethod + def list(cls, apiclient, **kwargs): + """Lists snapshot policies.""" + + cmd = listSnapshotPolicies.listSnapshotPoliciesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listSnapshotPolicies(cmd)) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/storagepool.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/storagepool.py b/tools/marvin/marvin/lib/cloudstack/storagepool.py new file mode 100644 index 0000000..1836c26 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/storagepool.py @@ -0,0 +1,122 @@ +# 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. + +from marvin.cloudstackAPI import createStoragePool,enableStorageMaintenance,deleteStoragePool,listStoragePools,findStoragePoolsForMigration +import time +class StoragePool: + """Manage Storage pools (Primary Storage)""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, scope=None, clusterid=None, + zoneid=None, podid=None, provider=None, tags=None, + capacityiops=None, capacitybytes=None, hypervisor=None): + """Create Storage pool (Primary Storage)""" + + cmd = createStoragePool.createStoragePoolCmd() + cmd.name = services["name"] + + if podid: + cmd.podid = podid + elif "podid" in services: + cmd.podid = services["podid"] + + cmd.url = services["url"] + if clusterid: + cmd.clusterid = clusterid + elif "clusterid" in services: + cmd.clusterid = services["clusterid"] + + if zoneid: + cmd.zoneid = zoneid + else: + cmd.zoneid = services["zoneid"] + + if scope: + cmd.scope = scope + elif "scope" in services: + cmd.scope = services["scope"] + + if provider: + cmd.provider = provider + elif "provider" in services: + cmd.provider = services["provider"] + + if tags: + cmd.tags = tags + elif "tags" in services: + cmd.tags = services["tags"] + + if capacityiops: + cmd.capacityiops = capacityiops + elif "capacityiops" in services: + cmd.capacityiops = services["capacityiops"] + + if capacitybytes: + cmd.capacitybytes = capacitybytes + elif "capacitybytes" in services: + cmd.capacitybytes = services["capacitybytes"] + + if hypervisor: + cmd.hypervisor = hypervisor + elif "hypervisor" in services: + cmd.hypervisor = services["hypervisor"] + + return StoragePool(apiclient.createStoragePool(cmd).__dict__) + + def delete(self, apiclient): + """Delete Storage pool (Primary Storage)""" + + # Storage pool must be in maintenance mode before deletion + cmd = enableStorageMaintenance.enableStorageMaintenanceCmd() + cmd.id = self.id + apiclient.enableStorageMaintenance(cmd) + time.sleep(30) + cmd = deleteStoragePool.deleteStoragePoolCmd() + cmd.id = self.id + apiclient.deleteStoragePool(cmd) + return + + def enableMaintenance(self, apiclient): + """enables maintenance mode Storage pool""" + + cmd = enableStorageMaintenance.enableStorageMaintenanceCmd() + cmd.id = self.id + return apiclient.enableStorageMaintenance(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all storage pools matching criteria""" + + cmd = listStoragePools.listStoragePoolsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listStoragePools(cmd)) + + @classmethod + def listForMigration(cls, apiclient, **kwargs): + """List all storage pools for migration matching criteria""" + + cmd = findStoragePoolsForMigration.findStoragePoolsForMigrationCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.findStoragePoolsForMigration(cmd)) + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/template.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/template.py b/tools/marvin/marvin/lib/cloudstack/template.py new file mode 100644 index 0000000..ca5a228 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/template.py @@ -0,0 +1,277 @@ +# 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. + +import time + +from marvin.cloudstackAPI import createTemplate,listOsTypes,registerTemplate,extractTemplate,deleteTemplate,updateTemplatePermissions,copyTemplate,updateTemplate,listTemplates +from marvin.lib.cloudstack.utils import random_gen + + +class Template: + """Manage template life cycle""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, volumeid=None, + account=None, domainid=None, projectid=None): + """Create template from Volume""" + # Create template from Virtual machine and Volume ID + cmd = createTemplate.createTemplateCmd() + cmd.displaytext = services["displaytext"] + cmd.name = "-".join([services["name"], random_gen()]) + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + # Find OSTypeId from Os type + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + raise Exception( + "Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + raise Exception( + "Unable to find Ostype is required for creating template") + + cmd.isfeatured = services[ + "isfeatured"] if "isfeatured" in services else False + cmd.ispublic = services[ + "ispublic"] if "ispublic" in services else False + cmd.isextractable = services[ + "isextractable"] if "isextractable" in services else False + cmd.passwordenabled = services[ + "passwordenabled"] if "passwordenabled" in services else False + + if volumeid: + cmd.volumeid = volumeid + + if account: + cmd.account = account + + if domainid: + cmd.domainid = domainid + + if projectid: + cmd.projectid = projectid + return Template(apiclient.createTemplate(cmd).__dict__) + + @classmethod + def register(cls, apiclient, services, zoneid=None, + account=None, domainid=None, hypervisor=None, + projectid=None): + """Create template from URL""" + + # Create template from Virtual machine and Volume ID + cmd = registerTemplate.registerTemplateCmd() + cmd.displaytext = services["displaytext"] + cmd.name = "-".join([services["name"], random_gen()]) + cmd.format = services["format"] + if hypervisor: + cmd.hypervisor = hypervisor + elif "hypervisor" in services: + cmd.hypervisor = services["hypervisor"] + + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + # Find OSTypeId from Os type + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + raise Exception( + "Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + raise Exception( + "Unable to find Ostype is required for registering template") + + cmd.url = services["url"] + + if zoneid: + cmd.zoneid = zoneid + else: + cmd.zoneid = services["zoneid"] + + cmd.isfeatured = services[ + "isfeatured"] if "isfeatured" in services else False + cmd.ispublic = services[ + "ispublic"] if "ispublic" in services else False + cmd.isextractable = services[ + "isextractable"] if "isextractable" in services else False + cmd.passwordenabled = services[ + "passwordenabled"] if "passwordenabled" in services else False + + if account: + cmd.account = account + + if domainid: + cmd.domainid = domainid + + if projectid: + cmd.projectid = projectid + elif "projectid" in services: + cmd.projectid = services["projectid"] + + # Register Template + template = apiclient.registerTemplate(cmd) + + if isinstance(template, list): + return Template(template[0].__dict__) + + @classmethod + def extract(cls, apiclient, id, mode, zoneid=None): + "Extract template " + + cmd = extractTemplate.extractTemplateCmd() + cmd.id = id + cmd.mode = mode + cmd.zoneid = zoneid + + return apiclient.extractTemplate(cmd) + + @classmethod + def create_from_snapshot(cls, apiclient, snapshot, services, + random_name=True): + """Create Template from snapshot""" + # Create template from Virtual machine and Snapshot ID + cmd = createTemplate.createTemplateCmd() + cmd.displaytext = services["displaytext"] + cmd.name = "-".join([ + services["name"], + random_gen() + ]) if random_name else services["name"] + + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + # Find OSTypeId from Os type + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + raise Exception( + "Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + raise Exception( + "Unable to find Ostype is required for creating template") + + cmd.snapshotid = snapshot.id + return Template(apiclient.createTemplate(cmd).__dict__) + + def delete(self, apiclient): + """Delete Template""" + + cmd = deleteTemplate.deleteTemplateCmd() + cmd.id = self.id + apiclient.deleteTemplate(cmd) + + def download(self, apiclient, timeout=5, interval=60): + """Download Template""" + # Sleep to ensure template is in proper state before download + time.sleep(interval) + + while True: + template_response = Template.list( + apiclient, + id=self.id, + zoneid=self.zoneid, + templatefilter='self' + ) + if isinstance(template_response, list): + + template = template_response[0] + # If template is ready, + # template.status = Download Complete + # Downloading - x% Downloaded + # Error - Any other string + if template.status == 'Download Complete': + break + + elif 'Downloaded' in template.status: + time.sleep(interval) + + elif 'Installing' not in template.status: + raise Exception( + "Error in downloading template: status - %s" % + template.status) + + elif timeout == 0: + break + + else: + time.sleep(interval) + timeout = timeout - 1 + return + + def updatePermissions(self, apiclient, **kwargs): + """Updates the template permissions""" + + cmd = updateTemplatePermissions.updateTemplatePermissionsCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.updateTemplatePermissions(cmd)) + + def update(self, apiclient, **kwargs): + """Updates the template details""" + + cmd = updateTemplate.updateTemplateCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.updateTemplate(cmd)) + + @classmethod + def copy(cls, apiclient, id, sourcezoneid, destzoneid): + "Copy Template from source Zone to Destination Zone" + + cmd = copyTemplate.copyTemplateCmd() + cmd.id = id + cmd.sourcezoneid = sourcezoneid + cmd.destzoneid = destzoneid + + return apiclient.copyTemplate(cmd) + + def copy(self, apiclient, sourcezoneid, destzoneid): + "Copy Template from source Zone to Destination Zone" + + cmd = copyTemplate.copyTemplateCmd() + cmd.id = self.id + cmd.sourcezoneid = sourcezoneid + cmd.destzoneid = destzoneid + + return apiclient.copyTemplate(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all templates matching criteria""" + + cmd = listTemplates.listTemplatesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listTemplates(cmd)) + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/user.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/user.py b/tools/marvin/marvin/lib/cloudstack/user.py new file mode 100644 index 0000000..7ae54c3 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/user.py @@ -0,0 +1,95 @@ +# 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. +from marvin.cloudstackAPI import createUser,deleteUser,listUsers,registerUserKeys,updateUser,login +from marvin.lib.cloudstack.utils import random_gen +class User: + """ User Life Cycle """ + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, account, domainid): + cmd = createUser.createUserCmd() + """Creates an user""" + + cmd.account = account + cmd.domainid = domainid + cmd.email = services["email"] + cmd.firstname = services["firstname"] + cmd.lastname = services["lastname"] + + if "userUUID" in services: + cmd.userid = "-".join([services["userUUID"], random_gen()]) + + cmd.password = services["password"] + cmd.username = "-".join([services["username"], random_gen()]) + user = apiclient.createUser(cmd) + + return User(user.__dict__) + + def delete(self, apiclient): + """Delete an account""" + cmd = deleteUser.deleteUserCmd() + cmd.id = self.id + apiclient.deleteUser(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """Lists users and provides detailed account information for + listed users""" + + cmd = listUsers.listUsersCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listUsers(cmd)) + + @classmethod + def registerUserKeys(cls, apiclient, userid): + cmd = registerUserKeys.registerUserKeysCmd() + cmd.id = userid + return apiclient.registerUserKeys(cmd) + + def update(self, apiclient, **kwargs): + """Updates the user details""" + + cmd = updateUser.updateUserCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return (apiclient.updateUser(cmd)) + + @classmethod + def update(cls, apiclient, id, **kwargs): + """Updates the user details (class method)""" + + cmd = updateUser.updateUserCmd() + cmd.id = id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return (apiclient.updateUser(cmd)) + + @classmethod + def login(cls, apiclient, username, password, domain=None, domainid=None): + """Logins to the CloudStack""" + + cmd = login.loginCmd() + cmd.username = username + cmd.password = password + if domain: + cmd.domain = domain + if domainid: + cmd.domainId = domainid + return apiclient.login(cmd) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/utils.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/utils.py b/tools/marvin/marvin/lib/cloudstack/utils.py new file mode 100644 index 0000000..c52e277 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/utils.py @@ -0,0 +1,504 @@ +# 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 time +import logging +import string +import random +import imaplib +import email +import socket +import urlparse +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 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""" + 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, 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 = urlparse.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, 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 get_process_status(hostip, port, username, password, linklocalip, process, hypervisor=None): + """Double hop and returns a process status""" + + #SSH to the machine + ssh = SshClient(hostip, port, username, password) + if (str(hypervisor).lower() == 'vmware' + or str(hypervisor).lower() == 'hyperv'): + ssh_command = "ssh -i /var/cloudstack/management/.ssh/id_rsa -ostricthostkeychecking=no " + else: + ssh_command = "ssh -i ~/.ssh/id_rsa.cloud -ostricthostkeychecking=no " + + ssh_command = ssh_command +\ + "-oUserKnownHostsFile=/dev/null -p 3922 %s %s" % ( + linklocalip, + process) + + # Double hop into router + timeout = 5 + # Ensure the SSH login is successful + while True: + res = ssh.execute(ssh_command) + + if res[0] != "Host key verification failed.": + break + elif timeout == 0: + break + + time.sleep(5) + timeout = timeout - 1 + return res + + +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 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": ".vhd", + "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 + from urllib2 import urlparse + parse_url = urlparse.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 veify 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: + parts = line.strip().split() + if str(parts[-2]) == str(size_to_verify): + return [SUCCESS,str(parts[-2])] + return [FAILED,"Volume Not Found"] + except Exception, 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].redundantstate not in allowedstates: + return [FAIL, "Redundant state of the router should be in %s but is %s" % + (allowedstates, routers[0].redundantstate)] + return [PASS, None] + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/vm.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/vm.py b/tools/marvin/marvin/lib/cloudstack/vm.py new file mode 100644 index 0000000..3b81778 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/vm.py @@ -0,0 +1,706 @@ +# 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. +import time +import base64 + +from marvin.codes import (FAIL, PASS, RUNNING, STOPPED, + STARTING, DESTROYED, EXPUNGING, + STOPPING) +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.cloudstack.utils import validateList, is_server_ssh_ready, random_gen +from marvin.lib.cloudstack.zone import Zone +from marvin.lib.cloudstack.network import SecurityGroup,PublicIPAddress,NATRule,FireWallRule,EgressFireWallRule +from marvin.cloudstackAPI import deployVirtualMachine,startVirtualMachine,stopVirtualMachine,rebootVirtualMachine,recoverVirtualMachine,\ + restoreVirtualMachine,resetSSHKeyForVirtualMachine,updateVirtualMachine,destroyVirtualMachine,expungeVirtualMachine,migrateVirtualMachine,\ + attachVolume,detachVolume,addNicToVirtualMachine,removeNicFromVirtualMachine,updateDefaultNicForVirtualMachine,attachIso,detachIso,scaleVirtualMachine,\ + changeServiceForVirtualMachine,listVirtualMachines,resetPasswordForVirtualMachine,assignVirtualMachine,updateVMAffinityGroup,createVMSnapshot,deleteVMSnapshot,\ + listVMSnapshot,createInstanceGroup,deleteInstanceGroup,listInstanceGroups,updateInstanceGroup,revertToVMSnapshot + + +class VirtualMachine: + """Manage virtual machine lifecycle""" + + '''Class level variables''' + # Variables denoting VM state - start + STOPPED = STOPPED + RUNNING = RUNNING + DESTROYED = DESTROYED + EXPUNGING = EXPUNGING + STOPPING = STOPPING + STARTING = STARTING + # Varibles denoting VM state - end + + def __init__(self, items, services): + self.__dict__.update(items) + if "username" in services: + self.username = services["username"] + else: + self.username = 'root' + if "password" in services: + self.password = services["password"] + else: + self.password = 'password' + if "ssh_port" in services: + self.ssh_port = services["ssh_port"] + else: + self.ssh_port = 22 + self.ssh_client = None + # extract out the ipaddress + self.ipaddress = self.nic[0].ipaddress + + @classmethod + def ssh_access_group(cls, apiclient, cmd): + """ + Programs the security group with SSH + access before deploying virtualmachine + @return: + """ + zone_list = Zone.list( + apiclient, + id=cmd.zoneid if cmd.zoneid else None, + domainid=cmd.domainid if cmd.domainid else None + ) + zone = zone_list[0] + # check if security groups settings is enabled for the zone + if zone.securitygroupsenabled: + list_security_groups = SecurityGroup.list( + apiclient, + account=cmd.account, + domainid=cmd.domainid, + listall=True, + securitygroupname="basic_sec_grp" + ) + + if not isinstance(list_security_groups, list): + basic_mode_security_group = SecurityGroup.create( + apiclient, + {"name": "basic_sec_grp"}, + cmd.account, + cmd.domainid, + ) + sec_grp_services = { + "protocol": "TCP", + "startport": 22, + "endport": 22, + "cidrlist": "0.0.0.0/0" + } + # Authorize security group for above ingress rule + basic_mode_security_group.authorize(apiclient, + sec_grp_services, + account=cmd.account, + domainid=cmd.domainid) + else: + basic_mode_security_group = list_security_groups[0] + + if isinstance(cmd.securitygroupids, list): + cmd.securitygroupids.append(basic_mode_security_group.id) + else: + cmd.securitygroupids = [basic_mode_security_group.id] + + @classmethod + def access_ssh_over_nat( + cls, apiclient, services, virtual_machine, allow_egress=False, + networkid=None): + """ + Program NAT and PF rules to open up ssh access to deployed guest + @return: + """ + public_ip = PublicIPAddress.create( + apiclient=apiclient, + accountid=virtual_machine.account, + zoneid=virtual_machine.zoneid, + domainid=virtual_machine.domainid, + services=services, + networkid=networkid + ) + FireWallRule.create( + apiclient=apiclient, + ipaddressid=public_ip.ipaddress.id, + protocol='TCP', + cidrlist=['0.0.0.0/0'], + startport=22, + endport=22 + ) + nat_rule = NATRule.create( + apiclient=apiclient, + virtual_machine=virtual_machine, + services=services, + ipaddressid=public_ip.ipaddress.id + ) + if allow_egress: + try: + EgressFireWallRule.create( + apiclient=apiclient, + networkid=virtual_machine.nic[0].networkid, + protocol='All', + cidrlist='0.0.0.0/0' + ) + except CloudstackAPIException, e: + # This could fail because we've already set up the same rule + if not "There is already a firewall rule specified".lower() in e.errorMsg.lower(): + raise + virtual_machine.ssh_ip = nat_rule.ipaddress + virtual_machine.public_ip = nat_rule.ipaddress + + @classmethod + def create(cls, apiclient, services, templateid=None, accountid=None, + domainid=None, zoneid=None, networkids=None, + serviceofferingid=None, securitygroupids=None, + projectid=None, startvm=None, diskofferingid=None, + affinitygroupnames=None, affinitygroupids=None, group=None, + hostid=None, keypair=None, ipaddress=None, mode='default', + method='GET', hypervisor=None, customcpunumber=None, + customcpuspeed=None, custommemory=None, rootdisksize=None): + """Create the instance""" + + cmd = deployVirtualMachine.deployVirtualMachineCmd() + + if serviceofferingid: + cmd.serviceofferingid = serviceofferingid + elif "serviceoffering" in services: + cmd.serviceofferingid = services["serviceoffering"] + + if zoneid: + cmd.zoneid = zoneid + elif "zoneid" in services: + cmd.zoneid = services["zoneid"] + + if hypervisor: + cmd.hypervisor = hypervisor + + if "displayname" in services: + cmd.displayname = services["displayname"] + + if "name" in services: + cmd.name = services["name"] + + if accountid: + cmd.account = accountid + elif "account" in services: + cmd.account = services["account"] + + if domainid: + cmd.domainid = domainid + elif "domainid" in services: + cmd.domainid = services["domainid"] + + if networkids: + cmd.networkids = networkids + allow_egress = False + elif "networkids" in services: + cmd.networkids = services["networkids"] + allow_egress = False + else: + # When no networkids are passed, network + # is created using the "defaultOfferingWithSourceNAT" + # which has an egress policy of DENY. But guests in tests + # need access to test network connectivity + allow_egress = True + + if templateid: + cmd.templateid = templateid + elif "template" in services: + cmd.templateid = services["template"] + + if diskofferingid: + cmd.diskofferingid = diskofferingid + elif "diskoffering" in services: + cmd.diskofferingid = services["diskoffering"] + + if keypair: + cmd.keypair = keypair + elif "keypair" in services: + cmd.keypair = services["keypair"] + + if ipaddress: + cmd.ipaddress = ipaddress + elif ipaddress in services: + cmd.ipaddress = services["ipaddress"] + + if securitygroupids: + cmd.securitygroupids = [str(sg_id) for sg_id in securitygroupids] + + if "affinitygroupnames" in services: + cmd.affinitygroupnames = services["affinitygroupnames"] + elif affinitygroupnames: + cmd.affinitygroupnames = affinitygroupnames + + if affinitygroupids: + cmd.affinitygroupids = affinitygroupids + + if projectid: + cmd.projectid = projectid + + if startvm is not None: + cmd.startvm = startvm + + if hostid: + cmd.hostid = hostid + + if "userdata" in services: + cmd.userdata = base64.urlsafe_b64encode(services["userdata"]) + + cmd.details = [{}] + + if customcpunumber: + cmd.details[0]["cpuNumber"] = customcpunumber + + if customcpuspeed: + cmd.details[0]["cpuSpeed"] = customcpuspeed + + if custommemory: + cmd.details[0]["memory"] = custommemory + + if rootdisksize >= 0: + cmd.details[0]["rootdisksize"] = rootdisksize + + if group: + cmd.group = group + + # program default access to ssh + if mode.lower() == 'basic': + cls.ssh_access_group(apiclient, cmd) + + virtual_machine = apiclient.deployVirtualMachine(cmd, method=method) + + virtual_machine.ssh_ip = virtual_machine.nic[0].ipaddress + if startvm is False: + virtual_machine.public_ip = virtual_machine.nic[0].ipaddress + return VirtualMachine(virtual_machine.__dict__, services) + + # program ssh access over NAT via PF + if mode.lower() == 'advanced': + cls.access_ssh_over_nat( + apiclient, + services, + virtual_machine, + allow_egress=allow_egress, + networkid=cmd.networkids[0] if cmd.networkids else None) + elif mode.lower() == 'basic': + if virtual_machine.publicip is not None: + # EIP/ELB (netscaler) enabled zone + vm_ssh_ip = virtual_machine.publicip + else: + # regular basic zone with security group + vm_ssh_ip = virtual_machine.nic[0].ipaddress + virtual_machine.ssh_ip = vm_ssh_ip + virtual_machine.public_ip = vm_ssh_ip + + return VirtualMachine(virtual_machine.__dict__, services) + + def start(self, apiclient): + """Start the instance""" + cmd = startVirtualMachine.startVirtualMachineCmd() + cmd.id = self.id + apiclient.startVirtualMachine(cmd) + response = self.getState(apiclient, VirtualMachine.RUNNING) + if response[0] == FAIL: + raise Exception(response[1]) + return + + def stop(self, apiclient, forced=None): + """Stop the instance""" + cmd = stopVirtualMachine.stopVirtualMachineCmd() + cmd.id = self.id + if forced: + cmd.forced = forced + apiclient.stopVirtualMachine(cmd) + response = self.getState(apiclient, VirtualMachine.STOPPED) + if response[0] == FAIL: + raise Exception(response[1]) + return + + def reboot(self, apiclient): + """Reboot the instance""" + cmd = rebootVirtualMachine.rebootVirtualMachineCmd() + cmd.id = self.id + apiclient.rebootVirtualMachine(cmd) + + def recover(self, apiclient): + """Recover the instance""" + cmd = recoverVirtualMachine.recoverVirtualMachineCmd() + cmd.id = self.id + apiclient.recoverVirtualMachine(cmd) + + def restore(self, apiclient, templateid=None): + """Restore the instance""" + cmd = restoreVirtualMachine.restoreVirtualMachineCmd() + cmd.virtualmachineid = self.id + if templateid: + cmd.templateid = templateid + return apiclient.restoreVirtualMachine(cmd) + + def get_ssh_client( + self, ipaddress=None, reconnect=False, port=None, + keyPairFileLocation=None): + """Get SSH object of VM""" + + # If NAT Rules are not created while VM deployment in Advanced mode + # then, IP address must be passed + if ipaddress is not None: + self.ssh_ip = ipaddress + if port: + self.ssh_port = port + + if keyPairFileLocation is not None: + self.password = None + + if reconnect: + self.ssh_client = is_server_ssh_ready( + self.ssh_ip, + self.ssh_port, + self.username, + self.password, + keyPairFileLocation=keyPairFileLocation + ) + self.ssh_client = self.ssh_client or is_server_ssh_ready( + self.ssh_ip, + self.ssh_port, + self.username, + self.password, + keyPairFileLocation=keyPairFileLocation + ) + return self.ssh_client + + def getState(self, apiclient, state, timeout=600): + """List VM and check if its state is as expected + @returnValue - List[Result, Reason] + 1) Result - FAIL if there is any exception + in the operation or VM state does not change + to expected state in given time else PASS + 2) Reason - Reason for failure""" + + returnValue = [FAIL, "VM state not trasited to %s,\ + operation timed out" % state] + + while timeout > 0: + try: + projectid = None + if hasattr(self, "projectid"): + projectid = self.projectid + vms = VirtualMachine.list(apiclient, projectid=projectid, + id=self.id, listAll=True) + validationresult = validateList(vms) + if validationresult[0] == FAIL: + raise Exception("VM list validation failed: %s" % validationresult[2]) + elif str(vms[0].state).lower().decode("string_escape") == str(state).lower(): + returnValue = [PASS, None] + break + except Exception as e: + returnValue = [FAIL, e] + break + time.sleep(60) + timeout -= 60 + return returnValue + + def resetSshKey(self, apiclient, **kwargs): + """Resets SSH key""" + + cmd = resetSSHKeyForVirtualMachine.resetSSHKeyForVirtualMachineCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.resetSSHKeyForVirtualMachine(cmd)) + + def update(self, apiclient, **kwargs): + """Updates the VM data""" + + cmd = updateVirtualMachine.updateVirtualMachineCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.updateVirtualMachine(cmd)) + + def delete(self, apiclient, expunge=True, **kwargs): + """Destroy an Instance""" + cmd = destroyVirtualMachine.destroyVirtualMachineCmd() + cmd.id = self.id + cmd.expunge = expunge + [setattr(cmd, k, v) for k, v in kwargs.items()] + apiclient.destroyVirtualMachine(cmd) + + def expunge(self, apiclient): + """Expunge an Instance""" + cmd = expungeVirtualMachine.expungeVirtualMachineCmd() + cmd.id = self.id + apiclient.expungeVirtualMachine(cmd) + + def migrate(self, apiclient, hostid=None): + """migrate an Instance""" + cmd = migrateVirtualMachine.migrateVirtualMachineCmd() + cmd.virtualmachineid = self.id + if hostid: + cmd.hostid = hostid + apiclient.migrateVirtualMachine(cmd) + + def attach_volume(self, apiclient, volume): + """Attach volume to instance""" + cmd = attachVolume.attachVolumeCmd() + cmd.id = volume.id + cmd.virtualmachineid = self.id + return apiclient.attachVolume(cmd) + + def detach_volume(self, apiclient, volume): + """Detach volume to instance""" + cmd = detachVolume.detachVolumeCmd() + cmd.id = volume.id + return apiclient.detachVolume(cmd) + + def add_nic(self, apiclient, networkId, ipaddress=None): + """Add a NIC to a VM""" + cmd = addNicToVirtualMachine.addNicToVirtualMachineCmd() + cmd.virtualmachineid = self.id + cmd.networkid = networkId + + if ipaddress: + cmd.ipaddress = ipaddress + + return apiclient.addNicToVirtualMachine(cmd) + + def remove_nic(self, apiclient, nicId): + """Remove a NIC to a VM""" + cmd = removeNicFromVirtualMachine.removeNicFromVirtualMachineCmd() + cmd.nicid = nicId + cmd.virtualmachineid = self.id + return apiclient.removeNicFromVirtualMachine(cmd) + + def update_default_nic(self, apiclient, nicId): + """Set a NIC to be the default network adapter for a VM""" + cmd = updateDefaultNicForVirtualMachine.\ + updateDefaultNicForVirtualMachineCmd() + cmd.nicid = nicId + cmd.virtualmachineid = self.id + return apiclient.updateDefaultNicForVirtualMachine(cmd) + + def attach_iso(self, apiclient, iso): + """Attach ISO to instance""" + cmd = attachIso.attachIsoCmd() + cmd.id = iso.id + cmd.virtualmachineid = self.id + return apiclient.attachIso(cmd) + + def detach_iso(self, apiclient): + """Detach ISO to instance""" + cmd = detachIso.detachIsoCmd() + cmd.virtualmachineid = self.id + return apiclient.detachIso(cmd) + + def scale_virtualmachine(self, apiclient, serviceOfferingId): + """ Scale up of service offering for the Instance""" + cmd = scaleVirtualMachine.scaleVirtualMachineCmd() + cmd.id = self.id + cmd.serviceofferingid = serviceOfferingId + return apiclient.scaleVirtualMachine(cmd) + + def change_service_offering(self, apiclient, serviceOfferingId): + """Change service offering of the instance""" + cmd = changeServiceForVirtualMachine.\ + changeServiceForVirtualMachineCmd() + cmd.id = self.id + cmd.serviceofferingid = serviceOfferingId + return apiclient.changeServiceForVirtualMachine(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all VMs matching criteria""" + + cmd = listVirtualMachines.listVirtualMachinesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listVirtualMachines(cmd)) + + def resetPassword(self, apiclient): + """Resets VM password if VM created using password enabled template""" + + cmd = resetPasswordForVirtualMachine.\ + resetPasswordForVirtualMachineCmd() + cmd.id = self.id + try: + response = apiclient.resetPasswordForVirtualMachine(cmd) + except Exception as e: + raise Exception("Reset Password failed! - %s" % e) + if response is not None: + return response.password + + def assign_virtual_machine(self, apiclient, account, domainid): + """Move a user VM to another user under same domain.""" + + cmd = assignVirtualMachine.assignVirtualMachineCmd() + cmd.virtualmachineid = self.id + cmd.account = account + cmd.domainid = domainid + try: + response = apiclient.assignVirtualMachine(cmd) + return response + except Exception as e: + raise Exception("assignVirtualMachine failed - %s" % e) + + def update_affinity_group(self, apiclient, affinitygroupids=None, + affinitygroupnames=None): + """Update affinity group of a VM""" + cmd = updateVMAffinityGroup.updateVMAffinityGroupCmd() + cmd.id = self.id + + if affinitygroupids: + cmd.affinitygroupids = affinitygroupids + + if affinitygroupnames: + cmd.affinitygroupnames = affinitygroupnames + + return apiclient.updateVMAffinityGroup(cmd) + + def scale(self, apiclient, serviceOfferingId, + customcpunumber=None, customcpuspeed=None, custommemory=None): + """Change service offering of the instance""" + cmd = scaleVirtualMachine.scaleVirtualMachineCmd() + cmd.id = self.id + cmd.serviceofferingid = serviceOfferingId + cmd.details = [{"cpuNumber": "", "cpuSpeed": "", "memory": ""}] + if customcpunumber: + cmd.details[0]["cpuNumber"] = customcpunumber + if customcpuspeed: + cmd.details[0]["cpuSpeed"] = customcpuspeed + if custommemory: + cmd.details[0]["memory"] = custommemory + return apiclient.scaleVirtualMachine(cmd) + + +class VmSnapshot: + """Manage VM Snapshot life cycle""" + def __init__(self, items): + self.__dict__.update(items) + @classmethod + def create(cls, apiclient, vmid, snapshotmemory="false", + name=None, description=None): + cmd = createVMSnapshot.createVMSnapshotCmd() + cmd.virtualmachineid = vmid + + if snapshotmemory: + cmd.snapshotmemory = snapshotmemory + if name: + cmd.name = name + if description: + cmd.description = description + return VmSnapshot(apiclient.createVMSnapshot(cmd).__dict__) + + @classmethod + def list(cls, apiclient, **kwargs): + cmd = listVMSnapshot.listVMSnapshotCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listVMSnapshot(cmd)) + + @classmethod + def revertToSnapshot(cls, apiclient, vmsnapshotid): + cmd = revertToVMSnapshot.revertToVMSnapshotCmd() + cmd.vmsnapshotid = vmsnapshotid + return apiclient.revertToVMSnapshot(cmd) + + @classmethod + def deleteVMSnapshot(cls, apiclient, vmsnapshotid): + cmd = deleteVMSnapshot.deleteVMSnapshotCmd() + cmd.vmsnapshotid = vmsnapshotid + return apiclient.deleteVMSnapshot(cmd) + + +class InstanceGroup: + """Manage VM instance groups""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, name=None, account=None, domainid=None, + projectid=None, networkid=None, rand_name=True): + """Creates instance groups""" + + cmd = createInstanceGroup.createInstanceGroupCmd() + cmd.name = "-".join([name, random_gen()]) if rand_name else name + if account is not None: + cmd.account = account + if domainid is not None: + cmd.domainid = domainid + if projectid is not None: + cmd.projectid = projectid + if networkid is not None: + cmd.networkid = networkid + return InstanceGroup(apiclient.createInstanceGroup(cmd).__dict__) + + def delete(self, apiclient): + """Delete instance group""" + cmd = deleteInstanceGroup.deleteInstanceGroupCmd() + cmd.id = self.id + apiclient.deleteInstanceGroup(cmd) + + def update(self, apiclient, **kwargs): + """Updates the instance groups""" + cmd = updateInstanceGroup.updateInstanceGroupCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return (apiclient.updateInstanceGroup(cmd)) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all instance groups""" + cmd = listInstanceGroups.listInstanceGroupsCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return (apiclient.listInstanceGroups(cmd)) + + def startInstances(self, apiclient): + """Starts all instances in a VM tier""" + + cmd = startVirtualMachine.startVirtualMachineCmd() + cmd.group = self.id + return apiclient.startVirtualMachine(cmd) + + def stopInstances(self, apiclient): + """Stops all instances in a VM tier""" + + cmd = stopVirtualMachine.stopVirtualMachineCmd() + cmd.group = self.id + return apiclient.stopVirtualMachine(cmd) + + def rebootInstances(self, apiclient): + """Reboot all instances in a VM tier""" + + cmd = rebootVirtualMachine.rebootVirtualMachineCmd() + cmd.group = self.id + return apiclient.rebootVirtualMachine(cmd) + + def deleteInstances(self, apiclient): + """Stops all instances in a VM tier""" + + cmd = destroyVirtualMachine.destroyVirtualMachineCmd() + cmd.group = self.id + return apiclient.destroyVirtualMachine(cmd) + + def changeServiceOffering(self, apiclient, serviceOfferingId): + """Change service offering of the vm tier""" + + cmd = changeServiceForVirtualMachine.\ + changeServiceForVirtualMachineCmd() + cmd.group = self.id + cmd.serviceofferingid = serviceOfferingId + return apiclient.changeServiceForVirtualMachine(cmd) + + def recoverInstances(self, apiclient): + """Recover the instances from vm tier""" + cmd = recoverVirtualMachine.recoverVirtualMachineCmd() + cmd.group = self.id + apiclient.recoverVirtualMachine(cmd) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/volume.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/volume.py b/tools/marvin/marvin/lib/cloudstack/volume.py new file mode 100644 index 0000000..be73c7a --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/volume.py @@ -0,0 +1,198 @@ +# 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. +import time + +from marvin.cloudstackAPI import createVolume,deleteVolume,listVolumes,resizeVolume,uploadVolume,extractVolume,migrateVolume +from marvin.lib.cloudstack.utils import random_gen + + +class Volume: + """Manage Volume Life cycle + """ + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, zoneid=None, account=None, + domainid=None, diskofferingid=None, projectid=None): + """Create Volume""" + cmd = createVolume.createVolumeCmd() + cmd.name = services["diskname"] + + if diskofferingid: + cmd.diskofferingid = diskofferingid + elif "diskofferingid" in services: + cmd.diskofferingid = services["diskofferingid"] + + if zoneid: + cmd.zoneid = zoneid + elif "zoneid" in services: + cmd.zoneid = services["zoneid"] + + if account: + cmd.account = account + elif "account" in services: + cmd.account = services["account"] + + if domainid: + cmd.domainid = domainid + elif "domainid" in services: + cmd.domainid = services["domainid"] + + if projectid: + cmd.projectid = projectid + return Volume(apiclient.createVolume(cmd).__dict__) + + @classmethod + def create_custom_disk(cls, apiclient, services, account=None, + domainid=None, diskofferingid=None): + """Create Volume from Custom disk offering""" + cmd = createVolume.createVolumeCmd() + cmd.name = services["diskname"] + + if diskofferingid: + cmd.diskofferingid = diskofferingid + elif "customdiskofferingid" in services: + cmd.diskofferingid = services["customdiskofferingid"] + + cmd.size = services["customdisksize"] + cmd.zoneid = services["zoneid"] + + if account: + cmd.account = account + else: + cmd.account = services["account"] + + if domainid: + cmd.domainid = domainid + else: + cmd.domainid = services["domainid"] + + return Volume(apiclient.createVolume(cmd).__dict__) + + @classmethod + def create_from_snapshot(cls, apiclient, snapshot_id, services, + account=None, domainid=None): + """Create Volume from snapshot""" + cmd = createVolume.createVolumeCmd() + cmd.name = "-".join([services["diskname"], random_gen()]) + cmd.snapshotid = snapshot_id + cmd.zoneid = services["zoneid"] + cmd.size = services["size"] + if account: + cmd.account = account + else: + cmd.account = services["account"] + if domainid: + cmd.domainid = domainid + else: + cmd.domainid = services["domainid"] + return Volume(apiclient.createVolume(cmd).__dict__) + + def delete(self, apiclient): + """Delete Volume""" + cmd = deleteVolume.deleteVolumeCmd() + cmd.id = self.id + apiclient.deleteVolume(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all volumes matching criteria""" + + cmd = listVolumes.listVolumesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listVolumes(cmd)) + + def resize(self, apiclient, **kwargs): + """Resize a volume""" + cmd = resizeVolume.resizeVolumeCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.resizeVolume(cmd)) + + @classmethod + def upload(cls, apiclient, services, zoneid=None, + account=None, domainid=None, url=None): + """Uploads the volume to specified account""" + + cmd = uploadVolume.uploadVolumeCmd() + if zoneid: + cmd.zoneid = zoneid + if account: + cmd.account = account + if domainid: + cmd.domainid = domainid + cmd.format = services["format"] + cmd.name = services["diskname"] + if url: + cmd.url = url + else: + cmd.url = services["url"] + return Volume(apiclient.uploadVolume(cmd).__dict__) + + def wait_for_upload(self, apiclient, timeout=10, interval=60): + """Wait for upload""" + # Sleep to ensure template is in proper state before download + time.sleep(interval) + + while True: + volume_response = Volume.list( + apiclient, + id=self.id, + zoneid=self.zoneid, + ) + if isinstance(volume_response, list): + + volume = volume_response[0] + # If volume is ready, + # volume.state = Allocated + if volume.state == 'Uploaded': + break + + elif 'Uploading' in volume.state: + time.sleep(interval) + + elif 'Installing' not in volume.state: + raise Exception( + "Error in uploading volume: status - %s" % + volume.state) + elif timeout == 0: + break + + else: + time.sleep(interval) + timeout = timeout - 1 + return + + @classmethod + def extract(cls, apiclient, volume_id, zoneid, mode): + """Extracts the volume""" + + cmd = extractVolume.extractVolumeCmd() + cmd.id = volume_id + cmd.zoneid = zoneid + cmd.mode = mode + return Volume(apiclient.extractVolume(cmd).__dict__) + + @classmethod + def migrate(cls, apiclient, **kwargs): + """Migrate a volume""" + cmd = migrateVolume.migrateVolumeCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + return(apiclient.migrateVolume(cmd)) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/d002c52a/tools/marvin/marvin/lib/cloudstack/zone.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/lib/cloudstack/zone.py b/tools/marvin/marvin/lib/cloudstack/zone.py new file mode 100644 index 0000000..614fa50 --- /dev/null +++ b/tools/marvin/marvin/lib/cloudstack/zone.py @@ -0,0 +1,67 @@ +# 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. +from marvin.cloudstackAPI import createZone,deleteZone,updateZone,listZones +class Zone: + """Manage Zone""" + + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, domainid=None): + """Create zone""" + cmd = createZone.createZoneCmd() + cmd.dns1 = services["dns1"] + cmd.internaldns1 = services["internaldns1"] + cmd.name = services["name"] + cmd.networktype = services["networktype"] + + if "dns2" in services: + cmd.dns2 = services["dns2"] + if "internaldns2" in services: + cmd.internaldns2 = services["internaldns2"] + if domainid: + cmd.domainid = domainid + if "securitygroupenabled" in services: + cmd.securitygroupenabled = services["securitygroupenabled"] + + return Zone(apiclient.createZone(cmd).__dict__) + + def delete(self, apiclient): + """Delete Zone""" + + cmd = deleteZone.deleteZoneCmd() + cmd.id = self.id + apiclient.deleteZone(cmd) + + def update(self, apiclient, **kwargs): + """Update the zone""" + + cmd = updateZone.updateZoneCmd() + cmd.id = self.id + [setattr(cmd, k, v) for k, v in kwargs.items()] + return apiclient.updateZone(cmd) + + @classmethod + def list(cls, apiclient, **kwargs): + """List all Zones matching criteria""" + + cmd = listZones.listZonesCmd() + [setattr(cmd, k, v) for k, v in kwargs.items()] + if 'account' in kwargs.keys() and 'domainid' in kwargs.keys(): + cmd.listall = True + return(apiclient.listZones(cmd))
