2023-03-28 22:48:58 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-11-01 19:03:23 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2023-01-20 22:02:44 +00:00
|
|
|
"io"
|
2023-01-11 21:34:27 +00:00
|
|
|
"strconv"
|
2022-11-01 19:03:23 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/testcontainers/testcontainers-go"
|
|
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
|
|
|
|
|
|
|
libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster"
|
2022-11-01 19:03:23 +00:00
|
|
|
"github.com/hashicorp/consul/test/integration/consul-container/libs/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
// exampleContainer
|
|
|
|
type exampleContainer struct {
|
2023-01-04 20:28:15 +00:00
|
|
|
ctx context.Context
|
|
|
|
container testcontainers.Container
|
|
|
|
ip string
|
|
|
|
httpPort int
|
|
|
|
grpcPort int
|
|
|
|
serviceName string
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
var _ Service = (*exampleContainer)(nil)
|
|
|
|
|
2023-02-14 22:22:09 +00:00
|
|
|
func (g exampleContainer) Exec(ctx context.Context, cmd []string) (string, error) {
|
|
|
|
exitCode, reader, err := g.container.Exec(ctx, cmd)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("exec with error %s", err)
|
|
|
|
}
|
|
|
|
if exitCode != 0 {
|
|
|
|
return "", fmt.Errorf("exec with exit code %d", exitCode)
|
|
|
|
}
|
|
|
|
buf, err := io.ReadAll(reader)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error reading from exec output: %w", err)
|
|
|
|
}
|
|
|
|
return string(buf), nil
|
|
|
|
}
|
|
|
|
|
2023-01-20 22:02:44 +00:00
|
|
|
func (g exampleContainer) Export(partition, peerName string, client *api.Client) error {
|
|
|
|
config := &api.ExportedServicesConfigEntry{
|
|
|
|
Name: partition,
|
|
|
|
Services: []api.ExportedService{{
|
|
|
|
Name: g.GetServiceName(),
|
|
|
|
Consumers: []api.ServiceConsumer{
|
|
|
|
// TODO: need to handle the changed field name in 1.13
|
|
|
|
{Peer: peerName},
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _, err := client.ConfigEntries().Set(config, &api.WriteOptions{})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g exampleContainer) GetAddr() (string, int) {
|
|
|
|
return g.ip, g.httpPort
|
|
|
|
}
|
|
|
|
|
2023-02-22 15:22:25 +00:00
|
|
|
func (g exampleContainer) GetAddrs() (string, []int) {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2023-02-24 20:57:44 +00:00
|
|
|
func (g exampleContainer) GetPort(port int) (int, error) {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
2023-01-27 16:25:48 +00:00
|
|
|
func (g exampleContainer) Restart() error {
|
|
|
|
return fmt.Errorf("Restart Unimplemented by ConnectContainer")
|
|
|
|
}
|
|
|
|
|
2023-01-20 22:02:44 +00:00
|
|
|
func (g exampleContainer) GetLogs() (string, error) {
|
2023-02-01 15:48:54 +00:00
|
|
|
rc, err := g.container.Logs(g.ctx)
|
2023-01-20 22:02:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not get logs for example service %s: %w", g.GetServiceName(), err)
|
|
|
|
}
|
|
|
|
defer rc.Close()
|
|
|
|
|
|
|
|
out, err := io.ReadAll(rc)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("could not read from logs for example service %s: %w", g.GetServiceName(), err)
|
|
|
|
}
|
|
|
|
return string(out), nil
|
|
|
|
}
|
|
|
|
|
2022-11-01 19:03:23 +00:00
|
|
|
func (g exampleContainer) GetName() string {
|
|
|
|
name, err := g.container.Name(g.ctx)
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2023-01-20 22:02:44 +00:00
|
|
|
func (g exampleContainer) GetServiceName() string {
|
|
|
|
return g.serviceName
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (g exampleContainer) Start() error {
|
|
|
|
if g.container == nil {
|
|
|
|
return fmt.Errorf("container has not been initialized")
|
|
|
|
}
|
|
|
|
return g.container.Start(context.Background())
|
|
|
|
}
|
|
|
|
|
2023-02-15 15:26:43 +00:00
|
|
|
func (g exampleContainer) Stop() error {
|
|
|
|
if g.container == nil {
|
|
|
|
return fmt.Errorf("container has not been initialized")
|
|
|
|
}
|
|
|
|
return g.container.Stop(context.Background(), nil)
|
|
|
|
}
|
|
|
|
|
2022-11-01 19:03:23 +00:00
|
|
|
func (c exampleContainer) Terminate() error {
|
2023-05-11 22:49:49 +00:00
|
|
|
return libcluster.TerminateContainer(c.ctx, c.container, true)
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
|
|
|
|
2023-02-01 15:48:54 +00:00
|
|
|
func (c exampleContainer) GetStatus() (string, error) {
|
|
|
|
state, err := c.container.State(c.ctx)
|
|
|
|
return state.Status, err
|
|
|
|
}
|
|
|
|
|
2023-02-24 20:57:44 +00:00
|
|
|
func NewExampleService(ctx context.Context, name string, httpPort int, grpcPort int, node libcluster.Agent, containerArgs ...string) (Service, error) {
|
2022-11-01 19:03:23 +00:00
|
|
|
namePrefix := fmt.Sprintf("%s-service-example-%s", node.GetDatacenter(), name)
|
|
|
|
containerName := utils.RandName(namePrefix)
|
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
pod := node.GetPod()
|
|
|
|
if pod == nil {
|
|
|
|
return nil, fmt.Errorf("node Pod is required")
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
httpPortStr = strconv.Itoa(httpPort)
|
|
|
|
grpcPortStr = strconv.Itoa(grpcPort)
|
|
|
|
)
|
|
|
|
|
2023-02-24 20:57:44 +00:00
|
|
|
command := []string{
|
|
|
|
"server",
|
|
|
|
"-http-port", httpPortStr,
|
|
|
|
"-grpc-port", grpcPortStr,
|
|
|
|
"-redirect-port", "-disabled",
|
|
|
|
}
|
|
|
|
|
|
|
|
command = append(command, containerArgs...)
|
|
|
|
|
2022-11-01 19:03:23 +00:00
|
|
|
req := testcontainers.ContainerRequest{
|
|
|
|
Image: hashicorpDockerProxy + "/fortio/fortio",
|
2023-05-11 22:49:49 +00:00
|
|
|
WaitingFor: wait.ForLog("").WithStartupTimeout(60 * time.Second),
|
2022-11-01 19:03:23 +00:00
|
|
|
AutoRemove: false,
|
|
|
|
Name: containerName,
|
2023-02-24 20:57:44 +00:00
|
|
|
Cmd: command,
|
|
|
|
Env: map[string]string{"FORTIO_NAME": name},
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
|
|
|
|
2023-05-11 22:49:49 +00:00
|
|
|
info, err := libcluster.LaunchContainerOnNode(ctx, node, req, []string{httpPortStr, grpcPortStr})
|
2022-11-01 19:03:23 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
out := &exampleContainer{
|
|
|
|
ctx: ctx,
|
|
|
|
container: info.Container,
|
|
|
|
ip: info.IP,
|
|
|
|
httpPort: info.MappedPorts[httpPortStr].Int(),
|
|
|
|
grpcPort: info.MappedPorts[grpcPortStr].Int(),
|
|
|
|
serviceName: name,
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
fmt.Printf("Example service exposed http port %d, gRPC port %d\n", out.httpPort, out.grpcPort)
|
2022-11-01 19:03:23 +00:00
|
|
|
|
2023-01-11 21:34:27 +00:00
|
|
|
return out, nil
|
2022-11-01 19:03:23 +00:00
|
|
|
}
|
2023-01-18 21:13:55 +00:00
|
|
|
|
|
|
|
func (g exampleContainer) GetAdminAddr() (string, int) {
|
|
|
|
return "localhost", 0
|
|
|
|
}
|