class CloudStackApiClient: _MAX_RETRY = 180 _ASYNC_INTERVAL = 5 _INSTANCE = None @classmethod def get_instance(cls, zoneid, debug=False): if cls._INSTANCE is None or ( cls._INSTANCE and cls._INSTANCE.zoneid != zoneid ): cls._INSTANCE = cls(zoneid, debug) return cls._INSTANCE def __init__(self, zoneid, debug): self.zoneid = zoneid self._debug = debug self._config = read_config() self._cs = CloudStack(**self._config) self._vms = {} self._zones = {} self._lbs = {} self._templates = {} self._offerings = {} self._networks = {} def _print_debug(self, text): if self._debug: print_debug(text) def _dump(self, data=None): if self._debug and data: print_debug(json.dumps(data, sort_keys=True, indent=2)) def queryAsyncJobResult(self, jobid): for i in range(0, self._MAX_RETRY): job = self._cs.queryAsyncJobResult(jobid=jobid) if job['jobstatus'] == 1: self._dump(job) return job self._print_debug("wait for job: {} times".format(i + 1)) time.sleep(self._ASYNC_INTERVAL) return None def _list(self, api_name, params): results = getattr(self._cs, api_name)(**params) self._dump(results) section = re.sub('^list(.+)s$', lambda m: m.group(1), api_name).lower() if section in results: return results[section] else: return [] def listZones(self): return self._list("listZones", { "zoneid": self.zoneid }) def listLoadBalancerRules(self): return self._list("listLoadBalancerRules", { "zoneid": self.zoneid }) def listNetworks(self): return self._list("listNetworks", { "zoneid": self.zoneid }) def listTemplates(self): return self._list("listTemplates", { "zoneid": self.zoneid, "templatefilter": "self" }) def listServiceOfferings(self): return self._list("listServiceOfferings", {}) def listVirtualMachines(self): return self._list("listVirtualMachines", { "zoneid": self.zoneid }) def deployVirtualMachine(self, name, templateid, offeringid, networkids): result = self._cs.deployVirtualMachine( zoneid=self.zoneid, serviceofferingid=offeringid, templateid=templateid, networkids=",".join(networkids), name=name, displayname=name ) job = self.queryAsyncJobResult(result['jobid']) if job is None or "jobresult" not in job or "virtualmachine" not in job["jobresult"]: return None uuid = job['jobresult']['virtualmachine']['id'] self._vms[uuid] = { 'uuid': uuid, 'name': name } return self._vms[uuid] def destroyVirtualMachine(self, vmid): result = self._cs.destroyVirtualMachine(id=vmid, expunge=True) self.queryAsyncJobResult(result['jobid']) def assignToLoadBalancerRule(self, lbid, vmid): result = self._cs.assignToLoadBalancerRule(id=lbid, virtualmachineids=vmid) self.queryAsyncJobResult(result['jobid']) def removeFromLoadBalancerRule(self, lbid, vmid): result = self._cs.removeFromLoadBalancerRule(id=lbid, virtualmachineids=vmid) self.queryAsyncJobResult(result['jobid']) def _get_data(self, uuid, cache, method, params, force=False): if uuid not in cache or force: cache = {} for x in getattr(self, method)(): cache[x['id']] = { p[0]:x[p[1]] for p in params } if uuid in cache: return cache[uuid] return None def get_vm_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._vms, method="listVirtualMachines", params=(('name', 'name'), ('uuid', 'id')), force=force ) def get_zone_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._zones, method="listZones", params=(('name', 'name'), ('uuid', 'id')), force=force ) def get_lb_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._lbs, method="listLoadBalancerRules", params=(('name', 'name'), ('uuid', 'id')), force=force ) def get_network_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._networks, method="listNetworks", params=(('name', 'name'), ('uuid', 'id')), force=force ) def get_template_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._templates, method="listTemplates", params=(('name', 'name'), ('uuid', 'id')), force=force ) def get_offering_data(self, uuid, force=False): return self._get_data( uuid=uuid, cache=self._offerings, method="listServiceOfferings", params=(('name', 'name'), ('uuid', 'id')), force=force ) def create_vm(self, name, lbid, templateid, offeringid, networkids): vm = self.deployVirtualMachine(name, templateid, offeringid, networkids) if vm: self.assignToLoadBalancerRule(lbid, vm['uuid']) return vm def remove_vm(self, vmid, lbid): self.removeFromLoadBalancerRule(lbid, vmid) self.destroyVirtualMachine(vmid)
class CloudstackCloudAdapter(CloudAdapter): def __init__(self): super().__init__() self.cs = CloudStack( endpoint=os.getenv("CLOUDSTACK_API_ENDPOINT"), key=os.getenv("CLOUDSTACK_API_KEY"), secret=os.getenv("CLOUDSTACK_API_SECRET"), ) def get_service_offering(self, name) -> dict: res = self.cs.listServiceOfferings(name=name) if not res: raise Exception(f"Error: Service offering not found: {name}") return res["serviceoffering"][0] def get_zone(self, name) -> dict: res = self.cs.listZones(name=name) if not res: raise Exception(f"Error: Zone not found: {name}") return res["zone"][0] def get_template(self, name) -> dict: for tf in ["community", "self"]: res = self.cs.listTemplates(name=name, templatefilter=tf) if res: break else: raise Exception(f"Error: Template not found: {name}") return res["template"][0] def get_params(self, name: str) -> dict: user_data = self.launch.get("user_data") if user_data: user_data = base64.b64encode(user_data.encode("utf-8")) return { "displayname": name, "serviceofferingid": self.get_service_offering( name=self.launch["service_offering"] ).get("id"), "affinitygroupnames": self.launch.get("affinity_groups"), "securitygroupnames": self.launch.get("security_groups"), "templateid": self.get_template(name=self.launch["template"]).get("id"), "zoneid": self.get_zone(name=self.launch["zone"]).get("id"), "userdata": user_data, "keypair": self.launch.get("ssh_key"), "group": self.launch.get("group"), "rootdisksize": self.launch.get("root_disk_size"), } def get_current_instances(self) -> List[GenericCloudInstance]: filter_tag = f"scalr={self.filter}" log.info(f"cloudstack: Querying with filter_tag: {filter_tag}") servers = self.cs.listVirtualMachines( tags=[ { "key": "scalr", "value": self.filter, } ], fetch_list=True, ) return [ GenericCloudInstance( id=server["id"], name=server["name"], status=server["state"].lower(), ) for server in sorted(servers, key=lambda i: i["created"]) ] def ensure_instances_running(self): for server in self.get_current_instances(): log.info(f"cloudstack: Server {server.name} status {server.status}") if server.status in ["stopping", "stopped"]: self.cs.startVirtualMachine(id=server.id) log.info(f"cloudstack: Server {server.name} started") def deploy_instance(self, name: str): params = self.get_params(name=name) tags = self.launch.get("tags", {}) tags = [ { "key": "scalr", "value": self.filter, } ] server = self.cs.deployVirtualMachine(**params) self.cs.createTags( resourceids=[ server["id"], ], resourcetype="UserVm", tags=tags, ) def destroy_instance(self, instance: GenericCloudInstance): log.info(f"cloudstack: Destroying instance {instance}") self.cs.destroyVirtualMachine(id=instance.id)
from cs import CloudStack import json import time import base64 cs = CloudStack(endpoint='https://api.exoscale.ch/v1', key='EXOf89522678165c544107d1099', secret='JgfCHbX_6cS-I3xoetLEXgHfHlJQe7e2Vlv0SC6XpCc') print("Deploying Database") #database db = cs.deployVirtualMachine( serviceofferingid="21624abb-764e-4def-81d7-9fc54b5957fb", templateid="2e9be9bf-558d-4a0b-8e6d-03695fe93555", zoneid="1128bd56-b4d9-4ac6-a7b9-c715b187ce11", keypair="CloudSys", securitygroupids=[ "83e73717-774f-4361-b8c1-a28158dd09c1", "21714f20-3e37-4e45-93da-b515ecdfdc03" ]) databaseID = db["id"] jobID = db["jobid"] #wait for job while True: res = cs.queryAsyncJobResult(jobid=jobID) if (res["jobstatus"] == 1): break time.sleep(1) allVM = cs.listVirtualMachines()