Skip to content

Commit

Permalink
dynamic_modules: scaffolds Go SDK
Browse files Browse the repository at this point in the history
Signed-off-by: Takeshi Yoneda <[email protected]>
  • Loading branch information
mathetake committed Sep 13, 2024
1 parent 0ad67a1 commit 98a6850
Show file tree
Hide file tree
Showing 18 changed files with 213 additions and 5 deletions.
10 changes: 9 additions & 1 deletion source/extensions/dynamic_modules/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ extern "C" {
#include <stddef.h>
#endif

#ifdef ENVOY_DYNAMIC_MODULE_GO_SDK
// Use uintptr_t to represent a raw pointer in C for simplicity in Go bindings.
typedef uintptr_t envoy_dynamic_module_raw_pointer;
#else
typedef void* envoy_dynamic_module_raw_pointer; // NOLINT(modernize-use-using)
#endif

// -----------------------------------------------------------------------------
// ---------------------------------- Types ------------------------------------
// -----------------------------------------------------------------------------
Expand All @@ -32,7 +39,8 @@ extern "C" {
* version of the dynamic module. This is used to ensure that the dynamic module is built against
* the compatible version of the ABI.
*/
typedef const char* envoy_dynamic_module_type_abi_version; // NOLINT(modernize-use-using)
typedef envoy_dynamic_module_raw_pointer // NOLINT(modernize-use-using)
envoy_dynamic_module_type_abi_version;

// -----------------------------------------------------------------------------
// ------------------------------- Event Hooks ---------------------------------
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/dynamic_modules/abi_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace DynamicModules {
#endif
// This is the ABI version calculated as a sha256 hash of the ABI header files. When the ABI
// changes, this value must change, and the correctness of this value is checked by the test.
const char* kAbiVersion = "749b1e6bf97309b7d171009700a80e651ac61e35f9770c24a63460d765895a51";
const char* kAbiVersion = "7bf4504e9874e385f15c4a835da3c4dfe9480a3d7262d46b18740d7192866649";

#ifdef __cplusplus
} // namespace DynamicModules
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/dynamic_modules/dynamic_modules.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ absl::StatusOr<DynamicModuleSharedPtr> newDynamicModule(const absl::string_view
absl::StrCat("Failed to resolve envoy_dynamic_module_on_program_init: ", dlerror()));
}

const char* abi_version = (*init_function)();
const char* abi_version = static_cast<const char*>((*init_function)());
if (abi_version == nullptr) {
return absl::InvalidArgumentError(
absl::StrCat("Failed to initialize dynamic module: ", object_file_path));
Expand Down
13 changes: 13 additions & 0 deletions source/extensions/dynamic_modules/sdk/go/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")

licenses(["notice"]) # Apache 2

exports_files(["abi.h"]) # Exported for tests.

go_library(
name = "envoy_dynamic_modules_go_sdk",
srcs = glob(["*.go"]) + ["abi.h"],
cgo = True,
importpath = "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go",
visibility = ["//visibility:public"],
)
20 changes: 20 additions & 0 deletions source/extensions/dynamic_modules/sdk/go/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//go:build cgo

package gosdk

/*
#define ENVOY_DYNAMIC_MODULE_GO_SDK 1
#include "abi.h"
*/
import "C"
import "unsafe"

var abiVersion = append([]byte(string("7bf4504e9874e385f15c4a835da3c4dfe9480a3d7262d46b18740d7192866649")), 0)

//export envoy_dynamic_module_on_program_init
func envoy_dynamic_module_on_program_init() C.envoy_dynamic_module_type_abi_version {
if OnProgramInit() {
return C.envoy_dynamic_module_type_abi_version(uintptr(unsafe.Pointer(&abiVersion[0])))
}
return 0
}
78 changes: 78 additions & 0 deletions source/extensions/dynamic_modules/sdk/go/abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

// NOLINT(namespace-envoy)

// This is a pure C header file that defines the ABI of the core of dynamic modules used by Envoy.
//
// This must not contain any dependencies besides standard library since it is not only used by
// Envoy itself but also by dynamic module SDKs written in non-C++ languages.
//
// Currently, compatibility is only guaranteed by an exact version match between the Envoy
// codebase and the dynamic module SDKs. In the future, after the ABI is stabilized, we will revisit
// this restriction and hopefully provide a wider compatibility guarantee. Until then, Envoy
// checks the hash of the ABI header files to ensure that the dynamic modules are built against the
// same version of the ABI.

#ifdef __cplusplus
#include <cstddef>
#include <cstdint>

extern "C" {
#else
#include <stddef.h>
#include <stdint.h>
#endif

#ifdef ENVOY_DYNAMIC_MODULE_GO_SDK
// Use uintptr_t to represent a raw pointer in C for simplicity in Go bindings.
typedef uintptr_t envoy_dynamic_module_raw_pointer;
#else
typedef void* envoy_dynamic_module_raw_pointer; // NOLINT(modernize-use-using)
#endif

// -----------------------------------------------------------------------------
// ---------------------------------- Types ------------------------------------
// -----------------------------------------------------------------------------
//
// Types used in the ABI. The name of a type must be prefixed with "envoy_dynamic_module_type_".

/**
* envoy_dynamic_module_type_abi_version represents a null-terminated string that contains the ABI
* version of the dynamic module. This is used to ensure that the dynamic module is built against
* the compatible version of the ABI.
*/
typedef envoy_dynamic_module_raw_pointer // NOLINT(modernize-use-using)
envoy_dynamic_module_type_abi_version;

// -----------------------------------------------------------------------------
// ------------------------------- Event Hooks ---------------------------------
// -----------------------------------------------------------------------------
//
// Event hooks are functions that are called by Envoy in response to certain events.
// The module must implement and export these functions in the dynamic module object file.
//
// Each event hook is defined as a function prototype. The symbol must be prefixed with
// "envoy_dynamic_module_on_".

/**
* envoy_dynamic_module_on_program_init is called by the main thread exactly when the module is
* loaded. The function returns the ABI version of the dynamic module. If null is returned, the
* module will be unloaded immediately.
*
* For Envoy, the return value will be used to check the compatibility of the dynamic module.
*
* For dynamic modules, this is useful when they need to perform some process-wide
* initialization or check if the module is compatible with the platform, such as CPU features.
* Note that initialization routines of a dynamic module can also be performed without this function
* through constructor functions in an object file. However, normal constructors cannot be used
* to check compatibility and gracefully fail the initialization because there is no way to
* report an error to Envoy.
*
* @return envoy_dynamic_module_type_abi_version is the ABI version of the dynamic module. Null
* means the error and the module will be unloaded immediately.
*/
envoy_dynamic_module_type_abi_version envoy_dynamic_module_on_program_init();

#ifdef __cplusplus
}
#endif
3 changes: 3 additions & 0 deletions source/extensions/dynamic_modules/sdk/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go

go 1.22.5
3 changes: 3 additions & 0 deletions source/extensions/dynamic_modules/sdk/go/gosdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gosdk

var OnProgramInit func() bool
2 changes: 1 addition & 1 deletion source/extensions/dynamic_modules/sdk/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ macro_rules! declare_program_init {
if ($f()) {
// This magic number is sha256 of the ABI headers which must match the
// value in abi_version.h
b"749b1e6bf97309b7d171009700a80e651ac61e35f9770c24a63460d765895a51\0".as_ptr()
b"7bf4504e9874e385f15c4a835da3c4dfe9480a3d7262d46b18740d7192866649\0".as_ptr()
as *const ::std::os::raw::c_char
} else {
::std::ptr::null()
Expand Down
4 changes: 4 additions & 0 deletions test/extensions/dynamic_modules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ envoy_cc_test(
"//test/extensions/dynamic_modules/test_data/c:no_op",
"//test/extensions/dynamic_modules/test_data/c:no_program_init",
"//test/extensions/dynamic_modules/test_data/c:program_init_fail",
"//test/extensions/dynamic_modules/test_data/go:abi_version_mismatch",
"//test/extensions/dynamic_modules/test_data/go:no_op",
"//test/extensions/dynamic_modules/test_data/go:no_program_init",
"//test/extensions/dynamic_modules/test_data/go:program_init_fail",
"//test/extensions/dynamic_modules/test_data/rust:abi_version_mismatch",
"//test/extensions/dynamic_modules/test_data/rust:no_op",
"//test/extensions/dynamic_modules/test_data/rust:no_program_init",
Expand Down
6 changes: 5 additions & 1 deletion test/extensions/dynamic_modules/dynamic_modules_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,15 @@ class DynamicModuleTestLanguages : public ::testing::TestWithParam<std::string>
};

INSTANTIATE_TEST_SUITE_P(LanguageTests, DynamicModuleTestLanguages,
testing::Values("c", "rust"), // TODO: add Go.
testing::Values("c", "rust", "go"),
DynamicModuleTestLanguages::languageParamToTestName);

TEST_P(DynamicModuleTestLanguages, DoNotClose) {
std::string language = GetParam();
if (language == "go") {
// Go does not support RTLD_NODELETE.
return;
}
using GetSomeVariableFuncType = int (*)();
absl::StatusOr<DynamicModuleSharedPtr> module =
newDynamicModule(testSharedObjectPath("no_op", language), false);
Expand Down
13 changes: 13 additions & 0 deletions test/extensions/dynamic_modules/test_data/go/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
load("//test/extensions/dynamic_modules/test_data/go:test_data.bzl", "test_program")

licenses(["notice"]) # Apache 2

package(default_visibility = ["//test/extensions/dynamic_modules:__pkg__"])

test_program(name = "no_op")

test_program(name = "no_program_init")

test_program(name = "program_init_fail")

test_program(name = "abi_version_mismatch")
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import "C"
import "unsafe"

func main() {} // c-shared must define the empty main function.

//export envoy_dynamic_module_on_program_init
func envoy_dynamic_module_on_program_init() uintptr {
version := append([]byte("invalid-version-hash"), 0)
return uintptr(unsafe.Pointer(&version[0]))
}
9 changes: 9 additions & 0 deletions test/extensions/dynamic_modules/test_data/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module main

go 1.22.5

require github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go v0.0.0

replace (
github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go => ../../../../../source/extensions/dynamic_modules/sdk/go/
)
11 changes: 11 additions & 0 deletions test/extensions/dynamic_modules/test_data/go/no_op.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
gosdk "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go"
)

func main() {} // c-shared must define the empty main function.

func init() {
gosdk.OnProgramInit = func() bool { return true }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package main

func main() {} // c-shared must define the empty main function.
11 changes: 11 additions & 0 deletions test/extensions/dynamic_modules/test_data/go/program_init_fail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
gosdk "github.com/envoyproxy/envoy/source/extensions/dynamic_modules/sdk/go"
)

func main() {} // c-shared must define the empty main function.

func init() {
gosdk.OnProgramInit = func() bool { return false }
}
16 changes: 16 additions & 0 deletions test/extensions/dynamic_modules/test_data/go/test_data.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary")

# This declares a go_binary target that is used to build a shared Go library.
# name + ".go" is the source file that is compiled to create the shared library.
def test_program(name):
go_binary(
name = name,
srcs = [name + ".go"],
out = "lib" + name + ".so",
cgo = True,
importpath = "github.com/envoyproxy/envoy/test/extensions/dynmic_modules/sdk/go",
linkmode = "c-shared",
deps = [
"//source/extensions/dynamic_modules/sdk/go:envoy_dynamic_modules_go_sdk",
],
)

0 comments on commit 98a6850

Please sign in to comment.