예제 #1
0
	def trashman(self):
		from webapp.libs.openstack import instance_info
		from webapp.libs.openstack import instance_decommission		

		# build the response
		response = {"response": "success", "result": {"message": "", "server": {}}}

		# get instance (server) info
		cluster_response = instance_info(self)

		if cluster_response['response'] == "success":
			# we should NOT have this, so try to decomission out of desperation
			cluster_response = instance_decommission(self)
			response['result']['message'] = "Terminating instance %s" % self.name
		else:
			# delete this instance into forever
			self.address_model.release()
			self.delete(self)
			response['result']['message'] = "Instance %s has been deleted." % self.name

		# make a call to the callback url to report instance details
		appliance = Appliance().get()
		callback_url = self.callback_url
		pool_response = pool_instances(
			url=callback_url,
			instance=self,
			appliance=appliance)

		return response
예제 #2
0
    def reserve(self, callback_url, flavor_id):
        # build response
        response = {"response": "success", "result": {"message": ""}}

        # find a willing instance
        instance = db.session.query(Instances).filter_by(
            state=1, flavor_id=flavor_id).first()

        if instance:
            # set that instance to reserved (active == 10)
            instance.state = 10
            instance.callback_url = callback_url
            instance.update()

            # tell the pool we're using it (url must be empty string to tell pool)
            appliance = Appliance().get()
            pool_response = pool_instances(instance=self, appliance=appliance)

            # response
            response['result'][
                'message'] = "Instance %s marked as reserved." % instance.name
            response['result']['instance'] = row2dict(instance)
            response['result']['ask'] = instance.flavor.ask
            response['result']['address'] = instance.address.address
        else:
            response['response'] = "error"
            response['result']['message'] = "No available instances."

        return response
예제 #3
0
	def reserve(self, callback_url, flavor_id):
		# build response
		response = {"response": "success", "result": {"message": ""}}

		# find a willing instance
		instance = db.session.query(Instances).filter_by(state=1, flavor_id=flavor_id).first()

		if instance:
			# set that instance to reserved (active == 10)
			instance.state = 10
			instance.callback_url = callback_url
			instance.update()

			# tell the pool we're using it (url must be empty string to tell pool)
			appliance = Appliance().get()
			pool_response = pool_instances(
				instance=self,
				appliance=appliance)
			
			# response
			response['result']['message'] = "Instance %s marked as reserved." % instance.name
			response['result']['instance'] = row2dict(instance)
			response['result']['ask'] = instance.flavor.ask
			response['result']['address'] = instance.address.address
		else:
			response['response'] = "error"
			response['result']['message'] = "No available instances."
		
		return response
예제 #4
0
    def trashman(self):
        from webapp.libs.openstack import instance_info
        from webapp.libs.openstack import instance_decommission

        # build the response
        response = {
            "response": "success",
            "result": {
                "message": "",
                "server": {}
            }
        }

        # get instance (server) info
        cluster_response = instance_info(self)

        if cluster_response['response'] == "success":
            # we should NOT have this, so try to decomission out of desperation
            cluster_response = instance_decommission(self)
            response['result'][
                'message'] = "Terminating instance %s" % self.name
        else:
            # delete this instance into forever
            self.address_model.release()
            self.delete(self)
            response['result'][
                'message'] = "Instance %s has been deleted." % self.name

        # make a call to the callback url to report instance details
        appliance = Appliance().get()
        callback_url = self.callback_url
        pool_response = pool_instances(url=callback_url,
                                       instance=self,
                                       appliance=appliance)

        return response
예제 #5
0
	def housekeeping(self):
		from webapp.libs.openstack import instance_info
		from webapp.libs.openstack import instance_decommission
		from webapp.libs.openstack import instance_console

		# build the response
		response = {"response": "success", "result": {"message": "", "server": {}}}

		# get instance (server) info
		cluster_response = instance_info(self)
		server = cluster_response['result']['server']

		# we all have limited time in this reality
		epoch_time = int(time.time())

		# set start state
		start_state = self.state

		# this is complicated...because we aren't EC with OpenStack...or I'm crazy
		if cluster_response['response'] == "success": 
			# openstack responded it found this instance
			if server.status == "ACTIVE":
				# openstack says the server is running
				if self.expires < epoch_time:
					# suspend the instance for non-payment
					try:
						self.suspend()
						response['result']['message'] = "Instance %s suspended." % self.name
					except Exception as e:
						response['response'] = 'error'
						response['result']['message'] = \
							'Instance {instance} suspending failed: "{error}".'.format(
								instance=self.name, error=str(e))
						return response
					self.state = 5
				elif self.expires > epoch_time:
					# openstack says we're running, and we're paid
					if self.state == 5 or self.state == 6:
						# we move the instance to starting mode
						response['result']['message'] = "Instance %s is starting." % self.name
						self.state = 3
			elif server.status == "SUSPENDED" or server.status == "SHUTOFF":
				# openstack says this instance is suspended
				if self.expires > epoch_time:
					# should be running because not expired
					try:
						self.resume()
						response['result']['message'] = "Instance %s resumed." % self.name
					except Exception as e:
						response['response'] = 'error'
						response['result']['message'] = \
							'Instance {instance} resume failed: "{error}".'.format(
								instance=self.name, error=str(e))
						return response
					self.state = 3 # mark as starting
				if self.expires + app.config['POOL_DECOMMISSION_TIME'] < epoch_time:
					# should be destroyed (suspended for +2 hours without pay)
					response['result']['message'] = "Instance %s decommissioned." % self.name
					self.state = 7
			else:
				# openstack indicates another state besides SUSPENDED or ACTIVE
				if self.expires > epoch_time:
					# we should be running, but in a weird state - destroy then restart
					response = instance_decommission(self)
					response['result']['message'] = "Instance %s restarted." % self.name
					self.state = 2 # set as paid and ready to start
					app.logger.error("OpenStack says instance=(%s) isn't in the correct state.  Setting to restart." % self.name)
				else:
					# expired but in a weird state - destroy
					response = instance_decommission(self)
					response['result']['message'] = "Instance %s decommissioned." % self.name
					self.state = 7
		else:
			# openstack can't find this instance
			if self.expires > epoch_time:
				if self.state == 2:
					# check error rate
					if self.message_count > 10:
						# we're failing to start the instance, so decomission
						response['result']['message'] = "Instance %s decommissioned." % self.name
						self.state = 7
						app.logger.error("Exceeded error rate on callbacks for instance=(%s). Decomissioning." % self.name)
				else:
					# set instance to restart - not expired, should be running
					response['response'] = "error" # technically, someone is probably f*****g with things
					response['result']['message'] = "Setting instance %s to restart." % self.name
					self.state = 2 # will be started shortly after this by start
					app.logger.error("OpenStack doesn't know about instance=(%s). Setting to restart." % self.name)
			else:
				# no reason to be running
				response['response'] = "error"
				response['result']['message'] = "Instance %s decommissioned." % self.name
				self.state = 7 # will be deleted shortly after this by trashman

		# get instance console output
		cluster_response = instance_console(self)
		if 'console' in response['result']:
			self.console = response['result']['console']

		# save updated properties
		self.save()

		# make a call to the callback url to report instance details if either the
		# state has changed or the last state change is less than 900 secs ago
		if self.state != start_state or self.updated >= int(time.time()) - 900:
			appliance = Appliance().get()
			callback_url = self.callback_url
			pool_response = pool_instances(
				url=callback_url,
				instance=self,
				appliance=appliance)

		return response
예제 #6
0
	def nudge(self):
		from webapp.libs.openstack import try_associate_floating_ip
		from webapp.libs.openstack import instance_info
		from webapp.libs.openstack import instance_console
		from webapp.libs.openstack import instance_decommission

		# get instance console output
		response = instance_console(self)
		if 'console' in response['result']:
			self.console = response['result']['console']
		self.update()

		# get instance (server) info
		response = instance_info(self)

		# set start state
		start_state = self.state

		# set instance meta data
		if response['response'] == "success":
			server = response['result']['server']

			# if the state is ACTIVE, we set to be running state==4
			if server.status == "ACTIVE":
				# set network info
				self.state = 4

				# try to get a floating ip for the new server
				float_response = try_associate_floating_ip(server)

				# check if call got a floating IP
				if float_response['response'] == "success":
						# get instance info again to pick up new IP
						response = instance_info(self)

						# load the response into the server object
						if response['response'] == "success":
							server = response['result']['server']

				else:
					# log 'errors' in floating assignment
					app.logger.info(float_response['result']['message'])

				# extract IP addresses using IPy
				# in some circumstances this will squash multiple same/same address types
				# we only extract and store one each of private ipv4, public ipv4, and public ipv6
				for key in server.networks.keys(): # any network names
					for address in server.networks[key]: # loop through each address for each network
						# private IPv4
						if IP(address).iptype() == "PRIVATE" and IP(address).version() == 4:
							self.privateipv4 = address
						# public IPv4
						elif IP(address).iptype() == "PUBLIC" and IP(address).version() == 4:
							self.publicipv4 = address
						# public IPv6
						elif IP(address).iptype() == "ALLOCATED ARIN" and IP(address).version() == 6:
							self.publicipv6 = address

				# update the instance
				self.update()

			# ERROR status from openstack
			elif server.status == "ERROR":
				# instance failed to start, so delete and reset to paid
				response = instance_decommission(self)

				self.state = 2 # will be started again shortly
				self.update()

				response['response'] = "error"
				response['result']['message'] = "OpenStack errored on instance start."
				
				app.logger.error("OpenStack error on starting instance=(%s).  Setting to restart." % self.name)

			# SPAWNING status from openstack
			else:
				# we all have limited time in this reality
				epoch_time = int(time.time())			

				# wait_timer is 5 minutes after the last update
				wait_timer = self.updated + 300

				# test to see if we are 'hung' on SPAWNING for more than wait_timer
				if epoch_time > wait_timer:
					# we're now  past when the instance needed to move to RUNNING
					response = instance_decommission(self)

					response['response'] = "error"
					response['result']['message'] = "Setting instance %s to restart." % self.name
					
					self.state = 2 # will be started shortly after this by start
					self.updated = epoch_time # given we 'timed' out, give the instance more time
					self.update()

					"""
					of anyplace, this is where you *might* want to add some time to the instance
					because a time based payment has been made on it.  however, this could lead to 
					a situation where an instance gets stuck in a circular state of erroring, getting
					more time, erroring again, rinse and repeat.  instead of embracing this eventuality, 
					we choose to short the customer her measly few cents instead, and let it serve as a 
					as an excuse to add 'karma hits' on bad starts from providers as a feature later
					"""

					app.logger.error("OpenStack hung starting instance=(%s).  Setting to restart." % self.name)
					
				else:
					# this is a 'soft' fail
					response['response'] = "error"
					response['result']['message'] = "Still starting instance=(%s)." % self.name


		# OpenStack reports instance NOT FOUND	
		else:
			# we all have limited time in this reality
			epoch_time = int(time.time())			

			# we first check if we're outright expired (shouldn't happen)
			if self.expires < epoch_time:
				# no reason to be running as we're expired
				self.state = 7 # will be deleted shortly after this by trashman
				self.update()

				response['response'] = "error"
				response['result']['message'] = "Instance %s decommissioned." % self.name

				app.logger.error("OpenStack couldn't find expired instance=(%s). Decomissioning." % self.name)

			else:
				# we didn't find the instance in openstack, yet we should be running
				self.state = 2 # set to be started again
				self.update()

				response['response'] = "error"
				response['result']['message'] = "OpenStack couldn't find instance.  Restarting."
				
				app.logger.error("OpenStack couldn't find instance=(%s). Setting to restart." % self.name)

		# make a call to the callback url to report instance details on state change
		if self.state != start_state:
			appliance = Appliance().get()
			callback_url = self.callback_url
			pool_response = pool_instances(
				url=callback_url,
				instance=self,
				appliance=appliance)

		return response
예제 #7
0
	def start(self):
		from webapp.libs.openstack import flavor_verify_install
		from webapp.libs.openstack import instance_start
	
		# build the response
		response = {"response": "success", "result": {"message": ""}}

		# appliance
		appliance = Appliance().get()

		# load the callback url (expected to be None first time through)
		callback_url = self.callback_url
		
		# check if instance needs to reset
		epoch_time = int(time.time())
		if self.expires < epoch_time:
			# instance time expired, so don't start
			self.state = 1
			self.update()
			response['response'] = "error"
			response['result']['message'] = "Instance payment is expired.  Now waiting on payment."

		# we run a maximum of 7 callback checks
		for loop_count in range(7):
			# set state to 3 just for the pool
			self.state = 3
			# make a call to the callback url to get instance details
			pool_response = pool_instances(
				url=callback_url,
				instance=self,
				appliance=appliance)
			# and set it back to 2 because we don't know yet if it's actually starting
			self.state = 2

			# check for a failure to contact the callback server
			if pool_response['response'] == "error":
				self.message = pool_response['result']['message']
				self.message_count = self.message_count + 1
				self.update()
				return pool_response

			# look and see if we have a callback_url in the response
			try:
				# run the loop again to call the callback url
				if pool_response['result']['instance']['callback_url'] == '':
					break
				else:
					callback_url = pool_response['result']['instance']['callback_url']
			except:
				# catch no callback_url keys
				break
		else:
			response['response'] = "error"
			response['result']['message'] = "Callback depth exceeded."
			self.message = response['result']['message']
			self.message_count = self.message_count + 1
			self.update()
			return response

		# get dictionary from pool's reply
		start_params = schemas['InstanceStartParametersSchema'](
			**pool_response['result']['instance']).as_dict()

		# and lo, callback_url is saved
		self.callback_url = callback_url

		# lookup the image for this instance, or create it otherwise
		image = self.image
		if not image:
			image = Images.query.filter_by(
				**dict(
					filter(
						lambda x: x[0] in ['url', 'container_format', 'disk_format'],
						start_params['image'].items()))).first()
			self.update(image=image)
		if image and not image.osid:
			image.delete()
			image = None
		if not image:
			image = Images(**start_params['image'])
			self.update(image=image)
			try:
				image.save()
			except Exception as e:
				app.logger.warning("Error creating image using copy_from, attempt proxying: \"{0}\"".format(str(e)))
				response = self._proxy_image(image)
				if response['response'] != 'success':
					return response

		# if image is not ready because it's either killed or still downloading
		try:
			image_status = image.status
		except Exception as e:
			err_string = "Error communicating with OpenStack: \"{0}\"".format(str(e))
			app.logger.error(err_string)
			response['response'] = "error"
			response['result']['message'] = err_string
			return response
		if image_status == "queued" or image_status == "saving":
			# image is still downloading and is not ready to be used yet
			response['response'] = "queued"
			response['result']['message'] = "image is being created"
			return response
		elif image_status == "killed":
			# image has been killed, prossibly our openstack is a nebula
			response = self._proxy_image(image)
			if response['response'] != 'success':
				return response

		# post creation file is blank to start
		post_creation_combo = ""
		
		# load the parser to unencode jinja2 template escaping from appliance
		h = HTMLParser()

		# ssh_key unrolling
		try:
			# loop through both strings and cat onto post_creation_ssh_key_combo
			# using prefered method of injecting keys with cloud-init
			post_creation_combo += "#cloud-config\n"
			post_creation_combo += "ssh_authorized_keys:\n"
			for line in start_params['ssh_keys']:
				post_creation_combo += " - %s\n" % h.unescape(line)
			post_creation_combo += "\n"

		except:
			# do nothing on various key failure
			pass

		# create utterio file data
		post_creation_file_data = ""
		post_creation_file_data += "export MY_BITCOIN_ADDRESS=%s\n" % self.address
		post_creation_file_data += "export MY_POOL_API_ADDRESS=%s/api/v1/instances/%s/\n" % (app.config['APP_WEBSITE'], self.name)
		
		# payment address source file
		post_creation_combo += "write_files:\n"
		post_creation_combo += "- encoding: b64\n"
		post_creation_combo += '  content: %s\n' % base64.b64encode(post_creation_file_data) 
		post_creation_combo += "  path: /etc/utterio\n"
		post_creation_combo += "  permissions: '0644'\n"

		# post creation configuration handling
		try:
			for line in start_params['post_create']:
				# import what the user put in the textbox for their wisp
				post_creation_combo += "%s\n" % h.unescape(line)

			post_creation_combo += "\n"

		except:
			# do nothing on post creation failure
			pass

		# update the instance with post creation
		self.post_creation = post_creation_combo

		self.update()

		# take the instance's flavor and verify install
		flavor = models.flavors.Flavors().get_by_id(self.flavor.id)
		flavor_response = flavor_verify_install(flavor)

		if flavor_response['response'] == "error" or flavor_response['response'] == "forbidden":
			# we've failed to install flavor, so we disable it
			flavor.osid = ""
			flavor.active = 0
			flavor.update()

			# now we disable the other instances using the flavor
			instances = Instances()
			instances.toggle(flavor.id, 0)

			# disable this instance
			self.state = 0
			self.expires = self.created # zeros out the payment
			self.update()

			if flavor_response['response'] == "forbidden":
				response['result']['message'] = \
					"Not allowed to create flavor inside OpenStack."

				# log it
				app.logger.error("Disabling all instances using flavor=(%s) and disabling "
												 "creation of flavors due to lack of permissions." % flavor.name)

			else:
				# log it
				app.logger.error("Disabling all instances using flavor=(%s) due to "
												 "OpenStack failure." % flavor.name)

				# build the response and return
				response['result']['message'] = flavor_response['result']['message']

			response['response'] = "error"
			return response

		# tell openstack to start the instance
		cluster_response = instance_start(self)

		# process response
		if cluster_response['response'] == "success":
			server = cluster_response['result']['server']
			self.update(osid=server.id, state=3)
			response['result'] = cluster_response['result']
		else:
			response = cluster_response

		return response
예제 #8
0
	def coinop(self, amount):
		# build response
		response = {"response": "success", "result": {"message": "", "instance": {}}}

		# calculate the purchased seconds based on payment we received
		ask = float(self.flavor.ask)/1000000 # BTC per hour
		
		try:
			purchased_seconds = (amount/ask)*3600 # amount in BTC/ask in BTC * seconds in hour
		except:
			purchased_seconds = 0

		# handle local appliance start
		if amount == 0:
			purchased_seconds = 15*60 # give 15 minutes to instance for free

		# current UTC time in seconds since epoch
		epoch_time = int(time.time())

		# if we're not running (state==1 or 10), set the run state to light (to be started)
		# if we're suspended (state==5), set the run state to relight (to be unsuspended)
		# cron jobs will take care of the rest of the job of starting/unsuspending
		# NOTE: We're getting paid pennies for doing nothing until cronjob runs!
		if self.state == 1 or self.state == 10:
			self.state = 2
			self.expires = epoch_time + purchased_seconds # starting from now
			self.updated = epoch_time
		elif self.state == 5:
			self.state = 6
			self.expires = epoch_time + purchased_seconds # starting from now
			self.updated = epoch_time
		else:
			# states 0, 2, 3, 4, 6, 7
			self.expires = self.expires + purchased_seconds # starting from expire time
			self.updated = epoch_time

		# get instance console output - only run if we've got an osid
		# basically this only runs when we get a repayment
		if self.osid:
			from webapp.libs.openstack import instance_console
			response = instance_console(self)
			if 'console' in response['result']:
				self.console = response['result']['console']

		# update the instance
		self.update()

		# make a call to the callback url to report instance details
		callback_url = self.callback_url
		appliance = Appliance().get()
		
		pool_response = pool_instances(
			url=callback_url,
			instance=self,
			appliance=appliance)

		if pool_response['response'] == "success":
			# overload response
			response['result']['message'] = "Added %s seconds to %s's expire time." % (purchased_seconds, self.name)
			response['result']['instance'] = row2dict(self)
		else:
			# note the error in the instance object
			self.message_count = self.message_count + 1
			self.message = pool_response['result']['message']
			self.update()

			# load response and log
			response = pool_response
			app.logger.error("Error sending instance=(%s) data to pool." % self.name)

		return response
예제 #9
0
    def housekeeping(self):
        from webapp.libs.openstack import instance_info
        from webapp.libs.openstack import instance_decommission
        from webapp.libs.openstack import instance_console

        # build the response
        response = {
            "response": "success",
            "result": {
                "message": "",
                "server": {}
            }
        }

        # get instance (server) info
        cluster_response = instance_info(self)
        server = cluster_response['result']['server']

        # we all have limited time in this reality
        epoch_time = int(time.time())

        # set start state
        start_state = self.state

        # this is complicated...because we aren't EC with OpenStack...or I'm crazy
        if cluster_response['response'] == "success":
            # openstack responded it found this instance
            if server.status == "ACTIVE":
                # openstack says the server is running
                if self.expires < epoch_time:
                    # suspend the instance for non-payment
                    try:
                        self.suspend()
                        response['result'][
                            'message'] = "Instance %s suspended." % self.name
                    except Exception as e:
                        response['response'] = 'error'
                        response['result']['message'] = \
                         'Instance {instance} suspending failed: "{error}".'.format(
                          instance=self.name, error=str(e))
                        return response
                    self.state = 5
                elif self.expires > epoch_time:
                    # openstack says we're running, and we're paid
                    if self.state == 5 or self.state == 6:
                        # we move the instance to starting mode
                        response['result'][
                            'message'] = "Instance %s is starting." % self.name
                        self.state = 3
            elif server.status == "SUSPENDED" or server.status == "SHUTOFF":
                # openstack says this instance is suspended
                if self.expires > epoch_time:
                    # should be running because not expired
                    try:
                        self.resume()
                        response['result'][
                            'message'] = "Instance %s resumed." % self.name
                    except Exception as e:
                        response['response'] = 'error'
                        response['result']['message'] = \
                         'Instance {instance} resume failed: "{error}".'.format(
                          instance=self.name, error=str(e))
                        return response
                    self.state = 3  # mark as starting
                if self.expires + app.config[
                        'POOL_DECOMMISSION_TIME'] < epoch_time:
                    # should be destroyed (suspended for +2 hours without pay)
                    response['result'][
                        'message'] = "Instance %s decommissioned." % self.name
                    self.state = 7
            else:
                # openstack indicates another state besides SUSPENDED or ACTIVE
                if self.expires > epoch_time:
                    # we should be running, but in a weird state - destroy then restart
                    response = instance_decommission(self)
                    response['result'][
                        'message'] = "Instance %s restarted." % self.name
                    self.state = 2  # set as paid and ready to start
                    app.logger.error(
                        "OpenStack says instance=(%s) isn't in the correct state.  Setting to restart."
                        % self.name)
                else:
                    # expired but in a weird state - destroy
                    response = instance_decommission(self)
                    response['result'][
                        'message'] = "Instance %s decommissioned." % self.name
                    self.state = 7
        else:
            # openstack can't find this instance
            if self.expires > epoch_time:
                if self.state == 2:
                    # check error rate
                    if self.message_count > 10:
                        # we're failing to start the instance, so decomission
                        response['result'][
                            'message'] = "Instance %s decommissioned." % self.name
                        self.state = 7
                        app.logger.error(
                            "Exceeded error rate on callbacks for instance=(%s). Decomissioning."
                            % self.name)
                else:
                    # set instance to restart - not expired, should be running
                    response[
                        'response'] = "error"  # technically, someone is probably f*****g with things
                    response['result'][
                        'message'] = "Setting instance %s to restart." % self.name
                    self.state = 2  # will be started shortly after this by start
                    app.logger.error(
                        "OpenStack doesn't know about instance=(%s). Setting to restart."
                        % self.name)
            else:
                # no reason to be running
                response['response'] = "error"
                response['result'][
                    'message'] = "Instance %s decommissioned." % self.name
                self.state = 7  # will be deleted shortly after this by trashman

        # get instance console output
        cluster_response = instance_console(self)
        if 'console' in response['result']:
            self.console = response['result']['console']

        # save updated properties
        self.save()

        # make a call to the callback url to report instance details if either the
        # state has changed or the last state change is less than 900 secs ago
        if self.state != start_state or self.updated >= int(time.time()) - 900:
            appliance = Appliance().get()
            callback_url = self.callback_url
            pool_response = pool_instances(url=callback_url,
                                           instance=self,
                                           appliance=appliance)

        return response
예제 #10
0
    def nudge(self):
        from webapp.libs.openstack import try_associate_floating_ip
        from webapp.libs.openstack import instance_info
        from webapp.libs.openstack import instance_console
        from webapp.libs.openstack import instance_decommission

        # get instance console output
        response = instance_console(self)
        if 'console' in response['result']:
            self.console = response['result']['console']
        self.update()

        # get instance (server) info
        response = instance_info(self)

        # set start state
        start_state = self.state

        # set instance meta data
        if response['response'] == "success":
            server = response['result']['server']

            # if the state is ACTIVE, we set to be running state==4
            if server.status == "ACTIVE":
                # set network info
                self.state = 4

                # try to get a floating ip for the new server
                float_response = try_associate_floating_ip(server)

                # check if call got a floating IP
                if float_response['response'] == "success":
                    # get instance info again to pick up new IP
                    response = instance_info(self)

                    # load the response into the server object
                    if response['response'] == "success":
                        server = response['result']['server']

                else:
                    # log 'errors' in floating assignment
                    app.logger.info(float_response['result']['message'])

                # extract IP addresses using IPy
                # in some circumstances this will squash multiple same/same address types
                # we only extract and store one each of private ipv4, public ipv4, and public ipv6
                for key in server.networks.keys():  # any network names
                    for address in server.networks[
                            key]:  # loop through each address for each network
                        # private IPv4
                        if IP(address).iptype() == "PRIVATE" and IP(
                                address).version() == 4:
                            self.privateipv4 = address
                        # public IPv4
                        elif IP(address).iptype() == "PUBLIC" and IP(
                                address).version() == 4:
                            self.publicipv4 = address
                        # public IPv6
                        elif IP(address).iptype() == "ALLOCATED ARIN" and IP(
                                address).version() == 6:
                            self.publicipv6 = address

                # update the instance
                self.update()

            # ERROR status from openstack
            elif server.status == "ERROR":
                # instance failed to start, so delete and reset to paid
                response = instance_decommission(self)

                self.state = 2  # will be started again shortly
                self.update()

                response['response'] = "error"
                response['result'][
                    'message'] = "OpenStack errored on instance start."

                app.logger.error(
                    "OpenStack error on starting instance=(%s).  Setting to restart."
                    % self.name)

            # SPAWNING status from openstack
            else:
                # we all have limited time in this reality
                epoch_time = int(time.time())

                # wait_timer is 5 minutes after the last update
                wait_timer = self.updated + 300

                # test to see if we are 'hung' on SPAWNING for more than wait_timer
                if epoch_time > wait_timer:
                    # we're now  past when the instance needed to move to RUNNING
                    response = instance_decommission(self)

                    response['response'] = "error"
                    response['result'][
                        'message'] = "Setting instance %s to restart." % self.name

                    self.state = 2  # will be started shortly after this by start
                    self.updated = epoch_time  # given we 'timed' out, give the instance more time
                    self.update()
                    """
					of anyplace, this is where you *might* want to add some time to the instance
					because a time based payment has been made on it.  however, this could lead to 
					a situation where an instance gets stuck in a circular state of erroring, getting
					more time, erroring again, rinse and repeat.  instead of embracing this eventuality, 
					we choose to short the customer her measly few cents instead, and let it serve as a 
					as an excuse to add 'karma hits' on bad starts from providers as a feature later
					"""

                    app.logger.error(
                        "OpenStack hung starting instance=(%s).  Setting to restart."
                        % self.name)

                else:
                    # this is a 'soft' fail
                    response['response'] = "error"
                    response['result'][
                        'message'] = "Still starting instance=(%s)." % self.name

        # OpenStack reports instance NOT FOUND
        else:
            # we all have limited time in this reality
            epoch_time = int(time.time())

            # we first check if we're outright expired (shouldn't happen)
            if self.expires < epoch_time:
                # no reason to be running as we're expired
                self.state = 7  # will be deleted shortly after this by trashman
                self.update()

                response['response'] = "error"
                response['result'][
                    'message'] = "Instance %s decommissioned." % self.name

                app.logger.error(
                    "OpenStack couldn't find expired instance=(%s). Decomissioning."
                    % self.name)

            else:
                # we didn't find the instance in openstack, yet we should be running
                self.state = 2  # set to be started again
                self.update()

                response['response'] = "error"
                response['result'][
                    'message'] = "OpenStack couldn't find instance.  Restarting."

                app.logger.error(
                    "OpenStack couldn't find instance=(%s). Setting to restart."
                    % self.name)

        # make a call to the callback url to report instance details on state change
        if self.state != start_state:
            appliance = Appliance().get()
            callback_url = self.callback_url
            pool_response = pool_instances(url=callback_url,
                                           instance=self,
                                           appliance=appliance)

        return response
예제 #11
0
    def start(self):
        from webapp.libs.openstack import flavor_verify_install
        from webapp.libs.openstack import instance_start

        # build the response
        response = {"response": "success", "result": {"message": ""}}

        # appliance
        appliance = Appliance().get()

        # load the callback url (expected to be None first time through)
        callback_url = self.callback_url

        # check if instance needs to reset
        epoch_time = int(time.time())
        if self.expires < epoch_time:
            # instance time expired, so don't start
            self.state = 1
            self.update()
            response['response'] = "error"
            response['result'][
                'message'] = "Instance payment is expired.  Now waiting on payment."

        # we run a maximum of 7 callback checks
        for loop_count in range(7):
            # set state to 3 just for the pool
            self.state = 3
            # make a call to the callback url to get instance details
            pool_response = pool_instances(url=callback_url,
                                           instance=self,
                                           appliance=appliance)
            # and set it back to 2 because we don't know yet if it's actually starting
            self.state = 2

            # check for a failure to contact the callback server
            if pool_response['response'] == "error":
                self.message = pool_response['result']['message']
                self.message_count = self.message_count + 1
                self.update()
                return pool_response

            # look and see if we have a callback_url in the response
            try:
                # run the loop again to call the callback url
                if pool_response['result']['instance']['callback_url'] == '':
                    break
                else:
                    callback_url = pool_response['result']['instance'][
                        'callback_url']
            except:
                # catch no callback_url keys
                break
        else:
            response['response'] = "error"
            response['result']['message'] = "Callback depth exceeded."
            self.message = response['result']['message']
            self.message_count = self.message_count + 1
            self.update()
            return response

        # get dictionary from pool's reply
        start_params = schemas['InstanceStartParametersSchema'](
            **pool_response['result']['instance']).as_dict()

        # and lo, callback_url is saved
        self.callback_url = callback_url

        # lookup the image for this instance, or create it otherwise
        image = self.image
        if not image:
            image = Images.query.filter_by(**dict(
                filter(
                    lambda x: x[
                        0] in ['url', 'container_format', 'disk_format'],
                    start_params['image'].items()))).first()
            self.update(image=image)
        if image and not image.osid:
            image.delete()
            image = None
        if not image:
            image = Images(**start_params['image'])
            self.update(image=image)
            try:
                image.save()
            except Exception as e:
                app.logger.warning(
                    "Error creating image using copy_from, attempt proxying: \"{0}\""
                    .format(str(e)))
                response = self._proxy_image(image)
                if response['response'] != 'success':
                    return response

        # if image is not ready because it's either killed or still downloading
        try:
            image_status = image.status
        except Exception as e:
            err_string = "Error communicating with OpenStack: \"{0}\"".format(
                str(e))
            app.logger.error(err_string)
            response['response'] = "error"
            response['result']['message'] = err_string
            return response
        if image_status == "queued" or image_status == "saving":
            # image is still downloading and is not ready to be used yet
            response['response'] = "queued"
            response['result']['message'] = "image is being created"
            return response
        elif image_status == "killed":
            # image has been killed, prossibly our openstack is a nebula
            response = self._proxy_image(image)
            if response['response'] != 'success':
                return response

        # post creation file is blank to start
        post_creation_combo = ""

        # load the parser to unencode jinja2 template escaping from appliance
        h = HTMLParser()

        # ssh_key unrolling
        try:
            # loop through both strings and cat onto post_creation_ssh_key_combo
            # using prefered method of injecting keys with cloud-init
            post_creation_combo += "#cloud-config\n"
            post_creation_combo += "ssh_authorized_keys:\n"
            for line in start_params['ssh_keys']:
                post_creation_combo += " - %s\n" % h.unescape(line)
            post_creation_combo += "\n"

        except:
            # do nothing on various key failure
            pass

        # create utterio file data
        post_creation_file_data = ""
        post_creation_file_data += "export MY_BITCOIN_ADDRESS=%s\n" % self.address
        post_creation_file_data += "export MY_POOL_API_ADDRESS=%s/api/v1/instances/%s/\n" % (
            app.config['APP_WEBSITE'], self.name)

        # payment address source file
        post_creation_combo += "write_files:\n"
        post_creation_combo += "- encoding: b64\n"
        post_creation_combo += '  content: %s\n' % base64.b64encode(
            post_creation_file_data)
        post_creation_combo += "  path: /etc/utterio\n"
        post_creation_combo += "  permissions: '0644'\n"

        # post creation configuration handling
        try:
            for line in start_params['post_create']:
                # import what the user put in the textbox for their wisp
                post_creation_combo += "%s\n" % h.unescape(line)

            post_creation_combo += "\n"

        except:
            # do nothing on post creation failure
            pass

        # update the instance with post creation
        self.post_creation = post_creation_combo

        self.update()

        # take the instance's flavor and verify install
        flavor = models.flavors.Flavors().get_by_id(self.flavor.id)
        flavor_response = flavor_verify_install(flavor)

        if flavor_response['response'] == "error" or flavor_response[
                'response'] == "forbidden":
            # we've failed to install flavor, so we disable it
            flavor.osid = ""
            flavor.active = 0
            flavor.update()

            # now we disable the other instances using the flavor
            instances = Instances()
            instances.toggle(flavor.id, 0)

            # disable this instance
            self.state = 0
            self.expires = self.created  # zeros out the payment
            self.update()

            if flavor_response['response'] == "forbidden":
                response['result']['message'] = \
                 "Not allowed to create flavor inside OpenStack."

                # log it
                app.logger.error(
                    "Disabling all instances using flavor=(%s) and disabling "
                    "creation of flavors due to lack of permissions." %
                    flavor.name)

            else:
                # log it
                app.logger.error(
                    "Disabling all instances using flavor=(%s) due to "
                    "OpenStack failure." % flavor.name)

                # build the response and return
                response['result']['message'] = flavor_response['result'][
                    'message']

            response['response'] = "error"
            return response

        # tell openstack to start the instance
        cluster_response = instance_start(self)

        # process response
        if cluster_response['response'] == "success":
            server = cluster_response['result']['server']
            self.update(osid=server.id, state=3)
            response['result'] = cluster_response['result']
        else:
            response = cluster_response

        return response
예제 #12
0
    def coinop(self, amount):
        # build response
        response = {
            "response": "success",
            "result": {
                "message": "",
                "instance": {}
            }
        }

        # calculate the purchased seconds based on payment we received
        ask = float(self.flavor.ask) / 1000000  # BTC per hour

        try:
            purchased_seconds = (
                amount /
                ask) * 3600  # amount in BTC/ask in BTC * seconds in hour
        except:
            purchased_seconds = 0

        # handle local appliance start
        if amount == 0:
            purchased_seconds = 15 * 60  # give 15 minutes to instance for free

        # current UTC time in seconds since epoch
        epoch_time = int(time.time())

        # if we're not running (state==1 or 10), set the run state to light (to be started)
        # if we're suspended (state==5), set the run state to relight (to be unsuspended)
        # cron jobs will take care of the rest of the job of starting/unsuspending
        # NOTE: We're getting paid pennies for doing nothing until cronjob runs!
        if self.state == 1 or self.state == 10:
            self.state = 2
            self.expires = epoch_time + purchased_seconds  # starting from now
            self.updated = epoch_time
        elif self.state == 5:
            self.state = 6
            self.expires = epoch_time + purchased_seconds  # starting from now
            self.updated = epoch_time
        else:
            # states 0, 2, 3, 4, 6, 7
            self.expires = self.expires + purchased_seconds  # starting from expire time
            self.updated = epoch_time

        # get instance console output - only run if we've got an osid
        # basically this only runs when we get a repayment
        if self.osid:
            from webapp.libs.openstack import instance_console
            response = instance_console(self)
            if 'console' in response['result']:
                self.console = response['result']['console']

        # update the instance
        self.update()

        # make a call to the callback url to report instance details
        callback_url = self.callback_url
        appliance = Appliance().get()

        pool_response = pool_instances(url=callback_url,
                                       instance=self,
                                       appliance=appliance)

        if pool_response['response'] == "success":
            # overload response
            response['result'][
                'message'] = "Added %s seconds to %s's expire time." % (
                    purchased_seconds, self.name)
            response['result']['instance'] = row2dict(self)
        else:
            # note the error in the instance object
            self.message_count = self.message_count + 1
            self.message = pool_response['result']['message']
            self.update()

            # load response and log
            response = pool_response
            app.logger.error("Error sending instance=(%s) data to pool." %
                             self.name)

        return response