def add(self, fail_on_error=True, fail_on_invalid_params=True, **kwargs): """Add new Cloud to the database This is only expected to be called by `Cloud.add` classmethod to create a cloud. Fields `owner` and `title` are already populated in `self.cloud`. The `self.cloud` model is not yet saved. Params: fail_on_error: If True, then a connection to the cloud will be established and if it fails, a `CloudUnavailableError` or `CloudUnauthorizedError` will be raised and the cloud will be deleted. fail_on_invalid_params: If True, then invalid keys in `kwargs` will raise an Error. Subclasses SHOULD NOT override or extend this method. If a subclass has to perform special parsing of `kwargs`, it can override `self._add__preparse_kwargs`. """ # Transform params with extra underscores for compatibility. rename_kwargs(kwargs, 'api_key', 'apikey') rename_kwargs(kwargs, 'api_secret', 'apisecret') # Cloud specific argument preparsing cloud-wide argument self.cloud.dns_enabled = kwargs.pop('dns_enabled', False) is True # Cloud specific kwargs preparsing. try: self._add__preparse_kwargs(kwargs) except MistError as exc: log.error("Error while adding cloud %s: %r", self.cloud, exc) raise except Exception as exc: log.exception("Error while preparsing kwargs on add %s", self.cloud) raise InternalServerError(exc=exc) try: self.update(fail_on_error=fail_on_error, fail_on_invalid_params=fail_on_invalid_params, **kwargs) except (CloudUnavailableError, CloudUnauthorizedError) as exc: # FIXME: Move this to top of the file once Machine model is # migrated. The import statement is currently here to avoid # circular import issues. from mist.api.machines.models import Machine # Remove any machines created from check_connection performing a # list_machines. Machine.objects(cloud=self.cloud).delete() # Propagate original error. raise # Add relevant polling schedules. self.add_polling_schedules()
def get_random_name_for_token(user): # produce a random name and make sure that this will not fall in an # infinite loop. if it can't get a valid new name then throw an exception for _ in range(10000): xyz = ''.join(random.choice(string.digits) for _ in range(5)) api_token_name = "api_token_" + xyz try: token_with_name_not_exists(user, api_token_name) return api_token_name except ConflictError: pass raise InternalServerError('Could not produce random api token name for ' 'user %s' % user.email)
def _create_machine_ec2(conn, key_name, private_key, public_key, machine_name, image, size, location, user_data): """Create a machine in Amazon EC2. Here there is no checking done, all parameters are expected to be sanitized by create_machine. """ with get_temp_file(public_key) as tmp_key_path: try: # create keypair with key name and pub key conn.ex_import_keypair(name=key_name, keyfile=tmp_key_path) except: # get existing key with that pub key try: keypair = conn.ex_find_or_import_keypair_by_key_material( pubkey=public_key ) key_name = keypair['keyName'] except Exception as exc: raise CloudUnavailableError("Failed to import key") # create security group name = config.EC2_SECURITYGROUP.get('name', '') description = config.EC2_SECURITYGROUP.get('description', '') try: log.info("Attempting to create security group") conn.ex_create_security_group(name=name, description=description) conn.ex_authorize_security_group_permissive(name=name) except Exception as exc: if 'Duplicate' in exc.message: log.info('Security group already exists, not doing anything.') else: raise InternalServerError("Couldn't create security group", exc) try: node = conn.create_node( name=machine_name, image=image, size=size, location=location, max_tries=1, ex_keyname=key_name, ex_securitygroup=config.EC2_SECURITYGROUP['name'], ex_userdata=user_data ) except Exception as e: raise MachineCreationError("EC2, got exception %s" % e, e) return node
def reboot_machine_ssh(self, machine): """Reboot machine by running command over SSH""" assert self.cloud == machine.cloud log.debug("Rebooting (SSH) machine %s", machine) try: if machine.public_ips: hostname = machine.public_ips[0] else: hostname = machine.private_ips[0] command = '$(command -v sudo) shutdown -r now' # TODO move it up from mist.api.methods import ssh_command ssh_command(self.cloud.owner, self.cloud.id, machine.machine_id, hostname, command) except MistError as exc: log.error("Could not reboot machine %s", machine) raise except Exception as exc: log.exception(exc) raise InternalServerError(exc=exc)
def _reboot_machine(self, machine, machine_libcloud): hypervisor = machine_libcloud.extra.get('tags', {}).get('type', None) if hypervisor == 'hypervisor': # issue an ssh command for the libvirt hypervisor try: hostname = machine_libcloud.public_ips[0] if \ machine_libcloud.public_ips else \ machine_libcloud.private_ips[0] command = '$(command -v sudo) shutdown -r now' # todo move it up from mist.api.methods import ssh_command ssh_command(self.cloud.owner, self.cloud.id, machine_libcloud.id, hostname, command) return True except MistError as exc: log.error("Could not ssh machine %s", machine.name) raise except Exception as exc: log.exception(exc) raise InternalServerError(exc=exc) else: machine_libcloud.reboot()
def destroy_machine(self, machine): """Destroy machine The param `machine` must be an instance of a machine model of this cloud. Not that the usual way to destroy a machine would be to run machine.ctl.destroy() which would in turn call this method, so that its cloud can customize it as needed. If a subclass of this controller wishes to override the way machines are destroyed, it should override `_destroy_machine` method instead. """ # assert isinstance(machine.cloud, Machine) assert self.cloud == machine.cloud if not machine.actions.destroy: raise ForbiddenError("Machine doesn't support destroy.") log.debug("Destroying machine %s", machine) machine_libcloud = self._get_machine_libcloud(machine) try: self._destroy_machine(machine, machine_libcloud) except MistError as exc: log.error("Could not destroy machine %s", machine) raise except Exception as exc: log.exception(exc) raise InternalServerError(exc=exc) while machine.key_associations: machine.key_associations.pop() machine.state = 'terminated' machine.save()
def update(self, fail_on_error=True, fail_on_invalid_params=True, **kwargs): """Edit an existing Cloud Params: fail_on_error: If True, then a connection to the cloud will be established and if it fails, a `CloudUnavailableError` or `CloudUnauthorizedError` will be raised and the cloud changes will not be saved. fail_on_invalid_params: If True, then invalid keys in `kwargs` will raise an Error. Subclasses SHOULD NOT override or extend this method. If a subclass has to perform special parsing of `kwargs`, it can override `self._update__preparse_kwargs`. """ # Close previous connection. self.disconnect() # Transform params with extra underscores for compatibility. rename_kwargs(kwargs, 'api_key', 'apikey') rename_kwargs(kwargs, 'api_secret', 'apisecret') # Cloud specific kwargs preparsing. try: self._update__preparse_kwargs(kwargs) except MistError as exc: log.error("Error while updating cloud %s: %r", self.cloud, exc) raise except Exception as exc: log.exception("Error while preparsing kwargs on update %s", self.cloud) raise InternalServerError(exc=exc) # Check for invalid `kwargs` keys. errors = {} for key in list(kwargs.keys()): if key not in self.cloud._cloud_specific_fields: error = "Invalid parameter %s=%r." % (key, kwargs[key]) if fail_on_invalid_params: errors[key] = error else: log.warning(error) kwargs.pop(key) if errors: log.error("Error updating %s: %s", self.cloud, errors) raise BadRequestError({ 'msg': "Invalid parameters %s." % list(errors.keys()), 'errors': errors, }) # Set fields to cloud model and perform early validation. for key, value in kwargs.items(): setattr(self.cloud, key, value) try: self.cloud.validate(clean=True) except me.ValidationError as exc: log.error("Error updating %s: %s", self.cloud, exc.to_dict()) raise BadRequestError({'msg': str(exc), 'errors': exc.to_dict()}) # Try to connect to cloud. if fail_on_error: try: self.compute.check_connection() except (CloudUnavailableError, CloudUnauthorizedError, SSLError) as exc: log.error( "Will not update cloud %s because " "we couldn't connect: %r", self.cloud, exc) raise except Exception as exc: log.exception( "Will not update cloud %s because " "we couldn't connect.", self.cloud) raise CloudUnavailableError(exc=exc) # Attempt to save. try: self.cloud.save() except me.ValidationError as exc: log.error("Error updating %s: %s", self.cloud, exc.to_dict()) raise BadRequestError({'msg': str(exc), 'errors': exc.to_dict()}) except me.NotUniqueError as exc: log.error("Cloud %s not unique error: %s", self.cloud, exc) raise CloudExistsError()
def update(self, **kwargs): """Edit an existing Schedule""" if self.auth_context is not None: auth_context = self.auth_context else: raise MistError("You are not authorized to update schedule") owner = auth_context.owner if kwargs.get('action'): if kwargs.get('action') not in [ 'reboot', 'destroy', 'start', 'stop' ]: raise BadRequestError("Action is not correct") script_id = kwargs.pop('script_id', '') if script_id: try: Script.objects.get(owner=owner, id=script_id, deleted=None) except me.DoesNotExist: raise ScriptNotFoundError('Script with id %s does not ' 'exist' % script_id) # SEC require permission RUN on script auth_context.check_perm('script', 'run', script_id) # for ui compatibility if kwargs.get('expires') == '': kwargs['expires'] = None if kwargs.get('max_run_count') == '': kwargs['max_run_count'] = None if kwargs.get('start_after') == '': kwargs['start_after'] = None # transform string to datetime if kwargs.get('expires'): try: kwargs['expires'] = datetime.datetime.strptime( kwargs['expires'], '%Y-%m-%d %H:%M:%S') except ValueError: raise BadRequestError('Expiration date value was not valid') if kwargs.get('start_after'): try: kwargs['start_after'] = datetime.datetime.strptime( kwargs['start_after'], '%Y-%m-%d %H:%M:%S') except ValueError: raise BadRequestError('Start-after date value was not valid') now = datetime.datetime.now() if self.schedule.expires and self.schedule.expires < now: raise BadRequestError('Date of future task is in the past. ' 'Please contact Marty McFly') if self.schedule.start_after and self.schedule.start_after < now: raise BadRequestError('Date of future task is in the past. ' 'Please contact Marty McFly') # Schedule conditions pre-parsing. try: self._update__preparse_machines(auth_context, kwargs) except MistError as exc: log.error("Error while updating schedule %s: %r", self.schedule.id, exc) raise except Exception as exc: log.exception("Error while preparsing kwargs on update %s", self.schedule.id) raise InternalServerError(exc=exc) action = kwargs.pop('action', '') if action: self.schedule.task_type = schedules.ActionTask(action=action) elif script_id: self.schedule.task_type = schedules.ScriptTask(script_id=script_id) schedule_type = kwargs.pop('schedule_type', '') if (schedule_type == 'crontab' or isinstance(self.schedule.schedule_type, schedules.Crontab)): schedule_entry = kwargs.pop('schedule_entry', {}) if schedule_entry: for k in schedule_entry: if k not in [ 'minute', 'hour', 'day_of_week', 'day_of_month', 'month_of_year' ]: raise BadRequestError("Invalid key given: %s" % k) self.schedule.schedule_type = schedules.Crontab( **schedule_entry) elif (schedule_type == 'interval' or type(self.schedule.schedule_type) == schedules.Interval): schedule_entry = kwargs.pop('schedule_entry', {}) if schedule_entry: for k in schedule_entry: if k not in ['period', 'every']: raise BadRequestError("Invalid key given: %s" % k) self.schedule.schedule_type = schedules.Interval( **schedule_entry) elif (schedule_type == 'one_off' or type(self.schedule.schedule_type) == schedules.OneOff): # implements Interval under the hood future_date = kwargs.pop('schedule_entry', '') if future_date: try: future_date = datetime.datetime.strptime( future_date, '%Y-%m-%d %H:%M:%S') except ValueError: raise BadRequestError('Date value was not valid') if future_date < now: raise BadRequestError( 'Date of future task is in the past. ' 'Please contact Marty McFly') delta = future_date - now one_off = schedules.OneOff(period='seconds', every=delta.seconds, entry=future_date) self.schedule.schedule_type = one_off self.schedule.max_run_count = 1 # set schedule attributes for key, value in kwargs.iteritems(): if key in self.schedule._fields: setattr(self.schedule, key, value) try: self.schedule.save() except me.ValidationError as e: log.error("Error updating %s: %s", self.schedule.name, e.to_dict()) raise BadRequestError({"msg": e.message, "errors": e.to_dict()}) except me.NotUniqueError as exc: log.error("Schedule %s not unique error: %s", self.schedule, exc) raise ScheduleNameExistsError() except me.OperationError: raise ScheduleOperationError()
def update(self, **kwargs): """Edit an existing Schedule""" if self.auth_context is not None: auth_context = self.auth_context else: raise MistError("You are not authorized to update schedule") owner = auth_context.owner if kwargs.get('action'): if kwargs.get('action') not in [ 'reboot', 'destroy', 'notify', 'start', 'stop' ]: raise BadRequestError("Action is not correct") script_id = kwargs.pop('script_id', '') if script_id: try: Script.objects.get(owner=owner, id=script_id, deleted=None) except me.DoesNotExist: raise ScriptNotFoundError('Script with id %s does not ' 'exist' % script_id) # SEC require permission RUN on script auth_context.check_perm('script', 'run', script_id) # for ui compatibility if kwargs.get('expires') == '': kwargs['expires'] = None if kwargs.get('max_run_count') == '': kwargs['max_run_count'] = None if kwargs.get('start_after') == '': kwargs['start_after'] = None # transform string to datetime if kwargs.get('expires'): try: if isinstance(kwargs['expires'], int): if kwargs['expires'] > 5000000000: # Timestamp in millis kwargs['expires'] = kwargs['expires'] / 1000 kwargs['expires'] = datetime.datetime.fromtimestamp( kwargs['expires']) else: kwargs['expires'] = datetime.datetime.strptime( kwargs['expires'], '%Y-%m-%d %H:%M:%S') except (ValueError, TypeError): raise BadRequestError('Expiration date value was not valid') if kwargs.get('start_after'): try: if isinstance(kwargs['start_after'], int): if kwargs['start_after'] > 5000000000: # Timestamp in ms kwargs['start_after'] = kwargs['start_after'] / 1000 kwargs['start_after'] = datetime.datetime.fromtimestamp( kwargs['start_after']) else: kwargs['start_after'] = datetime.datetime.strptime( kwargs['start_after'], '%Y-%m-%d %H:%M:%S') except (ValueError, TypeError): raise BadRequestError('Start-after date value was not valid') now = datetime.datetime.now() if self.schedule.expires and self.schedule.expires < now: raise BadRequestError('Date of future task is in the past. ' 'Please contact Marty McFly') if self.schedule.start_after and self.schedule.start_after < now: raise BadRequestError('Date of future task is in the past. ' 'Please contact Marty McFly') # Schedule selectors pre-parsing. try: self._update__preparse_machines(auth_context, kwargs) except MistError as exc: log.error("Error while updating schedule %s: %r", self.schedule.id, exc) raise except Exception as exc: log.exception("Error while preparsing kwargs on update %s", self.schedule.id) raise InternalServerError(exc=exc) action = kwargs.pop('action', '') if action: self.schedule.task_type = schedules.ActionTask(action=action) elif script_id: self.schedule.task_type = schedules.ScriptTask(script_id=script_id, params=kwargs.pop( 'params', '')) schedule_type = kwargs.pop('schedule_type', '') if (schedule_type == 'crontab' or isinstance(self.schedule.schedule_type, schedules.Crontab)): schedule_entry = kwargs.pop('schedule_entry', {}) if schedule_entry: for k in schedule_entry: if k not in [ 'minute', 'hour', 'day_of_week', 'day_of_month', 'month_of_year' ]: raise BadRequestError("Invalid key given: %s" % k) self.schedule.schedule_type = schedules.Crontab( **schedule_entry) elif (schedule_type == 'interval' or type(self.schedule.schedule_type) == schedules.Interval): schedule_entry = kwargs.pop('schedule_entry', {}) if schedule_entry: for k in schedule_entry: if k not in ['period', 'every']: raise BadRequestError("Invalid key given: %s" % k) self.schedule.schedule_type = schedules.Interval( **schedule_entry) elif (schedule_type in ['one_off', 'reminder'] or type(self.schedule.schedule_type) == schedules.OneOff): # implements Interval under the hood future_date = kwargs.pop('schedule_entry', '') if future_date: try: if isinstance(future_date, int): if future_date > 5000000000: # Timestamp is in millis future_date = future_date / 1000 future_date = datetime.datetime.fromtimestamp( future_date) else: future_date = datetime.datetime.strptime( future_date, '%Y-%m-%d %H:%M:%S') except (ValueError, TypeError): raise BadRequestError('Date value was not valid') if future_date < now: raise BadRequestError( 'Date of future task is in the past. ' 'Please contact Marty McFly') delta = future_date - now notify_msg = kwargs.get('notify_msg', '') if schedule_type == 'reminder': self.schedule.schedule_type = schedules.Reminder( period='seconds', every=delta.seconds, entry=future_date, message=notify_msg) else: self.schedule.schedule_type = schedules.OneOff( period='seconds', every=delta.seconds, entry=future_date) self.schedule.max_run_count = 1 notify = kwargs.pop('notify', 0) if notify: _delta = datetime.timedelta(0, notify) notify_at = future_date - _delta notify_at = notify_at.strftime('%Y-%m-%d %H:%M:%S') params = { 'action': 'notify', 'schedule_type': 'reminder', 'description': 'Machine expiration reminder', 'task_enabled': True, 'schedule_entry': notify_at, 'selectors': kwargs.get('selectors'), 'notify_msg': notify_msg } name = self.schedule.name + '-reminder' if self.schedule.reminder: self.schedule.reminder.delete() from mist.api.schedules.models import Schedule self.schedule.reminder = Schedule.add( auth_context, name, **params) # set schedule attributes try: kwargs.pop('selectors') except KeyError: pass for key, value in kwargs.items(): if key in self.schedule._fields: setattr(self.schedule, key, value) try: self.schedule.save() except me.ValidationError as e: log.error("Error updating %s: %s", self.schedule.name, e.to_dict()) raise BadRequestError({"msg": str(e), "errors": e.to_dict()}) except me.NotUniqueError as exc: log.error("Schedule %s not unique error: %s", self.schedule, exc) raise ScheduleNameExistsError() except me.OperationError: raise ScheduleOperationError()