'''
updateObject module.
This holds onto the UpdateObject class.
It allows us to easily abstract away different update types and provide a uniform
way to interpret the results through a set of basic actionable functions.
'''
import time
from paradrop.backend import exc
from paradrop.backend.fc import chutestorage
from pdtools.lib.output import out
from paradrop.lib import settings
from paradrop.lib import chute
UPDATE_SPECIFIC_ARGS = ["pkg", "func"]
[docs]class UpdateObject(object):
"""
The base UpdateObject class, covers a few basic methods but otherwise all the intelligence
exists in the inherited classes.
All update information passed by the API server is contained as variables of this class
such as update.updateType, update.updateClass, etc...
By default, the following variables should be utilized:
responses : an array of messages any module can choose to append warnings or errors to
failure : the module that chose to fail this update can set a string message to return
: to the user in the failure variable. It should be very clear as to why the
: failure occurred, but if the user wants more information they may find it
: in the responses variable which may contain debug information, etc...
"""
updateModuleList = []
def __init__(self, obj):
# Pull in all the keys from the obj identified
self.__dict__.update(obj)
# Any module can add notes and warnings here
self.responses = []
# In case of a failure, the final message about failure goes here
self.failure = None
# Each update gets its own plan map
self.plans = exc.plangraph.PlanMap(self.name)
# Grab a reference to our storage system
self.chuteStor = chutestorage.ChuteStorage()
# Explicitly define a reference to the new data object
self.new = chute.Chute(obj, strip=UPDATE_SPECIFIC_ARGS)
# Grab the old version if it exists
self.old = self.chuteStor.getChute(self.name)
# Save a timestamp from when the update object was created.
self.createdTime = time.time()
def __repr__(self):
return "<Update({}) :: {} - {} @ {}>".format(self.updateClass, self.name, self.updateType, self.tok)
def __str__(self):
return "<Update({}) :: {}>".format(self.updateClass, self.name)
[docs] def saveState(self):
"""
Function should be overwritten for each UpdateObject subtype
"""
pass
[docs] def complete(self, **kwargs):
"""
Signal to the API server that any action we need to perform is
complete and the API server can finish its connection with the
client that initiated the API request.
"""
# Save a timestamp from when we finished execution.
self.endTime = time.time()
if(settings.DEBUG_MODE):
kwargs['responses'] = self.responses
# Set our results
self.result = kwargs
try:
message = "Completed {} operation on chute {}: {}".format(
self.updateType, self.new.name,
"success" if kwargs['success'] else "failure")
out.usage(message, chute=self.new.name, updateType=self.updateType,
createdTime=self.createdTime, startTime=self.startTime,
endTime=self.endTime, **kwargs)
except Exception as e:
out.exception(e, True)
# Call the function we were provided
self.func(self)
[docs] def execute(self):
"""
The function that actually walks through the main process required to create the chute.
It follows the executeplan module through the paces of:
1) Generate the plans for each exc module
2) Prioritize the plans
3) Execute the plans
If at any point we fail then this function will directly take care of completing
the update process with an error state and will close the API connection.
"""
# Save a timestamp from when we started execution.
self.startTime = time.time()
# Generate the plans we need to setup the chute
if(exc.executionplan.generatePlans(self)):
out.warn('Failed to generate plans\n')
self.complete(success=False, message=self.failure)
return
# Aggregate those plans
exc.executionplan.aggregatePlans(self)
# Execute on those plans
if(exc.executionplan.executePlans(self)):
# Getting here means we need to abort what we did
res = exc.executionplan.abortPlans(self)
# Did aborting also fail? This is bad!
if(res):
###################################################################################
# Getting here means the abort system thinks it wasn't able to get the system
# back into the state it was in prior to this update.
###################################################################################
out.err('TODO: What do we do when we fail during abort?\n')
pass
# Report the failure back to the user
self.complete(success=False, message=self.failure)
return
# Now save the new state if we are all ok
self.saveState()
# Respond to the API server to let them know the result
self.complete(success=True, message='Chute {} {} success'.format(
self.name, self.updateType))
[docs]class UpdateChute(UpdateObject):
"""
Updates specifically tailored to chute actions like create, delete, etc...
"""
# List of all modules that need to be called during execution planning
updateModuleList = [
exc.name,
exc.state,
exc.struct,
exc.resource,
exc.traffic,
exc.runtime
]
def __init__(self, obj):
# TODO : do this better
if(obj.get('updateType', None) == "create"):
obj['state'] = chute.STATE_RUNNING
elif(obj.get('updateType', None) == "start"):
obj['state'] = chute.STATE_RUNNING
elif(obj.get('updateType', None) == "restart"):
obj['state'] = chute.STATE_RUNNING
elif(obj.get('updateType', None) == "delete"):
obj['state'] = chute.STATE_STOPPED
elif(obj.get('updateType', None) == "stop"):
obj['state'] = chute.STATE_STOPPED
super(UpdateChute, self).__init__(obj)
#for start and restart updates we need to get the config info from the old config without overwriting new update info
if self.updateType == "start" or self.updateType == "restart":
for k in set(self.old.__dict__.keys()).difference(set(self.new.__dict__.keys())):
self.new.__dict__.update({k: self.old.__dict__.get(k)})
[docs] def saveState(self):
"""
For chutes specifically we need to change the chuteStor object to reflect
the new state of the system after a chute update. Perform that update here.
"""
if(self.updateType == "delete"):
self.chuteStor.deleteChute(self.new)
else:
self.chuteStor.saveChute(self.new)
###################################################################################################
# Module functions and variables
###################################################################################################
UPDATE_CLASSES = {
"CHUTE": UpdateChute
}
[docs]def parse(obj):
"""
Determines the update type and returns the proper class.
"""
uclass = obj.get('updateClass', None)
cls = UPDATE_CLASSES.get(uclass, None)
if(cls is None):
raise Exception('BadUpdateType', 'updateClass is invalid, must be one of: %s' % ", ".join(UPDATE_CLASSES))
return cls(obj)