def policy_update(self, cnxt, policy_id, name=None, metadata=None, is_default=None): LOG.info(_LI("Updating policy: '%(id)s'"), {'id': policy_id}) policy = policy_mod.Policy.load(cnxt, policy_id=policy_id) changed = False if name is not None and name != policy.name: policies = policy_mod.Policy.load_all(cnxt, filters={'name': name}) if len(policies) > 0: msg = _("The policy (%(name)s) already exists." ) % {"name": name} raise exception.BileanBadRequest(msg=msg) policy.name = name changed = True if metadata is not None and metadata != policy.metadata: policy.metadata = metadata changed = True if is_default is not None and is_default != policy.is_default: is_default = utils.parse_bool_param('is_default', is_default) if is_default: # Set policy to default should unset old default policy. policies = policy_mod.load_all(cnxt, filters={'is_default': True}) if len(policies) == 1: default_policy = policies[0] default_policy.is_default = False default_policy.store(cnxt) policy.is_default = is_default changed = True if changed: policy.store(cnxt) LOG.info(_LI("Policy '%(id)s' is updated."), {'id': policy_id}) return policy.to_dict()
def run_child(self): def child_hup(*args): """Shuts down child processes, existing requests are handled.""" signal.signal(signal.SIGHUP, signal.SIG_IGN) eventlet.wsgi.is_accepting = False self.sock.close() pid = os.fork() if pid == 0: signal.signal(signal.SIGHUP, child_hup) signal.signal(signal.SIGTERM, signal.SIG_DFL) # ignore the interrupt signal to avoid a race whereby # a child worker receives the signal before the parent # and is respawned unnecessarily as a result signal.signal(signal.SIGINT, signal.SIG_IGN) # The child has no need to stash the unwrapped # socket, and the reference prevents a clean # exit on sighup self._sock = None self.run_server() LOG.info(_LI('Child %d exiting normally'), os.getpid()) # self.pool.waitall() is now called in wsgi's server so # it's safe to exit here sys.exit(0) else: LOG.info(_LI('Started child %s'), pid) self.children.add(pid)
def get_image_id_by_name(self, image_identifier): '''Return an id for the specified image name. :param image_identifier: image name :returns: the id of the requested :image_identifier: :raises: exception.ImageNotFound, exception.PhysicalResourceNameAmbiguity ''' try: filters = {'name': image_identifier} image_list = list(self.client().images.list(filters=filters)) except exc.ClientException as ex: raise exception.Error( _("Error retrieving image list from glance: %s") % ex) num_matches = len(image_list) if num_matches == 0: LOG.info(_LI("Image %s was not found in glance"), image_identifier) raise exception.ImageNotFound(image_name=image_identifier) elif num_matches > 1: LOG.info(_LI("Multiple images %s were found in glance with name"), image_identifier) raise exception.PhysicalResourceNameAmbiguity( name=image_identifier) else: return image_list[0].id
def rule_create(self, cnxt, name, spec, metadata=None): if len(plugin_base.Rule.load_all(cnxt, filters={'name': name})) > 0: msg = _("The rule (%(name)s) already exists." ) % {"name": name} raise exception.BileanBadRequest(msg=msg) type_name, version = schema.get_spec_version(spec) try: plugin = environment.global_env().get_plugin(type_name) except exception.RuleTypeNotFound: msg = _("The specified rule type (%(type)s) is not supported." ) % {"type": type_name} raise exception.BileanBadRequest(msg=msg) LOG.info(_LI("Creating rule type: %(type)s, name: %(name)s."), {'type': type_name, 'name': name}) rule = plugin.RuleClass(name, spec, metadata=metadata) try: rule.validate() except exception.InvalidSpec as ex: msg = six.text_type(ex) LOG.error(_LE("Failed in creating rule: %s"), msg) raise exception.BileanBadRequest(msg=msg) rule.store(cnxt) LOG.info(_LI("Rule %(name)s is created: %(id)s."), {'name': name, 'id': rule.id}) return rule.to_dict()
def rule_create(self, cnxt, name, spec, metadata=None): if len(rule_base.Rule.load_all(cnxt, filters={'name': name})) > 0: msg = _("The rule (%(name)s) already exists." ) % {"name": name} raise exception.BileanBadRequest(msg=msg) type_name, version = schema.get_spec_version(spec) try: plugin = environment.global_env().get_rule(type_name) except exception.RuleTypeNotFound: msg = _("The specified rule type (%(type)s) is not supported." ) % {"type": type_name} raise exception.BileanBadRequest(msg=msg) LOG.info(_LI("Creating rule type: %(type)s, name: %(name)s."), {'type': type_name, 'name': name}) rule = plugin(name, spec, metadata=metadata) try: rule.validate() except exception.InvalidSpec as ex: msg = six.text_type(ex) LOG.error(_LE("Failed in creating rule: %s"), msg) raise exception.BileanBadRequest(msg=msg) rule.store(cnxt) LOG.info(_LI("Rule %(name)s is created: %(id)s."), {'name': name, 'id': rule.id}) return rule.to_dict()
def stop(self): super(Dispatcher, self).stop() # Wait for all action threads to be finished LOG.info(_LI("Stopping all action threads of engine %s"), self.engine_id) # Stop ThreadGroup gracefully self.TG.stop(True) LOG.info(_LI("All action threads have been finished"))
def _stop_rpc_server(self): # Stop RPC connection to prevent new requests LOG.info(_LI("Stopping engine service...")) try: self._rpc_server.stop() self._rpc_server.wait() LOG.info(_LI('Engine service stopped successfully')) except Exception as ex: LOG.error(_LE('Failed to stop engine service: %s'), six.text_type(ex))
def _remove_children(self, pid): if pid in self.children: self.children.remove(pid) LOG.info(_LI('Removed dead child %s'), pid) elif pid in self.stale_children: self.stale_children.remove(pid) LOG.info(_LI('Removed stale child %s'), pid) else: LOG.warn(_LW('Unrecognised child %s'), pid)
def policy_create(self, cnxt, name, rule_ids=None, metadata=None): """Create a new policy.""" if len(policy_mod.Policy.load_all(cnxt, filters={'name': name})) > 0: msg = _("The policy (%(name)s) already exists." ) % {"name": name} raise exception.BileanBadRequest(msg=msg) rules = [] if rule_ids is not None: type_cache = [] for rule_id in rule_ids: try: rule = rule_base.Rule.load(cnxt, rule_id=rule_id) if rule.type not in type_cache: rules.append({'id': rule_id, 'type': rule.type}) type_cache.append(rule.type) else: msg = _("More than one rule in type: '%s', it's " "not allowed.") % rule.type raise exception.BileanBadRequest(msg=msg) except exception.RuleNotFound as ex: raise exception.BileanBadRequest(msg=six.text_type(ex)) kwargs = { 'rules': rules, 'metadata': metadata, } policy = policy_mod.Policy(name, **kwargs) policy.store(cnxt) LOG.info(_LI("Policy is created: %(id)s."), policy.id) return policy.to_dict()
def settle_account(self, context, task=None): '''Settle account for user.''' notifier = bilean_notifier.Notifier() timestamp = utils.make_decimal(wallclock()) self._settle_account(context, timestamp=timestamp) if task == 'notify' and self._notify_or_not(): self.status_reason = "The balance is almost used up" self.status = self.WARNING # Notify user msg = {'user': self.id, 'notification': self.status_reason} notifier.info('billing.notify', msg) elif task == 'freeze' and self.balance <= 0: reason = _("Balance overdraft") LOG.info(_LI("Freeze user %(user_id)s, reason: %(reason)s"), {'user_id': self.id, 'reason': reason}) resources = plugin_base.Resource.load_all( context, user_id=self.id, project_safe=False) for resource in resources: resource.do_delete(context, timestamp=timestamp) self.rate = 0 self.status = self.FREEZE self.status_reason = reason # Notify user msg = {'user': self.id, 'notification': self.status_reason} notifier.info('billing.notify', msg) self.store(context)
def _single_run(self, application, sock): """Start a WSGI server in a new green thread.""" LOG.info(_LI("Starting single process server")) eventlet.wsgi.server(sock, application, custom_pool=self.pool, url_length_limit=URL_LENGTH_LIMIT, log=self._wsgi_logger, debug=cfg.CONF.debug)
def policy_create(self, cnxt, name, rule_ids=None, metadata=None): """Create a new policy.""" if len(policy_mod.Policy.load_all(cnxt, filters={'name': name})) > 0: msg = _("The policy (%(name)s) already exists." ) % {"name": name} raise exception.BileanBadRequest(msg=msg) rules = [] if rule_ids is not None: type_cache = [] for rule_id in rule_ids: try: rule = plugin_base.Rule.load(cnxt, rule_id=rule_id) if rule.type not in type_cache: rules.append({'id': rule_id, 'type': rule.type}) type_cache.append(rule.type) else: msg = _("More than one rule in type: '%s', it's " "not allowed.") % rule.type raise exception.BileanBadRequest(msg=msg) except exception.RuleNotFound as ex: raise exception.BileanBadRequest(msg=six.text_type(ex)) kwargs = { 'rules': rules, 'metadata': metadata, } policy = policy_mod.Policy(name, **kwargs) if not policy.is_default: default_policy = policy_mod.Policy.load_default(cnxt) if default_policy is None: policy.is_default = True policy.store(cnxt) LOG.info(_LI("Successfully create policy (%s)."), policy.id) return policy.to_dict()
def stop(self): self._stop_rpc_server() LOG.info(_LI("Stopping billing scheduler")) self.scheduler.stop() super(SchedulerService, self).stop()
def _register_info(self, name, info): '''place the new info in the correct location in the registry. :param path: a string of plugin name. :param info: reference to a PluginInfo data structure, deregister a PluginInfo if specified as None. ''' registry = self._registry if info is None: # delete this entry. LOG.warn(_LW('Removing %(item)s from registry'), {'item': name}) registry.pop(name, None) return if name in registry and isinstance(registry[name], PluginInfo): if registry[name] == info: return details = { 'name': name, 'old': str(registry[name].plugin), 'new': str(info.plugin) } LOG.warn(_LW('Changing %(name)s from %(old)s to %(new)s'), details) else: LOG.info(_LI('Registering %(name)s -> %(value)s'), { 'name': name, 'value': str(info.plugin)}) info.user_provided = not self.is_global registry[name] = info
def _get_action(self, event_type): available_actions = ['create', 'delete', 'update'] for action in available_actions: if action in event_type: return action LOG.info(_LI("Can not get action info in event_type: %s") % event_type) return None
def start(self): self.scheduler_id = socket.gethostname() self.scheduler = cron_scheduler.CronScheduler( scheduler_id=self.scheduler_id) LOG.info(_LI("Starting billing scheduler")) self.scheduler.init_scheduler() self.scheduler.start() LOG.info(_LI("Starting rpc server for bilean scheduler service")) self.target = oslo_messaging.Target(version=consts.RPC_API_VERSION, server=self.scheduler_id, topic=self.topic) self._rpc_server = rpc_messaging.get_rpc_server(self.target, self) self._rpc_server.start() super(SchedulerService, self).start()
def stop(self): self._stop_rpc_server() LOG.info(_LI("Stopping billing scheduler for engine: %s"), self.engine_id) self.scheduler.stop() super(EngineService, self).stop()
def stop(self): self._stop_rpc_server() # Notify dispatcher to stop all action threads it started. LOG.info(_LI("Stopping dispatcher for engine %s"), self.engine_id) self.dispatcher.stop() self.TG.stop() super(EngineService, self).stop()
def user_delete(self, cnxt, user_id): """Delete a specify user according to the notification.""" LOG.info(_LI('Deleging user: %s'), user_id) user = user_mod.User.load(cnxt, user_id=user_id) if user.status in [user.ACTIVE, user.WARNING]: LOG.error(_LE("User (%s) is in use, can not delete."), user_id) return user_mod.User.delete(cnxt, user_id=user_id) bilean_scheduler.notify(bilean_scheduler.DELETE_JOBS, user=user.to_dict())
def _settle_account(self, context, timestamp=None): if self.rate == 0: LOG.info(_LI("Ignore settlement action because user is in '%s' " "status."), self.status) return now = timestamp or utils.make_decimal(wallclock()) usage_seconds = now - self.last_bill cost = self.rate * usage_seconds self.balance -= cost self.last_bill = now
def _verify_and_respawn_children(self, pid, status): if len(self.stale_children) == 0: LOG.debug('No stale children') if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0: LOG.error(_LE('Not respawning child %d, cannot ' 'recover from termination'), pid) if not self.children and not self.stale_children: LOG.info(_LI('All workers have terminated. Exiting')) self.running = False else: if len(self.children) < self.conf.workers: self.run_child()
def start(self): self.engine_id = str(uuid.uuid4()) LOG.info(_LI("initialise bilean users from keystone.")) user_mod.User.init_users(self.context) self.scheduler = scheduler.BileanScheduler(engine_id=self.engine_id, context=self.context) LOG.info(_LI("Starting billing scheduler for engine: %s"), self.engine_id) self.scheduler.init_scheduler() self.scheduler.start() LOG.info(_LI("Starting rpc server for engine: %s"), self.engine_id) target = oslo_messaging.Target(version=self.RPC_API_VERSION, server=self.host, topic=self.topic) self.target = target self._rpc_server = rpc_messaging.get_rpc_server(target, self) self._rpc_server.start() super(EngineService, self).start()
def start_wsgi(self): if self.conf.workers == 0: # Useful for profiling, test, debug etc. self.pool = eventlet.GreenPool(size=self.threads) self.pool.spawn_n(self._single_run, self.application, self.sock) return LOG.info(_LI("Starting %d workers") % self.conf.workers) signal.signal(signal.SIGTERM, self.kill_children) signal.signal(signal.SIGINT, self.kill_children) signal.signal(signal.SIGHUP, self.hup) while len(self.children) < self.conf.workers: self.run_child()
def info(context, entity, action, status=None, status_reason=None, timestamp=None): timestamp = timestamp or timeutils.utcnow() event = Event(timestamp, logging.INFO, entity, action=action, status=status, status_reason=status_reason, user_id=context.project) event.store(context) LOG.info(_LI('%(name)s [%(id)s] %(action)s - %(status)s: %(reason)s'), {'name': event.obj_name, 'id': event.obj_id and event.obj_id[:8], 'action': action, 'status': status, 'reason': status_reason})
def init_scheduler(self): """Init all jobs related to the engine from db.""" admin_context = bilean_context.get_admin_context() jobs = [] or db_api.job_get_all(admin_context, scheduler_id=self.scheduler_id) for job in jobs: if self._is_exist(job.id): continue LOG.info(_LI("Add job '%(job_id)s' to scheduler '%(id)s'."), {'job_id': job.id, 'id': self.scheduler_id}) self._add_job(job.id, job.job_type, **job.parameters) LOG.info(_LI("Initialise users from keystone.")) users = user_mod.User.init_users(admin_context) # Init daily job for all users if users: for user in users: job_id = self._generate_job_id(user.id, self.DAILY) if self._is_exist(job_id): continue self._add_daily_job(user)
def user_lock_acquire(context, user_id, action_id, engine=None, forced=False): """Try to lock the specified user. :param context: the context used for DB operations; :param user_id: ID of the user to be locked. :param action_id: ID of the action that attempts to lock the user. :param engine: ID of the engine that attempts to lock the user. :param forced: set to True to cancel current action that owns the lock, if any. :returns: True if lock is acquired, or False otherwise. """ owner = db_api.user_lock_acquire(user_id, action_id) if action_id == owner: return True retries = cfg.CONF.lock_retry_times retry_interval = cfg.CONF.lock_retry_interval while retries > 0: sleep(retry_interval) LOG.debug(_('Acquire lock for user %s again'), user_id) owner = db_api.user_lock_acquire(user_id, action_id) if action_id == owner: return True retries = retries - 1 if forced: owner = db_api.user_lock_steal(user_id, action_id) return action_id == owner action = db_api.action_get(context, owner) if (action and action.owner and action.owner != engine and is_engine_dead(context, action.owner)): LOG.info(_LI('The user %(u)s is locked by dead action %(a)s, ' 'try to steal the lock.'), { 'u': user_id, 'a': owner }) reason = _('Engine died when executing this action.') db_api.action_mark_failed(context, action.id, time.time(), reason=reason) db_api.user_lock_steal(user_id, action_id) return True LOG.error(_LE('User is already locked by action %(old)s, ' 'action %(new)s failed grabbing the lock'), {'old': owner, 'new': action_id}) return False
def init_scheduler(self): """Init all jobs related to the engine from db.""" jobs = db_api.job_get_all(self.context, engine_id=self.engine_id) if not jobs: LOG.info(_LI("No job found from db")) return True for job in jobs: if self.bilean_scheduler.is_exist(job.id): continue task_name = "_%s_task" % (job.job_type) task = getattr(self, task_name) self.bilean_task.add_job(task, job.id, job_type=job.job_type, params=job.parameters)
def settle_account(self, cnxt, user_id, task=None): params = { 'name': 'settle_account_%s' % user_id, 'cause': action_mod.CAUSE_RPC, 'status': action_mod.Action.READY, 'inputs': {'task': task}, } action_id = action_mod.Action.create(cnxt, user_id, consts.USER_SETTLE_ACCOUNT, **params) self.TG.start_action(self.engine_id, action_id=action_id) LOG.info(_LI('User settle_account action queued: %s'), action_id)
def _verify_and_respawn_children(self, pid, status): if len(self.stale_children) == 0: LOG.debug('No stale children') if os.WIFEXITED(status) and os.WEXITSTATUS(status) != 0: LOG.error( _LE('Not respawning child %d, cannot ' 'recover from termination'), pid) if not self.children and not self.stale_children: LOG.info(_LI('All workers have terminated. Exiting')) self.running = False else: if len(self.children) < self.conf.workers: self.run_child()
def resource_update(self, cnxt, user_id, resource): """Do resource update.""" params = { 'name': 'update_resource_%s' % resource.get('id'), 'cause': action_mod.CAUSE_RPC, 'status': action_mod.Action.READY, 'inputs': resource, } action_id = action_mod.Action.create(cnxt, user_id, consts.USER_UPDATE_RESOURCE, **params) dispatcher.start_action(action_id=action_id) LOG.info(_LI('Resource update action queued: %s'), action_id)
def user_attach_policy(self, cnxt, user_id, policy_id): """Attach specified policy to user.""" LOG.info(_LI("Attaching policy %(policy)s to user %(user)s."), {'policy': policy_id, 'user': user_id}) user = user_mod.User.load(cnxt, user_id=user_id) if user.policy_id is not None: msg = _("User %(user)s is using policy %(now_policy)s, can not " "attach %(policy)s.") % {'user': user_id, 'now_policy': user.policy_id, 'policy': policy_id} raise exception.BileanBadRequest(msg=msg) user.policy_id = policy_id user.store(cnxt) return user.to_dict()
def start(self): self._init_service() self.TG = ThreadGroupManager() # create a dispatcher RPC service for this engine. self.dispatcher = dispatcher.Dispatcher(self, self.dispatcher_topic, consts.RPC_API_VERSION, self.TG) LOG.info(_LI("Starting dispatcher for engine %s"), self.engine_id) self.dispatcher.start() LOG.info(_LI("Starting rpc server for engine: %s"), self.engine_id) target = oslo_messaging.Target(version=consts.RPC_API_VERSION, server=self.host, topic=self.topic) self.target = target self._rpc_server = rpc_messaging.get_rpc_server(target, self) self._rpc_server.start() self.TG.add_timer(cfg.CONF.periodic_interval, self.service_manage_report) super(EngineService, self).start()
def process_identity_notification(self, notification): """Convert notification to user.""" user_id = notification['payload'].get('resource_info') if not user_id: LOG.error(_LE("Cannot retrieve user_id from notification: %s"), notification) return oslo_messaging.NotificationResult.HANDLED action = self._get_action(notification['event_type']) if action: act = notify_action.UserAction(self.cnxt, action, user_id) LOG.info(_LI("Notify engine to %(action)s user: %(user)s") % {'action': action, 'user': user_id}) act.execute() return oslo_messaging.NotificationResult.HANDLED
def url_fetch(url, allowed_schemes=('http', 'https')): '''Get the data at the specified URL. The URL must use the http: or https: schemes. The file: scheme is also supported if you override the allowed_schemes argument. Raise an IOError if getting the data fails. ''' LOG.info(_LI('Fetching data from %s'), url) components = urllib.parse.urlparse(url) if components.scheme not in allowed_schemes: raise URLFetchError(_('Invalid URL scheme %s') % components.scheme) if components.scheme == 'file': try: return urllib.request.urlopen(url).read() except urllib.error.URLError as uex: raise URLFetchError(_('Failed to retrieve data: %s') % uex) try: resp = requests.get(url, stream=True) resp.raise_for_status() # We cannot use resp.text here because it would download the entire # file, and a large enough file would bring down the engine. The # 'Content-Length' header could be faked, so it's necessary to # download the content in chunks to until max_response_size is reached. # The chunk_size we use needs to balance CPU-intensive string # concatenation with accuracy (eg. it's possible to fetch 1000 bytes # greater than max_response_size with a chunk_size of 1000). reader = resp.iter_content(chunk_size=1000) result = "" for chunk in reader: result += chunk if len(result) > cfg.CONF.max_response_size: raise URLFetchError("Data exceeds maximum allowed size (%s" " bytes)" % cfg.CONF.max_response_size) return result except exceptions.RequestException as ex: raise URLFetchError(_('Failed to retrieve data: %s') % ex)
def _create(self): con = self.context volume_api_version = self.get_volume_api_version() if volume_api_version == 1: service_type = 'volume' client_version = '1' elif volume_api_version == 2: service_type = 'volumev2' client_version = '2' else: raise exception.Error(_('No volume service available.')) LOG.info(_LI('Creating Cinder client with volume API version %d.'), volume_api_version) endpoint_type = self._get_client_option('cinder', 'endpoint_type') args = { 'service_type': service_type, 'auth_url': con.auth_url or '', 'project_id': con.tenant, 'username': None, 'api_key': None, 'endpoint_type': endpoint_type, 'http_log_debug': self._get_client_option('cinder', 'http_log_debug'), 'cacert': self._get_client_option('cinder', 'ca_file'), 'insecure': self._get_client_option('cinder', 'insecure') } client = cc.Client(client_version, **args) management_url = self.url_for(service_type=service_type, endpoint_type=endpoint_type) client.client.auth_token = self.auth_token client.client.management_url = management_url client.volume_api_version = volume_api_version return client
def wait_on_children(self): """Wait on children exit.""" while self.running: try: pid, status = os.wait() if os.WIFEXITED(status) or os.WIFSIGNALED(status): self._remove_children(pid) self._verify_and_respawn_children(pid, status) except OSError as err: if err.errno not in (errno.EINTR, errno.ECHILD): raise except KeyboardInterrupt: LOG.info(_LI('Caught keyboard interrupt. Exiting.')) os.killpg(0, signal.SIGTERM) break except exception.SIGHUPInterrupt: self.reload() continue eventlet.greenio.shutdown_safe(self.sock) self.sock.close() LOG.debug('Exited')
def read_global_environment(self): '''Read and parse global environment files.''' cfg.CONF.import_opt('environment_dir', 'bilean.common.config') env_dir = cfg.CONF.environment_dir try: files = glob.glob(os.path.join(env_dir, '*')) except OSError as ex: LOG.error(_LE('Failed to read %s'), env_dir) LOG.exception(ex) return for fname in files: try: with open(fname) as f: LOG.info(_LI('Loading environment from %s'), fname) self.load(self.parse(f.read())) except ValueError as vex: LOG.error(_LE('Failed to parse %s'), fname) LOG.exception(six.text_type(vex)) except IOError as ioex: LOG.error(_LE('Failed to read %s'), fname) LOG.exception(six.text_type(ioex))
def user_delete(self, cnxt, user_id): """Delete a specify user according to the notification.""" LOG.info(_LI('Deleging user: %s'), user_id) user_mod.User.delete(cnxt, user_id=user_id)
def rule_delete(self, cnxt, rule_id): LOG.info(_LI("Deleting rule: '%s'."), rule_id) rule_base.Rule.delete(cnxt, rule_id)
def policy_delete(self, cnxt, policy_id): LOG.info(_LI("Deleting policy: '%s'."), policy_id) policy_mod.Policy.delete(cnxt, policy_id)