Skip to content

GODRIVER-2906 Add container Env to Handshake. #1382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 79 additions & 16 deletions x/mongo/driver/operation/hello.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,23 @@ const (
envNameVercel = "vercel"
)

const dockerEnvPath = "/.dockerenv"
const envVarK8s = "KUBERNETES_SERVICE_HOST"

const (
// Runtime names
runtimeNameDocker = "docker"

// Orchestrator names
orchestratorNameK8s = "kubernetes"
)

// getFaasEnvName parses the FaaS environment variable name and returns the
// corresponding name used by the client. If none of the variables or variables
// for multiple names are populated the client.env value MUST be entirely
// omitted. When variables for multiple "client.env.name" values are present,
// "vercel" takes precedence over "aws.lambda"; any other combination MUST cause
// "client.env" to be entirely omitted.
// for multiple names are populated the FaaS values MUST be entirely omitted.
// When variables for multiple "client.env.name" values are present, "vercel"
// takes precedence over "aws.lambda"; any other combination MUST cause FaaS
// values to be entirely omitted.
func getFaasEnvName() string {
envVars := []string{
envVarAWSExecutionEnv,
Expand Down Expand Up @@ -218,6 +229,31 @@ func getFaasEnvName() string {
return ""
}

type containerInfo struct {
runtime string
orchestrator string
}

// getContainerEnvInfo returns runtime and orchestrator of a container.
// If no fields is populated, the client.env.container value MUST be entirely
// omitted.
func getContainerEnvInfo() *containerInfo {
var runtime, orchestrator string
if _, err := os.Stat(dockerEnvPath); !os.IsNotExist(err) {
runtime = runtimeNameDocker
}
if v := os.Getenv(envVarK8s); v != "" {
orchestrator = orchestratorNameK8s
}
if runtime != "" || orchestrator != "" {
return &containerInfo{
runtime: runtime,
orchestrator: orchestrator,
}
}
return nil
}

// appendClientAppName appends the application metadata to the dst. It is the
// responsibility of the caller to check that this appending does not cause dst
// to exceed any size limitations.
Expand Down Expand Up @@ -256,14 +292,20 @@ func appendClientEnv(dst []byte, omitNonName, omitDoc bool) ([]byte, error) {
}

name := getFaasEnvName()
if name == "" {
container := getContainerEnvInfo()
// Omit the entire 'env' if both name and container are empty because other
// fields depend on either of them.
if name == "" && container == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line implies that we wouldn't append the client environment if the name and the container are unavailable. However, both name and container were made optional as part of DRIVERS-2570: https://github.com/mongodb/specifications/pull/1454/files#diff-0ecfac0976ff0bfe5b91b5bd52bc5a79dd1125434505ff22331c7cadf1b2f25eR170

I think we should completely remove this block.

If tests are failing, such as the handshake integration tests, it's because there is an expectation that they should contain a name which is now incorrect.

Copy link
Collaborator Author

@qingyang-hu qingyang-hu Sep 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid it's ambiguous. My understanding is that the entire client.env can be omitted because:

  1. The spec states the env is optional.
  2. Logically, we don't have to keep the env if it has nothing.

However, I will ask for clarification because we don't have a lot of reference implementations now.

Spec says:

If no fields of client.env would be populated, client.env MUST be entirely omitted. ref

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, so because the other fields in env depend on either name or container, if both are empty then implicitly the other fields will be empty. Could we add a comment to that effect?

return dst, nil
}

var idx int32

idx, dst = bsoncore.AppendDocumentElementStart(dst, "env")
dst = bsoncore.AppendStringElement(dst, "name", name)

if name != "" {
dst = bsoncore.AppendStringElement(dst, "name", name)
}

addMem := func(envVar string) []byte {
mem := os.Getenv(envVar)
Expand Down Expand Up @@ -306,6 +348,7 @@ func appendClientEnv(dst []byte, omitNonName, omitDoc bool) ([]byte, error) {
}

if !omitNonName {
// No other FaaS fields will be populated if the name is empty.
switch name {
case envNameAWSLambda:
dst = addMem(envVarAWSLambdaFunctionMemorySize)
Expand All @@ -319,6 +362,22 @@ func appendClientEnv(dst []byte, omitNonName, omitDoc bool) ([]byte, error) {
}
}

if container != nil {
var idxCntnr int32
idxCntnr, dst = bsoncore.AppendDocumentElementStart(dst, "container")
if container.runtime != "" {
dst = bsoncore.AppendStringElement(dst, "runtime", container.runtime)
}
if container.orchestrator != "" {
dst = bsoncore.AppendStringElement(dst, "orchestrator", container.orchestrator)
}
var err error
dst, err = bsoncore.AppendDocumentEnd(dst, idxCntnr)
if err != nil {
return dst, err
}
}

return bsoncore.AppendDocumentEnd(dst, idx)
}

Expand Down Expand Up @@ -358,21 +417,25 @@ func appendClientPlatform(dst []byte) []byte {
// name: "<string>"
// },
// driver: {
// name: "<string>",
// version: "<string>"
// name: "<string>",
// version: "<string>"
// },
// platform: "<string>",
// os: {
// type: "<string>",
// name: "<string>",
// architecture: "<string>",
// version: "<string>"
// type: "<string>",
// name: "<string>",
// architecture: "<string>",
// version: "<string>"
// },
// env: {
// name: "<string>",
// timeout_sec: 42,
// memory_mb: 1024,
// region: "<string>",
// name: "<string>",
// timeout_sec: 42,
// memory_mb: 1024,
// region: "<string>",
// container: {
// runtime: "<string>",
// orchestrator: "<string>"
// }
// }
// }
func encodeClientMetadata(appname string, maxLen int) ([]byte, error) {
Expand Down
50 changes: 43 additions & 7 deletions x/mongo/driver/operation/hello_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ func TestAppendClientEnv(t *testing.T) {
},
want: []byte(`{"env":{"name":"azure.func"}}`),
},
{
name: "k8s",
env: map[string]string{
envVarK8s: "0.0.0.0",
},
want: []byte(`{"env":{"container":{"orchestrator":"kubernetes"}}}`),
},
// client.env.container.runtime is untested.
}

for _, test := range tests {
Expand Down Expand Up @@ -382,11 +390,17 @@ func TestEncodeClientMetadata(t *testing.T) {
Architecture string `bson:"architecture,omitempty"`
}

type container struct {
Runtime string `bson:"runtime,omitempty"`
Orchestrator string `bson:"orchestrator,omitempty"`
}

type env struct {
Name string `bson:"name,omitempty"`
TimeoutSec int64 `bson:"timeout_sec,omitempty"`
MemoryMB int32 `bson:"memory_mb,omitempty"`
Region string `bson:"region,omitempty"`
Name string `bson:"name,omitempty"`
TimeoutSec int64 `bson:"timeout_sec,omitempty"`
MemoryMB int32 `bson:"memory_mb,omitempty"`
Region string `bson:"region,omitempty"`
Container *container `bson:"container,omitempty"`
}

type clientMetadata struct {
Expand All @@ -408,6 +422,7 @@ func TestEncodeClientMetadata(t *testing.T) {
t.Setenv(envVarAWSLambdaRuntimeAPI, "lambda")
t.Setenv(envVarAWSLambdaFunctionMemorySize, "123")
t.Setenv(envVarAWSRegion, "us-east-2")
t.Setenv(envVarK8s, "0.0.0.0")

t.Run("nothing is omitted", func(t *testing.T) {
got, err := encodeClientMetadata("foo", maxClientMetadataSize)
Expand All @@ -418,7 +433,14 @@ func TestEncodeClientMetadata(t *testing.T) {
Driver: &driver{Name: driverName, Version: version.Driver},
OS: &dist{Type: runtime.GOOS, Architecture: runtime.GOARCH},
Platform: runtime.Version(),
Env: &env{Name: envNameAWSLambda, MemoryMB: 123, Region: "us-east-2"},
Env: &env{
Name: envNameAWSLambda,
MemoryMB: 123,
Region: "us-east-2",
Container: &container{
Orchestrator: "kubernetes",
},
},
})

assertDocsEqual(t, got, want)
Expand All @@ -437,7 +459,12 @@ func TestEncodeClientMetadata(t *testing.T) {
Driver: &driver{Name: driverName, Version: version.Driver},
OS: &dist{Type: runtime.GOOS, Architecture: runtime.GOARCH},
Platform: runtime.Version(),
Env: &env{Name: envNameAWSLambda},
Env: &env{
Name: envNameAWSLambda,
Container: &container{
Orchestrator: "kubernetes",
},
},
})

assertDocsEqual(t, got, want)
Expand All @@ -454,6 +481,10 @@ func TestEncodeClientMetadata(t *testing.T) {

// Calculate what the env.name costs.
ndst := bsoncore.AppendStringElement(nil, "name", envNameAWSLambda)
idx, ndst := bsoncore.AppendDocumentElementStart(ndst, "container")
ndst = bsoncore.AppendStringElement(ndst, "orchestrator", "kubernetes")
ndst, err = bsoncore.AppendDocumentEnd(ndst, idx)
require.NoError(t, err)

// Environment sub name.
envSubName := len(edst) - len(ndst)
Expand All @@ -466,7 +497,12 @@ func TestEncodeClientMetadata(t *testing.T) {
Driver: &driver{Name: driverName, Version: version.Driver},
OS: &dist{Type: runtime.GOOS},
Platform: runtime.Version(),
Env: &env{Name: envNameAWSLambda},
Env: &env{
Name: envNameAWSLambda,
Container: &container{
Orchestrator: "kubernetes",
},
},
})

assertDocsEqual(t, got, want)
Expand Down