diff --git a/compute_tools/src/http/api.rs b/compute_tools/src/http/api.rs
index 7fa6426d8f..43e9fc42fc 100644
--- a/compute_tools/src/http/api.rs
+++ b/compute_tools/src/http/api.rs
@@ -310,6 +310,28 @@ async fn routes(req: Request
, compute: &Arc) -> Response {
+ info!("serving /installed_extensions HEAD request");
+ let status = compute.get_status();
+ if status != ComputeStatus::Running {
+ let msg = format!(
+ "invalid compute status for extensions request: {:?}",
+ status
+ );
+ error!(msg);
+ let mut resp = Response::new(Body::from(msg));
+ *resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
+ return resp;
+ }
+
+ let connstr = compute.connstr.clone();
+ // should I rewrite this to not wait for the result?
+ let _ = crate::installed_extensions::get_installed_extensions(connstr).await;
+ Response::new(Body::from("OK"))
+ }
+
// download extension files from remote extension storage on demand
(&Method::POST, route) if route.starts_with("/extension_server/") => {
info!("serving {:?} POST request", route);
diff --git a/compute_tools/src/http/openapi_spec.yaml b/compute_tools/src/http/openapi_spec.yaml
index 24a67cac71..b3b952d2e9 100644
--- a/compute_tools/src/http/openapi_spec.yaml
+++ b/compute_tools/src/http/openapi_spec.yaml
@@ -82,6 +82,21 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/InstalledExtensions"
+ head:
+ tags:
+ - Extension
+ summary: Report extension DDL to trigger metric recollection.
+ description: ""
+ operationId: ExtensionDDL
+ responses:
+ 200:
+ description: Result
+ 500:
+ description: Internal error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/GenericError"
/info:
get:
tags:
diff --git a/pgxn/neon/extension_server.c b/pgxn/neon/extension_server.c
index e38af08f89..754e184a75 100644
--- a/pgxn/neon/extension_server.c
+++ b/pgxn/neon/extension_server.c
@@ -12,7 +12,9 @@
#include
+#include "access/xact.h"
#include "utils/guc.h"
+#include "tcop/utility.h"
#include "extension_server.h"
#include "neon_utils.h"
@@ -21,6 +23,10 @@ static int extension_server_port = 0;
static download_extension_file_hook_type prev_download_extension_file_hook = NULL;
+static ProcessUtility_hook_type PreviousProcessUtilityHook = NULL;
+
+static bool extension_ddl_occured = false;
+
/*
* to download all SQL (and data) files for an extension:
* curl -X POST http://localhost:8080/extension_server/postgis
@@ -74,9 +80,142 @@ neon_download_extension_file_http(const char *filename, bool is_library)
return ret;
}
+
+// Handle extension DDL: we need this for monitoring of installed extensions.
+// General solution is hard, because extensions can be installed in many ways,
+// i.e. sometimes as a cascade operations.
+//
+// Also, we don't have enough information in the statement itself,
+// i.e. extension version is not always present and retrieved from the control file
+// at a later stage.
+//
+// So, just remember the fact of the extension DDL and send it to compute_ctl
+// on commit.
+static void
+NeonExtensionProcessUtility(
+ PlannedStmt *pstmt,
+ const char *queryString,
+ bool readOnlyTree,
+ ProcessUtilityContext context,
+ ParamListInfo params,
+ QueryEnvironment *queryEnv,
+ DestReceiver *dest,
+ QueryCompletion *qc)
+{
+ Node *parseTree = pstmt->utilityStmt;
+
+ switch (nodeTag(parseTree))
+ {
+ case T_CreateExtensionStmt:
+ case T_AlterExtensionStmt:
+ extension_ddl_occured = true;
+ break;
+ case T_DropStmt:
+ {
+ switch (((DropStmt *) parseTree)->removeType)
+ {
+ case OBJECT_EXTENSION:
+ extension_ddl_occured = true;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (PreviousProcessUtilityHook)
+ {
+ PreviousProcessUtilityHook(
+ pstmt,
+ queryString,
+ readOnlyTree,
+ context,
+ params,
+ queryEnv,
+ dest,
+ qc);
+ }
+ else
+ {
+ standard_ProcessUtility(
+ pstmt,
+ queryString,
+ readOnlyTree,
+ context,
+ params,
+ queryEnv,
+ dest,
+ qc);
+ }
+}
+
+
+
+static bool
+neon_send_extension_ddl_event_http()
+{
+ static CURL *handle = NULL;
+
+ CURLcode res;
+ char *compute_ctl_url;
+ bool ret = false;
+
+ if (handle == NULL)
+ {
+ handle = alloc_curl_handle();
+ }
+
+ compute_ctl_url = psprintf("http://localhost:%d/installed_extensions",
+ extension_server_port);
+
+ elog(LOG, "Sending request to compute_ctl: %s", compute_ctl_url);
+
+ curl_easy_setopt(handle, CURLOPT_URL, compute_ctl_url);
+
+ // Use HEAD request without payload, because this is just a notification.
+ //
+ // This is probably not the best API design, but I didn't want to introduce
+ // new endpoint for this. Suggestions are welcome.
+ curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
+
+ /* Perform the request, res will get the return code */
+ res = curl_easy_perform(handle);
+
+ /* Check for errors */
+ if (res != CURLE_OK)
+ {
+ /*
+ * Don't error here because this is just a monitoring feature.
+ */
+ elog(WARNING, "neon_send_extension_ddl_event_http failed: %s\n", curl_easy_strerror(res));
+ }
+
+ return ret;
+}
+
+static void
+NeonExtensionXactCallback(XactEvent event, void *arg)
+{
+ if (event == XACT_EVENT_PRE_COMMIT || event == XACT_EVENT_PARALLEL_PRE_COMMIT)
+ {
+ if (extension_ddl_occured)
+ neon_send_extension_ddl_event_http();
+ }
+
+ extension_ddl_occured = false;
+}
+
void
pg_init_extension_server()
{
+
+ PreviousProcessUtilityHook = ProcessUtility_hook;
+ ProcessUtility_hook = NeonExtensionProcessUtility;
+ RegisterXactCallback(NeonExtensionXactCallback, NULL);
+
/* Port to connect to compute_ctl on localhost */
/* to request extension files. */
DefineCustomIntVariable("neon.extension_server_port",