def event(self): return Event( name="REQUEST_CREATED", error=False, payload={"request": "request"}, metadata={}, )
def prepare(self): """Called before each verb handler""" # Used for recording prometheus metrics. # We keep this time in seconds, also time-zone does not matter # because we are just calculating a duration. self.request.created_time = time.time() # This is used for sending event notifications self.request.event = Event() self.request.event_extras = {} content_type = self.request.headers.get('content-type', '') if self.request.method.upper() in ['POST', 'PATCH'] and content_type: content_type = content_type.split(';') self.request.mime_type = content_type[0] if self.request.mime_type not in [ 'application/json', 'application/x-www-form-urlencoded' ]: raise ModelValidationError( 'Unsupported or missing content-type header') # Attempt to parse out the charset and decode the body, default to utf-8 charset = 'utf-8' if len(content_type) > 1: search_result = self.charset_re.search(content_type[1]) if search_result: charset = search_result.group(1) self.request.charset = charset self.request.decoded_body = self.request.body.decode(charset)
def startup(): """Do startup things. This is the first thing called from within the ioloop context. """ global event_publishers # Ensure we have a mongo connection yield _progressive_backoff( partial(bg_utils.setup_database, config), 'Unable to connect to mongo, is it started?' ) logger.info( 'Starting metrics server on %s:%d' % (config.web.host, config.metrics.port) ) start_http_server(config.metrics.port) logger.info( 'Starting HTTP server on %s:%d' % (config.web.host, config.web.port) ) server.listen(config.web.port, config.web.host) logger.info("Starting event publishers") event_publishers = _setup_event_publishers(client_ssl) logger.info('Starting scheduler') request_scheduler.start() logger.debug("Publishing application startup event") event_publishers.publish_event(Event(name=Events.BREWVIEW_STARTED.name)) brew_view.logger.info("Application is started. Hello!")
def publish_event(self, *args, **kwargs): """Publish a new event Args: *args: If a positional argument is given it's assumed to be an Event and will be used **kwargs: Will be used to construct a new Event to publish if no Event is given in the positional arguments Keyword Args: _publishers (Optional[List[str]]): List of publisher names. If given the Event will only be published to the specified publishers. Otherwise all publishers known to Beergarden will be used. Returns: bool: True if the publish was successful """ publishers = kwargs.pop("_publishers", None) event = args[0] if args else Event(**kwargs) return self.client.post_event(self.parser.serialize_event(event), publishers=publishers)
def shutdown(): """Do shutdown things This still operates within the ioloop, so stopping it should be the last thing done. Because things in startup aren't guaranteed to have been run we need to be careful about checking to make sure things actually need to be shut down. This execution is normally scheduled by the signal handler. """ if request_scheduler.running: logger.info('Stopping scheduler') request_scheduler.shutdown(wait=False) logger.info("Stopping HTTP server") server.stop() if event_publishers: logger.debug("Publishing application shutdown event") event_publishers.publish_event(Event(name=Events.BREWVIEW_STOPPED.name)) logger.info("Shutting down event publishers") yield list(filter( lambda x: isinstance(x, Future), event_publishers.shutdown() )) logger.info("Stopping IO loop") io_loop.add_callback(io_loop.stop)
def prepare(self): """Called before each verb handler""" # Used for calculating request handling duration self.request.created_time = datetime.datetime.utcnow() # This is used for sending event notifications self.request.event = Event() self.request.event_extras = {} content_type = self.request.headers.get("content-type", "") if self.request.method.upper() in ["POST", "PATCH"] and content_type: content_type = content_type.split(";") self.request.mime_type = content_type[0] if self.request.mime_type not in [ "application/json", "application/x-www-form-urlencoded", ]: raise ModelValidationError( "Unsupported or missing content-type header") # Attempt to parse out the charset and decode the body, default to utf-8 charset = "utf-8" if len(content_type) > 1: search_result = self.charset_re.search(content_type[1]) if search_result: charset = search_result.group(1) self.request.charset = charset self.request.decoded_body = self.request.body.decode(charset)
def test_handle_event_for_user_updated(self): role_assignments = [{ "role_name": "role1", "domain": { "scope": "Global" } }] user_updated_result = { "garden": "garden1", "user": { "username": "******", "role_assignments": role_assignments }, } event = Event( name=Events.USER_UPDATED.name, garden="garden1", metadata=user_updated_result, ) assert len( RemoteUser.objects.filter(username="******", garden="garden1")) == 0 handle_event(event) remote_user = RemoteUser.objects.get(username="******", garden="garden1") assert remote_user.role_assignments == role_assignments
async def shutdown(): """Do shutdown things This still operates within the ioloop, so stopping it should be the last thing done. Because things in startup aren't guaranteed to have been run we need to be careful about checking to make sure things actually need to be shut down. This execution is normally scheduled by the signal handler. """ logger.debug("Stopping server for new HTTP connections") server.stop() logger.debug("Stopping forward processing") beer_garden.router.forward_processor.stop() # This will almost definitely not be published to the websocket, because it would # need to make it up to the main process and back down into this process. We just # publish this here in case the main process is looking for it. publish( Event(name=Events.ENTRY_STOPPED.name, metadata={"entry_point_type": "HTTP"}) ) # We need to do this before the scheduler shuts down completely in order to kick any # currently waiting request creations logger.debug("Closing all open HTTP connections") await server.close_all_connections() logger.debug("Stopping IO loop") io_loop.add_callback(io_loop.stop)
def test_publish_skips_events_on_blocklist(self): yield self.ws_connect() EventSocket.write_message = Mock() event = Event(name=WEBSOCKET_EVENT_TYPE_BLOCKLIST[0]) EventSocket.publish(event) assert EventSocket.write_message.called is False
def initialize(self): """Actually construct all the various component pieces""" self.scheduler = self._setup_scheduler() load_plugin_log_config() plugin_config = config.get("plugin") self.helper_threads = [ HelperThread( StatusMonitor, timeout_seconds=plugin_config.status_timeout, heartbeat_interval=plugin_config.status_heartbeat, ) ] # Only want to run the MongoPruner if it would do anything tasks, run_every = db.prune_tasks(**config.get("db.ttl")) if run_every: self.helper_threads.append( HelperThread( db.get_pruner(), tasks=tasks, run_every=timedelta(minutes=run_every) ) ) metrics_config = config.get("metrics") if metrics_config.prometheus.enabled: self.helper_threads.append( HelperThread( PrometheusServer, metrics_config.prometheus.host, metrics_config.prometheus.port, ) ) beer_garden.router.forward_processor = QueueListener( action=beer_garden.router.forward, name="forwarder" ) self.mp_manager = self._setup_multiprocessing_manager() beer_garden.local_plugins.manager.lpm_proxy = self.mp_manager.PluginManager() self.entry_manager = beer_garden.api.entry_point.Manager() beer_garden.events.manager = self._setup_events_manager() file_event = Event(name=Events.PLUGIN_LOGGER_FILE_CHANGE.name) self.plugin_log_config_observer = MonitorFile( path=config.get("plugin.logging.config_file"), create_event=file_event, modify_event=file_event, ) self.plugin_local_log_config_observer = MonitorFile( path=config.get("plugin.local.logging.config_file"), create_event=file_event, modify_event=file_event, )
def run(ep_conn): conn_manager = StompManager(ep_conn) _setup_event_handling(conn_manager) _setup_operation_forwarding() entry_config = config.get("entry.stomp") parent_config = config.get("parent.stomp") garden_name = config.get("garden.name") if entry_config.get("enabled"): conn_manager.add_connection(stomp_config=entry_config, name=f"{garden_name}_entry", is_main=True) if parent_config.get("enabled"): conn_manager.add_connection(stomp_config=parent_config, name=f"{garden_name}_parent", is_main=True) for garden in get_gardens(include_local=False): if garden.name != garden_name and garden.connection_type: if garden.connection_type.casefold() == "stomp": connection_params = garden.connection_params.get("stomp", {}) connection_params["send_destination"] = None conn_manager.add_connection(stomp_config=connection_params, name=garden.name) conn_manager.start() logger.info("Stomp entry point started") publish( Event(name=Events.ENTRY_STARTED.name, metadata={"entry_point_type": "STOMP"})) while not shutdown_event.wait(10): for name, info in conn_manager.conn_dict.items(): connection = info.get("conn") if connection: logger.debug(f"{name}: Checking connection") if not connection.is_connected(): logger.debug(f"{name}: Attempting to reconnect") if connection.connect(): logger.debug(f"{name}: Reconnect successful") else: logger.debug(f"{name}: Reconnect failed") conn_manager.shutdown() conn_manager.stop() conn_manager.join(5) logger.debug("Stopping forward processing") beer_garden.router.forward_processor.stop()
class TestSendEventToParent(object): event = Event( payload_type="Request", payload=Request( system="system_test", command="command_test", namespace="test", ), ) def config_get(self, config_name): return [] @pytest.fixture() def command_blocklist(self): blocklist = CommandPublishingBlockList( namespace=self.event.payload.namespace, system=self.event.payload.system, command=self.event.payload.command, ).save() yield blocklist blocklist.delete() def test_command_exists_in_blocklist(self, command_blocklist, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) assert event_blocklisted(self.event) def test_command_missing_in_blocklist(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) assert not event_blocklisted(self.event) def test_event_not_request(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) event = Event(name="ENTRY_STARTED") assert not event_blocklisted(event) def test_can_send_event_error(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) event = Event(name="REQUEST_CREATE", error=True) assert event_blocklisted(event) def test_can_send_event_to_parent(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) assert not event_blocklisted(self.event) def test_can_send_event_to_parent_blocklist(self, command_blocklist, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) assert event_blocklisted(self.event)
def test_publish_auth_enabled_publishes_event_without_payload_type(self): ws_client = yield self.ws_connect() yield ws_client.read_message() # Read the AUTHORIZATION_REQUIRED message event = Event(name="ENTRY_STARTED") EventSocket.publish(event) response = yield ws_client.read_message() ws_client.close() response_dict = json.loads(response) assert response_dict["payload"] is None assert response_dict["name"] == event.name
def publish(event: Event) -> None: """Convenience method for publishing events All this does is place the event on the queue for the process-wide manager to pick up and process. Args: event: The event to publish Returns: None """ try: # Do some formatting / tweaking if not event.garden: event.garden = config.get("garden.name") if not event.timestamp: event.timestamp = datetime.now(timezone.utc) return manager.put(event) except Exception as ex: logger.exception(f"Error publishing event: {ex}")
def shutdown(self): self.logger.debug("Disconnecting connections") for value in self.conn_dict.values(): value["conn"].disconnect() # This will almost definitely not be published because # it would need to make it up to the main process and # back down into this process. We just publish this # here in case the main process is looking for it. publish( Event( name=Events.ENTRY_STOPPED.name, metadata={"entry_point_type": "STOMP"}, ), )
def rescan(*args, **kwargs) -> List[Runner]: """Scans plugin directory and starts any new runners""" new_runners = lpm_proxy.scan_path(*args, **kwargs) for runner in new_runners: publish( Event( name=Events.RUNNER_STARTED.name, payload_type=Runner.__name__, payload=runner, ) ) return new_runners
def process(self, event: Event): # TODO - This shouldn't be set here event.garden = conf.get("garden.name") if not event_blocklisted(event): try: operation = Operation( operation_type="PUBLISH_EVENT", model=event, model_type="Event" ) self._ez_client.forward(operation) except RequestException as ex: self.logger.error(f"Error while publishing event to parent: {ex}") self._connected = False self._reconnect()
def test_status_updated_at_preserved_on_child_garden_requests( self, child_garden_request ): status_updated_at = datetime.utcnow() - timedelta(days=1) status_updated_at = status_updated_at.replace(microsecond=0) child_garden_request.status = "SUCCESS" child_garden_request.status_updated_at = status_updated_at request_event = Event( payload=child_garden_request, name=Events.REQUEST_UPDATED.name ) beer_garden.requests.handle_event(request_event) updated_request = Request.objects.get(id=child_garden_request.id) assert updated_request.status_updated_at == status_updated_at
async def startup(): """Do startup things. This is the first thing called from within the ioloop context. """ global anonymous_principal http_config = config.get("entry.http") logger.debug( f"Starting HTTP server on {http_config.host}:{http_config.port}") server.listen(http_config.port, http_config.host) logger.info("Http entry point started") publish( Event(name=Events.ENTRY_STARTED.name, metadata={"entry_point_type": "HTTP"}))
def publish_event(self, *args, **kwargs): """Publish a new event by POSTing :param args: The Event to create :param _publishers: Optional list of specific publishers. If None all publishers will be used. :param kwargs: If no Event is given in the *args, on will be constructed from the kwargs :return: The response """ publishers = kwargs.pop('_publishers', None) json_event = self.parser.serialize_event(args[0] if args else Event( **kwargs)) response = self.client.post_event(json_event, publishers=publishers) if response.ok: return True else: self._handle_response_failure(response)
def _publish_failed_forward(operation: Operation = None, error_message: str = None, event_name: str = None): if operation.operation_type == "REQUEST_CREATE": complete_request( operation.model.id, status="ERROR", output=error_message, error_class=event_name, ) publish( Event( name=event_name, payload_type="Operation", payload=operation, error_message=error_message, )) raise RoutingRequestException(error_message)
def _async_callback(task, event_type=None): event = Event(name=event_type.name) try: result = task.result() event.payload_type = result.__class__.__name__ event.payload = result except Exception as ex: event.error = True event.error_message = str(ex) finally: try: publish(event) except Exception as ex: logger.exception(f"Error publishing event: {ex}")
async def startup(): """Do startup things. This is the first thing called from within the ioloop context. """ global anonymous_principal # Need to wait until after mongo connection established to load anonymous_principal = load_anonymous() http_config = config.get("entry.http") logger.debug(f"Starting HTTP server on {http_config.host}:{http_config.port}") server.listen(http_config.port, http_config.host) logger.debug("Starting forward processor") beer_garden.router.forward_processor.start() beer_garden.api.http.logger.info("Http entry point is started. Hello!") publish( Event(name=Events.ENTRY_STARTED.name, metadata={"entry_point_type": "HTTP"}) )
def rescan(*args, **kwargs) -> List[Runner]: """Scan plugin directory and start any new runners. Args: *args: Arguments to pass to ``scan_path`` in the PluginManager object. **kwargs: Keyword arguments to pass to ``scan_path`` in the PluginManager object. Returns: A list of the new runners """ new_runners = lpm_proxy.scan_path(*args, **kwargs) for the_runner in new_runners: publish( Event( name=Events.RUNNER_STARTED.name, payload_type=Runner.__name__, payload=the_runner, )) return new_runners
def shutdown(): """Do shutdown things This still operates within the ioloop, so stopping it should be the last thing done. Because things in startup aren't guaranteed to have been run we need to be careful about checking to make sure things actually need to be shut down. This execution is normally scheduled by the signal handler. """ if request_scheduler.running: logger.info("Pausing scheduler - no more jobs will be run") yield request_scheduler.pause() logger.info("Stopping server for new HTTP connections") server.stop() if event_publishers: logger.debug("Publishing application shutdown event") event_publishers.publish_event( Event(name=Events.BREWVIEW_STOPPED.name)) logger.info("Shutting down event publishers") yield list( filter(lambda x: isinstance(x, Future), event_publishers.shutdown())) # We need to do this before the scheduler shuts down completely in order to kick any # currently waiting request creations logger.info("Closing all open HTTP connections") yield server.close_all_connections() if request_scheduler.running: logger.info("Shutting down scheduler") yield request_scheduler.shutdown(wait=False) logger.info("Stopping IO loop") io_loop.add_callback(io_loop.stop)
def startup(): """Do startup things. This is the first thing called from within the ioloop context. """ global event_publishers, anonymous_principal # Ensure we have a mongo connection logger.info("Checking for Mongo connection") yield _progressive_backoff(partial(setup_database, config), "Unable to connect to mongo, is it started?") # Need to wait until after mongo connection established to load anonymous_principal = load_anonymous() logger.info("Starting event publishers") event_publishers = _setup_event_publishers(client_ssl) logger.info("Initializing metrics") initialize_counts() logger.info("Starting metrics server on %s:%d" % (config.web.host, config.metrics.port)) start_http_server(config.metrics.port) logger.info("Starting HTTP server on %s:%d" % (config.web.host, config.web.port)) server.listen(config.web.port, config.web.host) logger.info("Starting scheduler") request_scheduler.start() logger.debug("Publishing application startup event") event_publishers.publish_event(Event(name=Events.BREWVIEW_STARTED.name)) brew_view.logger.info("Application is started. Hello!")
def wrapper(wrapped, _, args, kwargs): # Allows for conditionally disabling publishing _publish_success = kwargs.pop("_publish_success", True) _publish_error = kwargs.pop("_publish_error", True) event = Event(name=event_type.name) try: result = wrapped(*args, **kwargs) event.payload_type = result.__class__.__name__ event.payload = result return result except Exception as ex: event.error = True event.error_message = str(ex) raise finally: if (not event.error and _publish_success) or (event.error and _publish_error): publish(event)
def bg_event(event_dict, ts_dt): """An event as a model.""" dict_copy = copy.deepcopy(event_dict) dict_copy['timestamp'] = ts_dt return Event(**dict_copy)
def test_can_send_event_error(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) event = Event(name="REQUEST_CREATE", error=True) assert event_blocklisted(event)
def test_event_not_request(self, monkeypatch): monkeypatch.setattr(config, "get", self.config_get) event = Event(name="ENTRY_STARTED") assert not event_blocklisted(event)