diff --git a/backend/tomato/api/elements.py b/backend/tomato/api/elements.py index 86fa539a8..63d394160 100644 --- a/backend/tomato/api/elements.py +++ b/backend/tomato/api/elements.py @@ -298,6 +298,7 @@ def element_usage(id): #@ReservedAssignment """ el = _getElement(id) return el.totalUsage.info() + from .. import elements, currentUser from topology import _getTopology diff --git a/backend/tomato/api/host.py b/backend/tomato/api/host.py index c455f7d79..b7fcd6a3d 100644 --- a/backend/tomato/api/host.py +++ b/backend/tomato/api/host.py @@ -169,6 +169,16 @@ def host_modify(name, attrs): host_list.invalidate() return h.info() +@checkauth +def host_deactivate(name): + """ + undocumented + """ + h = _getHost(name) + h.deactivate() + return h.info() + + @checkauth def host_remove(name): """ @@ -178,6 +188,7 @@ def host_remove(name): h.remove() host_list.invalidate() + @checkauth def host_users(name): """ diff --git a/backend/tomato/config.py b/backend/tomato/config.py index e200bd13b..1a78f48e6 100644 --- a/backend/tomato/config.py +++ b/backend/tomato/config.py @@ -178,4 +178,11 @@ A new user, %s, has just registered at the ToMaTo testbed.\n\ You can review all pending user registrations at https://master.tomato-lab.org/account/registrations\n\n\ Best Wishes,\nThe ToMaTo Testbed" -} \ No newline at end of file +} + + + +# Load Balancing, Migration, Resource Allocator: +MIGRATION_TRESHOLD = 1.2; #New Host has to be 20% better +AVG_MINIMUM = 0.25; +AVG_MAXIMUM = 0.7; diff --git a/backend/tomato/elements/__init__.py b/backend/tomato/elements/__init__.py index 44035b5d3..62433d0e4 100644 --- a/backend/tomato/elements/__init__.py +++ b/backend/tomato/elements/__init__.py @@ -468,6 +468,8 @@ def fetchInfo(self): mel = self.mainElement() if mel: mel.updateInfo() + + def updateUsage(self): self.totalUsage.updateFrom([el.usageStatistics for el in self.getHostElements()] diff --git a/backend/tomato/elements/external_network.py b/backend/tomato/elements/external_network.py index ee53f4e8a..640090f12 100644 --- a/backend/tomato/elements/external_network.py +++ b/backend/tomato/elements/external_network.py @@ -19,10 +19,11 @@ from .. import elements, resources, host from ..resources import network as r_network from ..lib.attributes import Attr #@UnresolvedImport -from generic import ST_CREATED, ST_STARTED +from generic import ST_CREATED, ST_STARTED, ST_PREPARED from .. import currentUser from ..auth import Flags from ..lib.error import UserError +from tomato.config import MIGRATION_TRESHOLD class External_Network(elements.generic.ConnectingElement, elements.Element): name_attr = Attr("name", desc="Name") @@ -36,6 +37,7 @@ class External_Network(elements.generic.ConnectingElement, elements.Element): CUSTOM_ACTIONS = { "start": [ST_CREATED], "stop": [ST_STARTED], + "migrate": [ST_PREPARED], elements.REMOVE_ACTION: [ST_CREATED], } CUSTOM_ATTRS = { @@ -192,6 +194,42 @@ def action_stop(self): self.element = None self.setState(ST_CREATED, True) + def checkMigrate(self): + #We only migrate if the external network is already started, otherwise we don't have to migrate + return self.state in [ST_PREPARED] + + def try_migrate(self): + + UserError.check(self.element.checkMigrate(), code=UserError.UNSUPPORTED_ACTION, message="Element can't be migrated",data={"type": self.TYPE}) + + + hPref, sPref = self.getLocationPrefs() + + bestHost,bestPref = host.getBestHost(site=self.site, elementTypes=[self.TYPE]+self.CAP_CHILDREN.keys(), hostPrefs=hPref, sitePrefs=sPref) + UserError.check(bestHost, code=UserError.NO_RESOURCES, message="No matching host found for element", data={"type": self.TYPE}) + + + hostScore = host.getHostScore(self.element.host,site=self.site, elementTypes=[self.TYPE]+self.CAP_CHILDREN.keys(), hostPrefs=hPref, sitePrefs=sPref) + + if bestPref > hostScore*MIGRATION_TRESHOLD: + return + + + + kind = self.getParent().network.kind if self.parent and self.getParent().samenet else self.kind + + if self.parent and self.getParent().samenet: + self.network = r_network.getInstance(host, self.getParent().network.kind) + else: + self.network = r_network.getInstance(host, self.kind) + attrs = {"network": self.network.network.kind} + + self.action_destroy() + self.element = bestHost.createElement("external_network", parent=None, attrs=attrs, ownerElement=self) + self.setState(ST_STARTED) + self.triggerConnectionStart() + + def readyToConnect(self): return bool(self.element) diff --git a/backend/tomato/elements/generic.py b/backend/tomato/elements/generic.py index 385db34dc..3231a2212 100644 --- a/backend/tomato/elements/generic.py +++ b/backend/tomato/elements/generic.py @@ -14,14 +14,16 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see - from django.db import models from .. import elements, resources, host from ..resources import profile as r_profile, template as r_template from ..lib.attributes import Attr, attribute #@UnresolvedImport -from ..lib import attributes #@UnresolvedImport +from ..lib import attributes,rpc #@UnresolvedImport from ..lib.error import UserError, InternalError +from ..lib.util import upload import time +import urllib +from tomato.config import MIGRATION_TRESHOLD ST_CREATED = "created" ST_PREPARED = "prepared" @@ -47,6 +49,7 @@ class VMElement(elements.Element): "prepare": [ST_CREATED], "destroy": [ST_PREPARED], "change_template": [ST_CREATED, ST_PREPARED], + "migrate": [ST_PREPARED], elements.REMOVE_ACTION: [ST_CREATED], } CUSTOM_ATTRS = { @@ -237,6 +240,47 @@ def after_start(self): def after_upload_use(self): self.custom_template = True self.save() + + def try_migrate(self): + + + if self.state in [ST_CREATED]: return + + hPref, sPref = self.getLocationPrefs() + + bestHost,bestPref = host.getBestHost(site=self.site, elementTypes=[self.TYPE]+self.CAP_CHILDREN.keys(), hostPrefs=hPref, sitePrefs=sPref) + UserError.check(bestHost, code=UserError.NO_RESOURCES, message="No matching host found for element", data={"type": self.TYPE}) + + hostScore = host.getHostScore(self.element.host,site=self.site, elementTypes=[self.TYPE]+self.CAP_CHILDREN.keys(), hostPrefs=hPref, sitePrefs=sPref) + + if bestPref > hostScore*MIGRATION_TRESHOLD: + return + + + #Download template. Receive download_grant from template and save it to a tempfile? + urllib.urlretrieve(self.element.host.grantUrl(self.element.action("download_grant"), "download"), self.name+"_tmp_image.tar.gz") + + attrs = self._remoteAttrs() + #Create identical element on new host + new_el = bestHost.createElement(self.TYPE, parent=None, attrs=attrs, ownerElement=self) + + #Kill old element on old host + self.action_destroy() + + #Set new element and save it + self.element = new_el + self.save() + + for iface in self.getChildren(): + iface.try_migrate() + self.element.action("prepare") + + self.setState(ST_PREPARED, True) + + upload(bestHost.grantUrl(new_el.action("upload_grant"),"upload"),self.name+"_tmp_image.tar.gz") + new_el.action("upload_use") + + def upcast(self): return self @@ -297,13 +341,31 @@ def _remove(self): self.element = None self.save() + def checkMigrate(self): + return self.state in [ST_CREATED,ST_PREPARED] + + def try_migrate(self): + + if self.state in [ST_CREATED,ST_STARTED]: return + + + + parEl = self.getParent().element + assert parEl + attrs = self._remoteAttrs() + element = parEl.createChild(self.TYPE, attrs=attrs, ownerElement=self) + + self.element.remove() + self.element = element + self.save() + def readyToConnect(self): return self.state == ST_STARTED def upcast(self): return self - + class ConnectingElement: def getLocationData(self, maxDepth=3): """ diff --git a/backend/tomato/elements/repy.py b/backend/tomato/elements/repy.py index 0283d0880..894203c74 100644 --- a/backend/tomato/elements/repy.py +++ b/backend/tomato/elements/repy.py @@ -58,6 +58,8 @@ def action_destroy(self): self.element = None self.setState(generic.ST_CREATED, True) + + class Repy_Interface(generic.VMInterface): TYPE = "repy_interface" diff --git a/backend/tomato/elements/tinc.py b/backend/tomato/elements/tinc.py index 6a56a50db..30ec0d12a 100644 --- a/backend/tomato/elements/tinc.py +++ b/backend/tomato/elements/tinc.py @@ -22,6 +22,7 @@ from ..lib.attributes import Attr #@UnresolvedImport from generic import ST_CREATED, ST_PREPARED, ST_STARTED from ..lib.error import UserError, assert_ +from tomato.config import MIGRATION_TRESHOLD class Tinc_VPN(elements.generic.ConnectingElement, elements.Element): name_attr = Attr("name", desc="Name", type="str") @@ -34,6 +35,7 @@ class Tinc_VPN(elements.generic.ConnectingElement, elements.Element): "destroy": [ST_PREPARED], "start": [ST_PREPARED], "stop": [ST_STARTED], + "migrate": [ST_PREPARED,ST_STARTED], elements.REMOVE_ACTION: [ST_CREATED], } CUSTOM_ATTRS = { @@ -225,6 +227,31 @@ def action_start(self): self._parallelChildActions(self._childsByState()[ST_PREPARED], "start") self.setState(ST_STARTED) + + def checkMigrate(self): + return self._childsByState()[ST_PREPARED] != [] or self._childsByState()[ST_CREATED] != [] + + def try_migrate(self): + + childList = self._childsByState()[ST_PREPARED] + lock = threading.RLock() + class WorkerThread(threading.Thread): + def run(self): + while True: + with lock: + if not childList: + return + ch = childList.pop() + ch.try_migrate() + threads = [] + for _ in xrange(0, min(len(childList), 10)): + thread = WorkerThread() + threads.append(thread) + thread.start() + for thread in threads: + thread.join() + + def upcast(self): return self @@ -249,6 +276,7 @@ class Tinc_Endpoint(elements.generic.ConnectingElement, elements.Element): "stop": [ST_STARTED], "prepare": [ST_CREATED], "destroy": [ST_PREPARED], + "migrate": [ST_PREPARED,ST_STARTED], elements.REMOVE_ACTION: [ST_CREATED], } CUSTOM_ATTRS = { @@ -335,6 +363,30 @@ def action_stop(self): self.element.action("stop") self.setState(ST_PREPARED, True) + def checkMigrate(self): + return self.state in [ST_CREATED,ST_PREPARED] + + def try_migrate(self): + + if self.state in [ST_CREATED]: return + + hPref, sPref = self.getLocationPrefs() + + + bestHost,bestPref = host.getBestHost(elementTypes=["tinc"], hostPrefs=hPref, sitePrefs=sPref) + UserError.check(bestHost, code=UserError.NO_RESOURCES, message="No matching host found for element", data={"type": self.TYPE}) + + hostScore = host.getHostScore(self.element.host,elementTypes=["tinc"], hostPrefs=hPref, sitePrefs=sPref) + + if bestPref > hostScore*MIGRATION_TRESHOLD: + return + + + self.action_destroy() + self.element = bestHost.createElement(self.remoteType(), parent=None, attrs=attrs, ownerElement=self) + self.save() + self.setState(ST_PREPARED, True) + def upcast(self): return self diff --git a/backend/tomato/elements/udp.py b/backend/tomato/elements/udp.py index 0431d8be3..66011fabe 100644 --- a/backend/tomato/elements/udp.py +++ b/backend/tomato/elements/udp.py @@ -20,6 +20,8 @@ from ..lib.attributes import Attr #@UnresolvedImport from generic import ST_CREATED, ST_PREPARED, ST_STARTED from ..lib.error import UserError +from tomato.config import MIGRATION_TRESHOLD + class UDP_Endpoint(elements.Element): element = models.ForeignKey(host.HostElement, null=True, on_delete=models.SET_NULL) @@ -32,6 +34,7 @@ class UDP_Endpoint(elements.Element): "stop": [ST_STARTED], "prepare": [ST_CREATED], "destroy": [ST_PREPARED], + "migrate": [ST_PREPARED,ST_STARTED], elements.REMOVE_ACTION: [ST_CREATED], } CUSTOM_ATTRS = { @@ -111,6 +114,30 @@ def action_stop(self): self.element.action("stop") self.setState(ST_PREPARED, True) + def checkMigrate(self): + return self.state in [ST_CREATED,ST_PREPARED] + + + def try_migrate(self): + + if self.state in [ST_CREATED]: return + + host_ = host.select(elementTypes=[self.remoteType()]) + + bestHost,bestPref = host.getBestHost(elementTypes=[self.remoteType()]) + UserError.check(bestHost, code=UserError.NO_RESOURCES, message="No matching host found for element", data={"type": self.TYPE}) + + hostScore = host.getHostScore(self.element.host,elementTypes=[self.remoteType()]) + + if bestPref > hostScore*MIGRATION_TRESHOLD: + return + + + self.action_destroy() + self.element = bestHost.createElement(self.remoteType(), parent=None, attrs=attrs, ownerElement=self) + self.save() + self.setState(ST_PREPARED, True) + def upcast(self): return self diff --git a/backend/tomato/host.py b/backend/tomato/host.py index fbf1060cc..1bcf79aeb 100644 --- a/backend/tomato/host.py +++ b/backend/tomato/host.py @@ -25,6 +25,7 @@ from auth import Flags from dumpmanager import DumpSource import time, hashlib, threading, datetime, zlib, base64, sys +from tomato.config import AVG_MINIMUM, AVG_MAXIMUM class RemoteWrapper: def __init__(self, url, host, *args, **kwargs): @@ -284,6 +285,10 @@ class Host(attributes.Mixin, DumpSource, models.Model): availability = attributes.attribute("availability", float, 1.0) description_text = attributes.attribute("description_text", unicode, "") dump_last_fetch = attributes.attribute("dump_last_fetch", float, 0) + detachable = attributes.attribute("detachable", bool,False) + detached = attributes.attribute("detached",bool, False) + fixedPref = attributes.attribute("fixed_pref",float,0) + # connections: [HostConnection] # elements: [HostElement] # templates: [TemplateOnHost] @@ -327,6 +332,7 @@ def update(self): self.hostInfoTimestamp = (before + after) / 2.0 self.hostInfo["query_time"] = after - before self.hostInfo["time_diff"] = self.hostInfo["time"] - self.hostInfoTimestamp + self.detachable = self.hostInfo["detachable"] try: self.hostNetworks = self.getProxy().host_networks() except: @@ -583,6 +589,8 @@ def modify(self, attrs): self.enabled = value elif key == "description_text": self.description_text = value + elif key == "detached": + self.detached else: raise UserError(code=UserError.UNSUPPORTED_ATTRIBUTE, message="Unknown host attribute", data={"attribute": key}) @@ -704,7 +712,11 @@ def info(self): "host_info_timestamp": self.hostInfoTimestamp, "availability": self.availability, "description_text": self.description_text, - "networks": [n for n in self.hostNetworks] if self.hostNetworks else None + "detachable": self.detachable, + "detached": self.detached, + "fixed_pref": self.fixedPref, + "networks": [n for n in self.hostNetworks] if self.hostNetworks else None, + } def __str__(self): @@ -738,7 +750,7 @@ def dump_set_last_fetch(self, last_fetch): def dump_get_last_fetch(self): return self.dump_last_fetch - + class HostElement(attributes.Mixin, models.Model): host = models.ForeignKey(Host, null=False, related_name="elements") @@ -1011,8 +1023,25 @@ def create(name, site, attrs=None): return host -def select(site=None, elementTypes=None, connectionTypes=None, networkKinds=None, hostPrefs=None, sitePrefs=None): - # STEP 1: limit host choices to what is possible +def getHostScore(host, site=None, elementTypes=None, connectionTypes=None, networkKinds=None, hostPrefs=None, sitePrefs=None): + if not sitePrefs: sitePrefs = {} + if not hostPrefs: hostPrefs = {} + if not networkKinds: networkKinds = [] + if not connectionTypes: connectionTypes = [] + if not elementTypes: elementTypes = [] + pref = 0.0 + pref -= host.componentErrors * 25 # discourage hosts with previous errors + pref -= host.getLoad() * 100 # up to -100 points for load + pref -= len(HostElement.objects.filter(host = host)); + pref += host.fixedPref + + if host in hostPrefs: + pref += hostPrefs[host] + if host.site in sitePrefs: + pref += sitePrefs[host.site] + return pref + +def getHostList(site=None, elementTypes=None, connectionTypes=None,networkKinds=None, hostPrefs=None, sitePrefs=None, detached=False): if not sitePrefs: sitePrefs = {} if not hostPrefs: hostPrefs = {} if not networkKinds: networkKinds = [] @@ -1029,41 +1058,146 @@ def select(site=None, elementTypes=None, connectionTypes=None, networkKinds=None continue if set(networkKinds) - set(host.getNetworkKinds()): continue + if host.detached and detached: + continue hosts.append(host) UserError.check(hosts, code=UserError.INVALID_CONFIGURATION, message="No hosts found for requirements") - # any host in hosts can handle the request - prefs = dict([(h, 0.0) for h in hosts]) - # STEP 2: calculate preferences based on host load - els = 0.0 - cons = 0.0 - for h in hosts: - prefs[h] -= h.componentErrors * 25 # discourage hosts with previous errors - prefs[h] -= h.getLoad() * 100 # up to -100 points for load - els += h.elements.count() - cons += h.connections.count() - avgEls = els / len(hosts) - avgCons = cons / len(hosts) - for h in hosts: - # between -30 and +30 points for element/connection over-/under-population - if avgEls: - prefs[h] -= max(-20.0, min(10.0 * (h.elements.count() - avgEls) / avgEls, 20.0)) - if avgCons: - prefs[h] -= max(-10.0, min(10.0 * (h.connections.count() - avgCons) / avgCons, 10.0)) - # STEP 3: calculate preferences based on host location - for h in hosts: - if h in hostPrefs: - prefs[h] += hostPrefs[h] - if h.site in sitePrefs: - prefs[h] += sitePrefs[h.site] - #STEP 4: select the best host - hosts.sort(key=lambda h: prefs[h], reverse=True) - logging.logMessage("select", category="host", result=hosts[0].name, - prefs=dict([(k.name, v) for k, v in prefs.iteritems()]), - site=site.name if site else None, element_types=elementTypes, connection_types=connectionTypes, - network_types=networkKinds, - host_prefs=dict([(k.name, v) for k, v in hostPrefs.iteritems()]), - site_prefs=dict([(k.name, v) for k, v in sitePrefs.iteritems()])) - return hosts[0] + prefs = dict([(h, getHostScore(h, site, elementTypes, connectionTypes, networkKinds, hostPrefs, sitePrefs)) for h in hosts]) + + return hosts, prefs + +def getBestHost(site=None, elementTypes=None, connectionTypes=None,networkKinds=None, hostPrefs=None, sitePrefs=None, detached=False): + if not sitePrefs: sitePrefs = {} + if not hostPrefs: hostPrefs = {} + if not networkKinds: networkKinds = [] + if not connectionTypes: connectionTypes = [] + if not elementTypes: elementTypes = [] + all_ = getAll(site=site) if site else getAll() + bestHost = None; + bestScore = None; + + for host in all_: + score = getHostScore(host, site, elementTypes, connectionTypes, networkKinds, hostPrefs, sitePrefs) + if(score > bestScore): + bestHost = host; + bestScore = score; + + + return bestHost, bestScore + +@util.wrap_task +def loadInfluencer(): + import statistics as stat + from tomato.elements.generic import ST_STARTED + + hosts = Host.objects.filter(detached=False,enabled=True) + + + + sum_cpu_power = 0 + sum_disc_space = 0 + sum_memory = 0 + + + cloud_cpu_load =[] + cloud_memory_used=[] + cloud_disk_space_used=[] + #Get a sum of all resources of all hosts + for host in hosts: + + hostInfo = host.hostinfo + + cloud_cpu_load.append(host.getLoad()) + cloud_memory_used.append(hostInfo['resources']['diskspace']['memory']['used']) + cloud_disk_space_used.append(hostInfo['resources']['diskspace']['data']['used']/hostInfo['resources']['diskspace']['data']['total']) + + sum_cpu_power += (hostInfo['resources']['cpus_present']['count']*hostInfo['resources']['cpu_present']['bogomips_avg']) + sum_disc_space += hostInfo['resources']['diskspace']['data']['total'] + sum_memory += hostInfo['resources']['diskspace']['memory']['total'] + + + + avgLoad = [] + avgLoad.append(stat.mean(cloud_cpu_load)) + avgLoad.append(stat.mean(cloud_memory_used/sum_memory)) + avgLoad.append(stat.mean(cloud_disk_space_used/sum_disc_space)) + + + cloud_load = min(max(avgLoad),1.0) + #If the average load is not less or equal to the defined MINIMUM, stop this function + + + #Check for hosts that can fit into the resources of the cloud + detachableHosts = Host.objects.filter(detachable=True,enabled=True) + detachableHosts = sorted(detachableHosts, key = lambda host: (len(HostElement.objects.filter(host = host,state = ST_STARTED)), getHostScore(host))) + + for host in detachableHosts: + + + fixedPref = 0 + + hostInfo = host.hostinfo + + host_cpu_power = (hostInfo['resources']['cpus_present']['count']*hostInfo['resources']['cpu_present']['bogomips_avg']) + host_disc_space = hostInfo['resources']['diskspace']['data']['used'] + host_memory = hostInfo['resources']['diskspace']['memory']['used'] + host_load = host.getLoad() + + + #Check for active elements and connections and reduce fixedPref for each + host_elements = HostElement.objects.filter(host = host,state = ST_STARTED) + host_connections = HostConnection.objects.filter(host = host) + + if(cloud_load> AVG_MINIMUM): + host.fixedPref = 0; + else: + fixedPref -= 25; + + + + avgLoad_tmp = [] + avgLoad_tmp.append(stat.mean(cloud_cpu_load.remove(host_load))) + avgLoad_tmp.append(stat.mean(cloud_memory_used/(sum_memory-host_memory))) + avgLoad_tmp.append(stat.mean(cloud_disk_space_used/(sum_disc_space-host_disc_space))) + + tmp_cloud_load = min(max(avgLoad_tmp),1.0) + + + + #If we can fit the used resources of a detachable host into the other hosts, just do so + if tmp_cloud_load < AVG_MAXIMUM: + fixedPref -= 200 + + if host_elements == [] and host_connections == []: + #He has no active elements and we can handle his load + fixedPref -=900 + + + host.fixedPref = fixedPref + +def checkMigration(): + + import elements as ele + + from tomato.elements.generic import ST_PREPARED + #Walk through all elements and think about reallocating them. + elementList = list(ele.getAll()); + + for el in elementList: + if(el.state == ST_PREPARED): + el.try_migrate() + + +def select(site=None, elementTypes=None, connectionTypes=None, networkKinds=None, hostPrefs=None, sitePrefs=None): + if not sitePrefs: sitePrefs = {} + if not hostPrefs: hostPrefs = {} + if not networkKinds: networkKinds = [] + if not connectionTypes: connectionTypes = [] + if not elementTypes: elementTypes = [] + + host, pref = getBestHost(site, elementTypes, connectionTypes,networkKinds, hostPrefs, sitePrefs) + + return host; @cached(timeout=3600, autoupdate=True) @@ -1145,7 +1279,61 @@ def synchronizeComponents(): hel.synchronize() for hcon in HostConnection.objects.all(): hcon.synchronize() + + + + + +@util.wrap_task +def dynamic_allocation(): + + import statistics + + avg = [] + for h in getHostList(): + avg.append(h.getLoad()) + + avg = statistics.mean(avg) + + if avg < AVG_MINIMUM: + host_deactivation() + elif avg >= AVG_MAXIMUM: + + host_allocation() + + +def host_deactivation(): + + loadInfluencer() + + hosts,prefs = getHostList() + + hosts = filter(lambda x: not x.detached and x.detachable, hosts) + + if not hosts == []: + host = hosts[-1] + if host.fixedPref < -1000 and HostElement.objects.filter(host = host) == []: + host.modify({'detached':True}) + #host.deactivate() + + + +def host_allocation(): + hosts, prefs = getHostList() + + hosts.sort(key=lambda h: prefs[h], reverse=True) + + hosts_detached = filter(lambda x: x.detached==True, hosts) + + if hosts_detached != []: + hosts_detached[0].modify({'detached':False}) + #else: + #TODO: Do something to chose the correct cloud provider plugin to allocate new hosts + scheduler.scheduleRepeated(config.HOST_UPDATE_INTERVAL, synchronize) # @UndefinedVariable scheduler.scheduleRepeated(3600, synchronizeComponents) # @UndefinedVariable +scheduler.scheduleRepeated(30, checkMigration) # @UndefinedVariable +scheduler.scheduleRepeated(300, loadInfluencer) # @UndefinedVariable +scheduler.scheduleRepeated(300, dynamic_allocation) # @UndefinedVariable diff --git a/hostmanager/tomato/api/host.py b/hostmanager/tomato/api/host.py index b432329cd..d2af3f8cb 100644 --- a/hostmanager/tomato/api/host.py +++ b/hostmanager/tomato/api/host.py @@ -141,6 +141,7 @@ def host_info(): "data": hostinfo.diskinfo(config.DATA_DIR), }, }, + "detachable":config.DETACHABLE, "uptime": hostinfo.uptime(), "system": hostinfo.system(), "dumps": dump.getCount() diff --git a/hostmanager/tomato/config.py b/hostmanager/tomato/config.py index 2c2ad0c31..ccc540c2b 100644 --- a/hostmanager/tomato/config.py +++ b/hostmanager/tomato/config.py @@ -201,6 +201,13 @@ MAINTENANCE = os.environ.has_key('TOMATO_MAINTENANCE') + +""" +A Flag that informs the backend if a host is detachable or not +""" + +DETACHABLE = False + try: import sys for path in filter(os.path.exists, ["/etc/tomato/hostmanager.conf", os.path.expanduser("~/.tomato/hostmanager.conf"), "hostmanager.conf"]): diff --git a/hostmanager/tomato/lib/newcmd/qemu_img.py b/hostmanager/tomato/lib/newcmd/qemu_img.py index 5cd6c6b0e..a10707c49 100644 --- a/hostmanager/tomato/lib/newcmd/qemu_img.py +++ b/hostmanager/tomato/lib/newcmd/qemu_img.py @@ -75,7 +75,7 @@ def create(path, format="qcow2", size=None, backingImage=None): @_public def convert(src, dst, srcFormat="qcow2", dstFormat="qcow2", compress=True): src = params.convert(src, check=_checkImage) - dst = params.convert(dst, check=lambda p: not os.path.exists(p)) + dst = params.convert(dst, check=lambda p: not os.path.exists(p[1])) srcFormat = params.convert(srcFormat, convert=str) dstFormat = params.convert(dstFormat, convert=str) try: diff --git a/shared/lib/util.py b/shared/lib/util.py index f842783bb..0ea35102b 100644 --- a/shared/lib/util.py +++ b/shared/lib/util.py @@ -221,3 +221,31 @@ def secondsInMonth(year, month): end = startOfMonth(year, month+1) if month < 12 else startOfMonth(year+1, 1) return end-start +def upload (url, file, name="upload"): + import httplib, urlparse, os + parts = urlparse.urlparse(url) + conn = httplib.HTTPConnection(parts.netloc) + req = parts.path + if parts.query: + req += "?" + parts.query + conn.putrequest("POST", req) + filename = os.path.basename(file) + filesize = os.path.getsize(file) + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + prepend = "--" + BOUNDARY + CRLF + 'Content-Disposition: form-data; name="%s"; filename="%s"' % ( + name, filename) + CRLF + "Content-Type: application/data" + CRLF + CRLF + append = CRLF + "--" + BOUNDARY + "--" + CRLF + CRLF + conn.putheader("Content-Length", len(prepend) + filesize + len(append)) + conn.putheader("Content-Type", 'multipart/form-data; boundary=%s' % BOUNDARY) + conn.endheaders() + conn.send(prepend) + fd = open(file, "r") + data = fd.read(8192) + while data: + conn.send(data) + data = fd.read(8192) + fd.close() + conn.send(append) + resps = conn.getresponse() + data = resps.read()