def __init__(self): """ Provides some convenient variables for commands. Also sets requires_* parameters for some sets of commands. All of the command objects are singletons (or Borg). """ self.dbf = DbFactory() self.config = Config() self.az = AuthorizationBroker() self.formatter = ResponseFormatter() self.catalog = StatusCatalog() # Force the instance to have local copies of the class defaults... # This allows resources.py to modify instances without worrying # about inheritance issues (classes sharing required or optional # parameters). self.required_parameters = self.required_parameters[:] self.optional_parameters = self.optional_parameters[:] # Parameter checks are filled in automatically based on input.xml. This # lets us do some rudimentary checks before the actual command is # invoked. self.parameter_checks = {} self.action = self.__module__ package_prefix = "aquilon.worker.commands." if self.action.startswith(package_prefix): self.action = self.action[len(package_prefix):] # self.command is set correctly in resources.py after parsing input.xml self.command = self.action # The readonly and format flags are done here for convenience # and simplicity. They could be overridden by the __init__ # method of any show/search/cat commands that do not want these # defaults. Some 'one-off' commands (like ping and status) # just set the variables themselves. if self.action.startswith("show") or self.action.startswith("search"): self.requires_readonly = True self.requires_format = True if self.action.startswith("cat"): self.requires_format = True self.requires_readonly = True self._is_lock_free = True if not self.requires_readonly \ and self.config.get('broker', 'mode') == 'readonly': self.badmode = 'readonly' else: self.badmode = False self._update_render(self.render) if not self.defer_to_thread: if self.requires_azcheck or self.requires_transaction: self.defer_to_thread = True log.msg("Forcing defer_to_thread to True because of " "required authorization or transaction for %s" % self.command) # Not sure how to handle formatting with deferred... self.requires_format = False
class BrokerCommand(object): """ The basis for each command module under commands. Several class-level lists and flags are defined here that can be overridden on a per-command basis. Some can only be overridden in __init__, though, so check the docstrings. """ required_parameters = [] """ This will generally be overridden in the command class. It could theoretically be parsed out of input.xml, but that is tricky in some cases and possibly error-prone. """ optional_parameters = [] """ Optional parameters are filled in automatically from input.xml. This may contain entries that are also in the required_parameters list. If so, the required entry "wins". """ requires_azcheck = True """ Opt out of authorization checks by setting this flag to False.""" requires_transaction = True """ This sets up a session and cleans up when finished. Currently, setting azcheck to True will force this value to be True, because the azcheck requires a session that will need to be cleaned up. """ requires_format = False """ Run command results through the formatter. It is automatically set to True for all cat, search, and show commands, but could be reversed back to False by overriding __init__ for the command. """ requires_readonly = False """ Require read only isolation level for the render session. It is automatically set to True for all search and show commands, but could be reversed back to False by overriding __init__ for the command. """ # Override to indicate whether the command will generally take a # lock during execution. # # Any command with this flag set to True will use the separate # NLSession thread pool and should not have to wait on commands # that are potentially all blocking on the same lock. # # If set to None (the default), the is_lock_free property will # examine the command's module to try to determine if a lock # may be required and then cache the value. _is_lock_free = None # Run the render method on a separate thread. This will be forced # to True if requires_azcheck or requires_transaction. defer_to_thread = True def __init__(self): """ Provides some convenient variables for commands. Also sets requires_* parameters for some sets of commands. All of the command objects are singletons (or Borg). """ self.dbf = DbFactory() self.config = Config() self.az = AuthorizationBroker() self.formatter = ResponseFormatter() self.catalog = StatusCatalog() # Force the instance to have local copies of the class defaults... # This allows resources.py to modify instances without worrying # about inheritance issues (classes sharing required or optional # parameters). self.required_parameters = self.required_parameters[:] self.optional_parameters = self.optional_parameters[:] # Parameter checks are filled in automatically based on input.xml. This # lets us do some rudimentary checks before the actual command is # invoked. self.parameter_checks = {} # The parameter types are filled in automatically based on input.xml. self.parameter_types = {} # This is the pivot of the above, filled in at the same time. It is a # dictionary of type names to lists of parameters. self.parameters_by_type = {} self.action = self.__module__ package_prefix = "aquilon.worker.commands." if self.action.startswith(package_prefix): self.action = self.action[len(package_prefix):] # self.command is set correctly in resources.py after parsing input.xml self.command = self.action # The readonly and format flags are done here for convenience # and simplicity. They could be overridden by the __init__ # method of any show/search/cat commands that do not want these # defaults. Some 'one-off' commands (like ping and status) # just set the variables themselves. if self.action.startswith("show") or self.action.startswith("search"): self.requires_readonly = True self.requires_format = True if self.action.startswith("cat"): self.requires_format = True self.requires_readonly = True self._is_lock_free = True if not self.requires_readonly \ and self.config.get('broker', 'mode') == 'readonly': self.badmode = 'readonly' else: self.badmode = False self._update_render(self.render) if not self.defer_to_thread: if self.requires_azcheck or self.requires_transaction: self.defer_to_thread = True log.msg("Forcing defer_to_thread to True because of " "required authorization or transaction for %s" % self.command) # Not sure how to handle formatting with deferred... self.requires_format = False #free = "True " if self.is_lock_free else "False" #log.msg("is_lock_free = %s [%s]" % (free, self.command)) def audit_result(self, session, key, value, **arguments): # We need a place to store the result somewhere until we can finish the # audit record. Use the request object for now. request = arguments["request"] if not hasattr(request, "_audit_result"): request._audit_result = [] request._audit_result.append((key, value)) def render(self, **arguments): # pragma: no cover """ Implement this method to create a functional broker command. The base __init__ method wraps all implementations using _update_render() to enforce the class requires_* flags. """ if self.__class__.__module__ == 'aquilon.worker.broker': # Default class... no useful command info to repeat back... raise UnimplementedError("Command has not been implemented.") raise UnimplementedError("%s has not been implemented" % self.__class__.__module__) def _update_render(self, command): """ Wrap the render method using the requires_* attributes. An alternate implementation would be to just have a wrap_rendor() method or similar that got called instead of rendor(). """ def updated_render(self, *args, **kwargs): principal = kwargs["user"] request = kwargs["request"] logger = kwargs["logger"] raising_exception = None rollback_failed = False try: if self.requires_transaction or self.requires_azcheck: # Set up a session... if not "session" in kwargs: if self.is_lock_free: kwargs["session"] = self.dbf.NLSession() else: kwargs["session"] = self.dbf.Session() session = kwargs["session"] if session.bind.dialect.name == "oracle": # Make the name of the command and the request ID # available in v$session. Trying to set a value longer # than the allowed length will generate ORA-24960, so # do an explicit truncation. conn = session.connection() dbapi_con = conn.connection.connection dbapi_con.action = str(self.action)[:32] # TODO: we should include the command number as well, # since that is easier to find in the logs dbapi_con.clientinfo = str(kwargs["requestid"])[:64] # This does a COMMIT, which in turn invalidates the session. # We should therefore avoid looking up anything in the DB # before this point which might be used later. self._record_xtn(session, logger.get_status()) dbuser = get_or_create_user_principal(session, principal, commitoncreate=True) kwargs["dbuser"] = dbuser if self.requires_azcheck: self.az.check(principal=principal, dbuser=dbuser, action=self.action, resource=request.path) if self.requires_readonly: self._set_readonly(session) # begin() is only required if session transactional=False #session.begin() if self.badmode: # pragma: no cover raise UnimplementedError("Command %s not available on " "a %s broker." % (self.command, self.badmode)) for key in kwargs.keys(): if key in self.parameter_checks: kwargs[key] = self.parameter_checks[key]("--" + key, kwargs[key]) # Command is an instance method already having self... retval = command(*args, **kwargs) if self.requires_format: style = kwargs.get("style", None) retval = self.formatter.format(style, retval, request) if "session" in kwargs: session.commit() return retval except Exception, e: raising_exception = e # Need to close after the rollback, or the next time session # is accessed it tries to commit the transaction... (?) if "session" in kwargs: try: session.rollback() except: # pragma: no cover rollback_failed = True raise session.close() raise finally:
def __init__(self): """ Provides some convenient variables for commands. Also sets requires_* parameters for some sets of commands. All of the command objects are singletons (or Borg). """ self.dbf = DbFactory() self.config = Config() self.az = AuthorizationBroker() self.formatter = ResponseFormatter() self.catalog = StatusCatalog() # Force the instance to have local copies of the class defaults... # This allows resources.py to modify instances without worrying # about inheritance issues (classes sharing required or optional # parameters). self.required_parameters = self.required_parameters[:] self.optional_parameters = self.optional_parameters[:] # Parameter checks are filled in automatically based on input.xml. This # lets us do some rudimentary checks before the actual command is # invoked. self.parameter_checks = {} # The parameter types are filled in automatically based on input.xml. self.parameter_types = {} # This is the pivot of the above, filled in at the same time. It is a # dictionary of type names to lists of parameters. self.parameters_by_type = {} self.action = self.__module__ package_prefix = "aquilon.worker.commands." if self.action.startswith(package_prefix): self.action = self.action[len(package_prefix):] # self.command is set correctly in resources.py after parsing input.xml self.command = self.action # The readonly and format flags are done here for convenience # and simplicity. They could be overridden by the __init__ # method of any show/search/cat commands that do not want these # defaults. Some 'one-off' commands (like ping and status) # just set the variables themselves. if self.action.startswith("show") or self.action.startswith("search"): self.requires_readonly = True self.requires_format = True if self.action.startswith("cat"): self.requires_format = True self.requires_readonly = True self._is_lock_free = True if not self.requires_readonly \ and self.config.get('broker', 'mode') == 'readonly': self.badmode = 'readonly' else: self.badmode = False self._update_render(self.render) if not self.defer_to_thread: if self.requires_azcheck or self.requires_transaction: self.defer_to_thread = True log.msg("Forcing defer_to_thread to True because of " "required authorization or transaction for %s" % self.command) # Not sure how to handle formatting with deferred... self.requires_format = False