def get(self): # lookup user's auth info user_info = User.get_by_id(long(self.user_id)) # look up instances, then patch them for hotstart support pinstances = [] instances = Instance.get_all() for instance in instances: try: username_patch = instance.user.get().username except: username_patch = "#HOTSTART#" # no self pinstance = { "name": instance.name, "status": instance.status, "username_patch": username_patch, "created": instance.created, "expires": instance.expires, "key": instance.key } pinstances.append(pinstance) params = {'instances': pinstances} return self.render_template('admin/instances.html', **params)
def get(self, name=None): # check token token = self.request.get('token') if token != "": user_info = User.get_by_token(token) if user_info: db_instances = Instance.get_all() # work around index warning/errors using a .filter() in models.py instances = [] for db_instance in db_instances: # limit to instances the user has started if db_instance.user == user_info.key: instances.append(db_instance) params = { 'user_name': user_info.username, 'instances': instances } self.response.headers['Content-Type'] = "application/json" return self.render_template('api/instances.json', **params) # no token, no user, no data params = { "response": "fail", "message": "must include [token] parameter with a valid token" } self.response.status = '402 Payment Required' self.response.status_int = 402 self.response.headers['Content-Type'] = "application/json" return self.render_template('api/response.json', **params)
def get(self): db_instances = Instance.get_all() # work around index warning/errors using a .filter() in models.py statuses = [] for db_instance in db_instances: # limit to instances the user has started statuses.append(db_instance.status[0]) params = {'statuses': statuses} self.response.headers['Content-Type'] = "application/json" return self.render_template('api/instances.csv', **params)
def get(self, sid=None): # lookup user's auth info user_info = User.get_by_id(long(self.user_id)) # redirect to a POST if we have a sid in the URL if sid and user_info.email: return self.post(sid) try: if not user_info.email or not user_info.name or not user_info.company: need_more_info = True else: need_more_info = False except: need_more_info = True # look up user's instances db_instances = Instance.get_all() # work around index warning/errors using a .filter() in models.py instances = [] for db_instance in db_instances: # limit to instances the user has started if db_instance.user == user_info.key: if db_instance.renamed == None: db_instance.renamed = "" instances.append(db_instance) streams = Stream.get_all() params = { 'instances': instances, 'num_instances': len(instances), 'streams': streams, 'user_id': self.user_id, 'user_info': user_info, 'sid': sid, 'need_more_info': need_more_info } return self.render_template('instance/list.html', **params)
def post(self, sid=None): # a POST here is a create instance event # know the user user_info = User.get_by_id(long(self.user_id)) if sid and user_info.email: # get form values stream = Stream.get_by_sid(sid) try: size = stream.instance_size except: size = 0 # look up user's instances db_instances = Instance.get_all() # check the user's limits instance_count = 0 for db_instance in db_instances: # limit to instances the user has started if db_instance.user == user_info.key: instance_count = instance_count + 1 # warn and redirect if limit is reached if (instance_count + 1) > user_info.max_instances: self.add_message( 'Instance limit reached. This account may only start %s instances. Please delete an existing instance to start a new one!' % user_info.max_instances, 'warning') return self.redirect_to('instances-list') ## HOT START # check for a hot start instances = Instance.get_hotstarts() for instance in instances: # fiveminutesago depends on number of seconds at end of this *** fiveminutesago = datetime.datetime.now() - datetime.timedelta( 0, 900) # if this hotstart instance has a matching sid, assign and redirect to it if instance.created < fiveminutesago and instance.stream.get( ).sid == stream.sid and instance.status == "RUNNING": # map to user instance.user = user_info.key instance.hotstart = False instance.put() self.add_message( 'Instance assigned! Use login buttons to access %s.' % stream.name, 'success') slack.slack_message("Instance type %s assigned for %s!" % (stream.name, user_info.username)) return self.redirect_to('instance-detail', name=instance.name) # ## TRATS TOH # make the instance call handle http = httplib2.Http(timeout=10) # where and who created it (labels for google cloud console) if config.isdev: iuser = "******" % ("dev", user_info.username.lower()) else: iuser = "******" % ("prod", user_info.username.lower()) # build url to create new instance from stream url = '%s/api/stream/%s?token=%s&user=%s&size=%s' % ( config.fastener_host_url, sid, config.fastener_api_token, iuser, size) try: # pull the response back TODO add error handling response, content = http.request(url, 'POST', None, headers={}) gcinstance = json.loads(content) name = gcinstance['instance'] password = gcinstance['password'] if name == "failed": raise Exception("Instance start failed.") # set up an instance instance = Instance(name=name, status="PROVISIONING", user=user_info.key, stream=stream.key, size=size, password=password, expires=datetime.datetime.now() + datetime.timedelta(0, 604800), started=datetime.datetime.now()) instance.put() slack.slack_message("Instance type %s created for %s!" % (stream.name, user_info.username)) # give the db a second to update if config.isdev: time.sleep(1) self.add_message( 'Instance created! Grab some coffee and wait for %s to start.' % stream.name, 'success') params = {'name': name} return self.redirect_to('instance-detail', **params) except: self.add_message( 'The system is currently busy with other instances. Please try again in a few minutes.', 'warning') return self.redirect_to('instances-list') else: # email update sumbission if not self.form.validate(): self.add_message( "There were errors validating your email address.", "error") return self.get() email = self.form.email.data.strip() user_info = User.get_by_id(long(self.user_id)) user_info.email = email.strip() user_info.put() if len(email) > 3 and not config.isdev: name = user_info.name try: mc = MarketoClient(config.munchkin_id, config.mclient_id, config.mclient_secret) try: first = name.split()[0] except: first = "" try: last = name.split()[1] except: last = "" try: company = user_info.company except: company = "None" leads = [{ "email": user_info.email, "firstName": first, "lastName": last, "company": company, "leadSource": config.mclient_leadSource }] lead = mc.execute( method='push_lead', leads=leads, lookupField='email', programName=config.mclient_programName, programStatus=config.mclient_programStatus) except Exception as ex: slack.slack_message( "Marketo lead create failed because %s." % ex) slack.slack_message( "We got %s to update their email for instance launch!" % user_info.username) self.add_message("Thank you! Your email has been updated.", 'success') # redirect back to GET on list, but with a sid AND email in place this time to create if sid: return self.redirect_to('streams-start3', sid=sid) else: return self.redirect_to('instances-list')
def get(self, sid): # know the user user_info = User.get_by_id(long(self.user_id)) # check if we have their email if not user_info.email: self.add_message( 'Please update your email address before starting an instance!', 'warning') return self.redirect_to('account-settings') # look up user's instances db_instances = Instance.get_all() # check the user's limits instance_count = 0 for db_instance in db_instances: # limit to instances the user has started if db_instance.user == user_info.key: instance_count = instance_count + 1 # warn and redirect if limit is reached if (instance_count + 1) > user_info.max_instances: self.add_message( 'Instance limit reached. This account may only start %s instances. Please delete an existing instance to start a new one!' % user_info.max_instances, 'warning') return self.redirect_to('instances-list') # get stream stream = Stream.get_by_sid(sid) try: size = stream.instance_size except: size = 0 ## HOT START # check for a hot start instances = Instance.get_hotstarts() for instance in instances: # fiveminutesago depends on number of seconds at end of this *** fiveminutesago = datetime.datetime.now() - datetime.timedelta( 0, 900) # if this hotstart instance has a matching sid, assign and redirect to it if instance.created < fiveminutesago and instance.stream.get( ).sid == stream.sid and instance.status == "RUNNING": # map to user instance.user = user_info.key instance.hotstart = False instance.put() self.add_message( 'Instance assigned! Use login buttons to access %s.' % stream.name, 'success') slack.slack_message("Instance type %s assigned for %s!" % (stream.name, user_info.username)) return self.redirect_to('instance-detail', name=instance.name) # ## TOH TRATS # make the instance call to the control box http = httplib2.Http(timeout=10) # where and who created it if config.isdev: iuser = "******" % ("dev", user_info.username) else: iuser = "******" % ("prod", user_info.username) url = '%s/api/stream/%s?token=%s&user=%s&size=%s' % ( config.fastener_host_url, sid, config.fastener_api_token, iuser, size) try: # pull the response back TODO add error handling response, content = http.request(url, 'POST', None, headers={}) gcinstance = json.loads(content) name = gcinstance['instance'] password = gcinstance['password'] if name == "failed": raise Exception("Instance start failed.") # set up an instance (note there are two ways to create an instance - see below) instance = Instance(name=name, status="PROVISIONING", user=user_info.key, stream=stream.key, size=size, password=password, expires=datetime.datetime.now() + datetime.timedelta(0, 604800), started=datetime.datetime.now()) instance.put() slack.slack_message("Instance type %s created for %s!" % (stream.name, user_info.username)) # give the db a second to update if config.isdev: time.sleep(1) self.add_message( 'Instance created! Give the system a few minutes to start %s.' % stream.name, 'success') params = {'name': name} return self.redirect_to('instance-detail', **params) except: self.add_message( 'The system is currently busy with other instances. Please try again in a few minutes.', 'warning') return self.redirect_to('instances-list')
def get(self): try: # grab a list of instances from the Fastener API http = httplib2.Http(timeout=30) url = '%s/api/instance/list?token=%s' % (config.fastener_host_url, config.fastener_api_token) response, content = http.request(url, 'GET') # list of instances from Google Cloud (see ./fastener/sample-output.json) gcinstances = json.loads(content) if len(gcinstances) > 0: message = "ok" else: message = "no instances were returned from fastener API" except Exception as ex: gcinstances = [] slack.slack_message( "Tender::Exception with list query to fastener box.") message = "failed to contact fastener API" # list of instances from db instances = Instance.get_all() # bail if we didn't get any instances from Google if len(gcinstances) == 0: params = { "message": message, "gc_count": len(gcinstances), "db_count": len(instances) } slack.slack_message("Tender::No instances from Google?") self.response.headers['Content-Type'] = "application/json" return self.render_template('api/tender.json', **params) ###### # loop through list of instances in local or production DB for instance in instances: name = instance.name found = False # loop through the instances we got from google for gcinstance in gcinstances: # check if the names match if name == gcinstance['name']: # got a match found = True try: # grab the IP address and status instance.ip = gcinstance['networkInterfaces'][0][ 'accessConfigs'][0]['natIP'] except: # got limited or no data about instance instance.ip = "None" # leave commented out slack.slack_message("Tender::%s has status %s" % (instance.name, instance.status)) # if not BUILDING then update status right from google if instance.status not in ("BUILDING"): instance.status = gcinstance['status'] # at this point if the instance.status is NOT BUILDING, then we grab # whatever google says the instance is doing. if it's RUNNING, then # we do an instance link test to ensure the fusion service is running # if Fusion does not respond, we set it to CONFIGURING # BUILDING state is kept until an update of BUILDING state is done # see APIs # are we running? if instance.status == "RUNNING": # check if the box is running fusion admin yet (CONFIGURING if NOT) try: # fast fail connection for checking if fusion is up http_test = httplib2.Http(timeout=2) test_url = 'http://%s:8764' % instance.ip response, content = http_test.request( test_url, 'GET') test_status = response['status'] except: slack.slack_message( "Tender::%s FAILED Fusion port test." % (instance.name)) test_status = "404" # set admin_link if box is running and test comes back 200 if test_status == "200": instance.admin_link = test_url # build app link and update, if it exists try: if instance.stream.get().app_stub: app_stub = instance.stream.get().app_stub instance.app_link = "http://%s%s" % ( instance.ip, app_stub) else: instance.app_link = None except: instance.app_link = None else: # show the box is in CONFIGURING instance.status = "CONFIGURING" instance.admin_link = None instance.app_link = None instance.put() else: # NOT RUNNING STATUS (FROM GOOGLE) OR BUILDING (FROM DEPLOY SCRIPT) # should we start it? if instance.tender_action == "START": # try to start it http = httplib2.Http(timeout=10) url = '%s/api/instance/%s/start?token=%s' % ( config.fastener_host_url, name, config.fastener_api_token) try: # pull the response back TODO add error handling response, content = http.request(url, 'GET', None, headers={}) # update if google returns pending if json.loads(content)[ 'status'] == "PENDING" or json.loads( content)['status'] == "DONE": params = { "response": "success", "message": "instance %s started" % name } instance.status = "PROVISIONING" instance.tender_action = "NONE" instance.started = datetime.datetime.now() instance.put() slack.slack_message( "Tender::%s wanted and got a START" % (instance.name)) else: slack.slack_message( "Tender::%s wanted START but google reported %s" % (instance.name, json.loads(content)['status'])) except Exception as ex: slack.slack_message( "Tender::Exception %s with %s" % (ex, instance.name)) else: # slack.slack_message("Tender::%s not requesting any actions." % instance.name) pass # instance has been terminated if instance.status == "TERMINATED": # set start time to far in the past instance.started = instance.created - datetime.timedelta( 0, 604800) # make sure we write all changes out instance.put() break # no need to keep looking else: # instance doesn't match the one we're working on pass if not found: # box wasn't found on GCP (via fastener LIST call) slack.slack_message( "Instance %s noted not being on GCP - looking" % name) http = httplib2.Http(timeout=10) url = '%s/api/instance/%s/status?token=%s' % ( config.fastener_host_url, name, config.fastener_api_token) # pull the response back response, content = http.request(url, 'GET', None, headers={}) result = json.loads(content) if not result: # we could not verify box was or wasn't running (fastener might not be running) slack.slack_message("Can't tell what is going on.") pass else: try: if result['error'] == "NOTFOUND": slack.slack_message( "Deleting instance %s from DB for not being on GCP." % name) instance.key.delete() except: # no error # why are we here, we got a response this box is running pass else: # no instances in db pass # cleanup stray and EOL instances for gcinstance in gcinstances: instance = Instance.get_by_name(gcinstance['name']) if instance: name = instance.name # if instance is expired, end it if instance.expires < datetime.datetime.now(): # make the instance call to the control box try: http = httplib2.Http(timeout=10) url = '%s/api/instance/%s/delete?token=%s' % ( config.fastener_host_url, name, config.fastener_api_token) # pull the response back response, content = http.request(url, 'GET', None, headers={}) if content['status'] == "PENDING": instance.key.delete() slack.slack_message( "DELETING instance %s's from GCP because expired." % name) except: slack.slack_message( "ERROR: failed deleting instance %s's from GCP because expired." % name) else: # instance wasn't found in db name = gcinstance['name'] # make sure we don't delete non-demo or prod instances if 'button' in name and config.isdev == False: # i.e. put 'button' in an instance name & this will delete the instance slack.slack_message( "Not found in DB. Will try to delete instance %s's from Google Cloud." % name) # make the instance call to the control box # THIS IS THE DANGEROUS BITS try: http = httplib2.Http(timeout=10) url = '%s/api/instance/%s/delete?token=%s' % ( config.fastener_host_url, name, config.fastener_api_token) # pull the response back response, content = http.request(url, 'GET', None, headers={}) if content['status'] == "PENDING": slack.slack_message( "DELETING instance %s's from Google Cloud." % name) else: slack.slack_message( "ERROR: funky content returned while deleting instance %s's from Google Cloud." % name) except: slack.slack_message( "ERROR: failed deleting instance %s from Google Cloud." % name) else: # no instances from cloud - this should never run pass params = { "message": message, "gc_count": len(gcinstances), "db_count": len(instances) } self.response.headers['Content-Type'] = "application/json" return self.render_template('api/tender.json', **params)
def post(self, sid=None): # topic (stackexchange subdomain, for now) topic = self.request.get('topic') if not topic: topic = "ai" # region region = self.request.get('region') if not region: region = "any" # preemptible request (add check user perms) preemptible = self.request.get('preemptible') if not preemptible: preemptible = 1 # preemptible else: if preemptible == "False" or preemptible == "false" or int( preemptible) == 0: preemptible = 0 # not preemptible else: preemptible = 1 # check token token = self.request.get('token') if token != "": user_info = User.get_by_token(token) if user_info: # look up streams stream = Stream.get_by_sid(sid) if not stream: params = { "response": "fail", "message": "stream %s does not exist on these endpoints" % sid } return self.render_template('api/response.json', **params) try: size = stream.instance_size except: size = 0 # look up user's instances db_instances = Instance.get_all() # check the user's limits instance_count = 0 for db_instance in db_instances: # limit to instances the user has started (doing it this way because can't figure out ndb indexing) if db_instance.user == user_info.key: instance_count = instance_count + 1 # check preemtibility ability if preemptible == 0: if user_info.superuser == False: # if the user can't start preemptible tell them params = { "response": "fail", "message": "token may not create non-preemptible instance" } return self.render_template('api/response.json', **params) # warn and redirect if limit is reached if (instance_count + 1) > user_info.max_instances: params = { "response": "fail", "message": "max instances reached for provided token" } return self.render_template('api/response.json', **params) # make the instance call to the control box http = httplib2.Http(timeout=10) # where and who created it if config.isdev: iuser = "******" % ("dev", user_info.username) else: iuser = "******" % ("prod", user_info.username) url = '%s/api/stream/%s?token=%s&user=%s&topic=%s®ion=%s&preemptible=%s&size=%s' % ( config.fastener_host_url, sid, config.fastener_api_token, iuser, topic, region, preemptible, size) try: # pull the response back TODO add error handling # CREATE HAPPENS HERE response, content = http.request(url, 'POST', None, headers={}) gcinstance = json.loads(content) name = gcinstance['instance'] password = gcinstance['password'] if name == "failed": raise Exception( "Instance start was delayed due to quota. Try again in a few minutes." ) # set up an instance instance = Instance(name=name, status="PROVISIONING", user=user_info.key, stream=stream.key, size=size, region=region, topic=topic, preemptible=bool(preemptible), password=password, expires=datetime.datetime.now() + datetime.timedelta(0, 604800), started=datetime.datetime.now()) instance.put() try: slack.slack_message( "Instance type %s created for %s!" % (stream.name, user_info.username)) except: print "slack failing" # give the db a second to update if config.isdev: time.sleep(3) params = {'instance': instance} self.response.headers['Content-Type'] = "application/json" return self.render_template('api/instance.json', **params) except Exception as ex: # the horror params = { "response": "fail", "message": "exception thrown: %s" % ex } return self.render_template('api/response.json', **params) # no token, no user, no data self.response.status = '402 Payment Required' self.response.status_int = 402 self.response.headers['Content-Type'] = "application/json" params = { "response": "fail", "message": "must include [token] with a valid token" } return self.render_template('api/response.json', **params)