def _Delete(self): """Delete a DigitalOcean VM instance.""" stdout, ret = util.RunCurlCommand('DELETE', 'droplets/%s' % self.droplet_id) if ret != 0: if ret == 404: return # Assume already deleted. raise errors.Resource.RetryableDeletionError( 'Deletion failed: %s' % GetErrorMessage(stdout)) # Get the droplet's actions so that we can look up the # ID for the deletion just issued. stdout, ret = util.RunCurlCommand( 'GET', 'droplets/%s/actions' % self.droplet_id) if ret != 0: # There's a race condition here - if the lookup fails, assume it's # due to deletion already being complete. Don't raise an error in # that case, the _Exists check should trigger retry if needed. return response = json.loads(stdout)['actions'] # Get the action ID for the 'destroy' action. This assumes there's only # one of them, but AFAIK there can't be more since 'destroy' locks the VM. destroy = [v for v in response if v['type'] == 'destroy'][0] # Wait for completion. Actions are global objects, so the action # status should still be retrievable after the VM got destroyed. # We don't care about the result, let the _Exists check decide if we # need to try again. self._GetActionResult(destroy['id'])
def _PostCreate(self): """Get the instance's data.""" stdout, _ = util.RunCurlCommand('GET', 'droplets/%s' % self.droplet_id) response = json.loads(stdout)['droplet'] for interface in response['networks']['v4']: if interface['type'] == 'public': self.ip_address = interface['ip_address'] else: self.internal_ip = interface['ip_address']
def _Exists(self): """Returns true if the VM exists.""" _, ret = util.RunCurlCommand('GET', 'droplets/%s' % self.droplet_id, suppress_warning=True) if ret == 0: return True if ret == 404: # Definitely doesn't exist. return False # Unknown status - assume it doesn't exist. TODO(klausw): retry? return False
def _Create(self): """Create a DigitalOcean VM instance (droplet).""" with open(self.ssh_public_key) as f: public_key = f.read().rstrip('\n') stdout, ret = util.RunCurlCommand( 'POST', 'droplets', { 'name': self.name, 'region': self.zone, 'size': self.machine_type, 'image': self.image, 'backups': False, 'ipv6': False, 'private_networking': True, 'ssh_keys': [], 'user_data': CLOUD_CONFIG_TEMPLATE.format(self.user_name, public_key) }) if ret != 0: msg = GetErrorMessage(stdout) if ret in FATAL_CREATION_ERRORS: raise errors.Error( 'Creation request invalid, not retrying: %s' % msg) raise errors.Resource.RetryableCreationError( 'Creation failed: %s' % msg) response = json.loads(stdout) self.droplet_id = response['droplet']['id'] # The freshly created droplet will be in a locked and unusable # state for a while, and it cannot be deleted or modified in # this state. Wait for the action to finish and check the # reported result. if not self._GetActionResult(response['links']['actions'][0]['id']): raise errors.Resource.RetryableCreationError( 'Creation failed, see log.')
def _GetActionResult(self, action_id, wait_seconds=DEFAULT_ACTION_WAIT_SECONDS, max_tries=DEFAULT_ACTION_MAX_TRIES): """Wait until a VM action completes.""" for _ in xrange(max_tries): time.sleep(wait_seconds) stdout, ret = util.RunCurlCommand('GET', 'actions/%s' % action_id) if ret != 0: logging.warn('Unexpected action lookup failure.') return False response = json.loads(stdout) status = response['action']['status'] logging.debug('action %d: status is "%s".', action_id, status) if status == 'completed': return True elif status == 'errored': return False # If we get here, waiting timed out. Treat as failure. logging.debug('action %d: timed out waiting.', action_id) return False