2023-08-11 13:12:13 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
2023-07-17 22:15:22 +00:00
|
|
|
package sprawl
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
|
|
|
|
|
|
"github.com/hashicorp/consul/testing/deployer/topology"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s *Sprawl) ensureLicense() error {
|
|
|
|
if s.license != "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
v, err := readLicense()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.license = v
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func readLicense() (string, error) {
|
|
|
|
if license := os.Getenv("CONSUL_LICENSE"); license != "" {
|
|
|
|
return license, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
licensePath := os.Getenv("CONSUL_LICENSE_PATH")
|
|
|
|
if licensePath == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
|
|
|
licenseBytes, err := os.ReadFile(licensePath)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(licenseBytes)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Sprawl) initTenancies(cluster *topology.Cluster) error {
|
|
|
|
var (
|
|
|
|
client = s.clients[cluster.Name]
|
|
|
|
logger = s.logger.With("cluster", cluster.Name)
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO: change this to UPSERT
|
|
|
|
|
|
|
|
var (
|
|
|
|
partClient = client.Partitions()
|
|
|
|
nsClient = client.Namespaces()
|
|
|
|
|
|
|
|
partitionNameList []string
|
|
|
|
)
|
|
|
|
for _, ap := range cluster.Partitions {
|
|
|
|
if ap.Name != "default" {
|
|
|
|
old, _, err := partClient.Read(context.Background(), ap.Name, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error reading partition %q: %w", ap.Name, err)
|
|
|
|
}
|
|
|
|
if old == nil {
|
|
|
|
obj := &api.Partition{
|
|
|
|
Name: ap.Name,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _, err := partClient.Create(context.Background(), obj, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating partition %q: %w", ap.Name, err)
|
|
|
|
}
|
|
|
|
logger.Info("created partition", "partition", ap.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
partitionNameList = append(partitionNameList, ap.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.createCrossNamespaceCatalogReadPolicies(cluster, ap.Name); err != nil {
|
|
|
|
return fmt.Errorf("createCrossNamespaceCatalogReadPolicies[%s]: %w", ap.Name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ns := range ap.Namespaces {
|
|
|
|
old, _, err := nsClient.Read(ns, &api.QueryOptions{Partition: ap.Name})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if old == nil {
|
|
|
|
obj := &api.Namespace{
|
|
|
|
Partition: ap.Name,
|
|
|
|
Name: ns,
|
|
|
|
ACLs: &api.NamespaceACLConfig{
|
|
|
|
PolicyDefaults: []api.ACLLink{
|
|
|
|
{Name: "cross-ns-catalog-read"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
if ns == "default" {
|
|
|
|
_, _, err := nsClient.Update(obj, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logger.Info("updated namespace", "namespace", ns, "partition", ap.Name)
|
|
|
|
} else {
|
|
|
|
_, _, err := nsClient.Create(obj, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
logger.Info("created namespace", "namespace", ns, "partition", ap.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := s.waitUntilPartitionedSerfIsReady(context.TODO(), cluster, partitionNameList); err != nil {
|
|
|
|
return fmt.Errorf("waitUntilPartitionedSerfIsReady: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Sprawl) waitUntilPartitionedSerfIsReady(ctx context.Context, cluster *topology.Cluster, partitions []string) error {
|
|
|
|
var (
|
|
|
|
logger = s.logger.With("cluster", cluster.Name)
|
|
|
|
)
|
|
|
|
|
|
|
|
readyLogs := make(map[string]string)
|
|
|
|
for _, partition := range partitions {
|
|
|
|
readyLogs[partition] = `agent.server: Added serf partition to gossip network: partition=` + partition
|
|
|
|
}
|
|
|
|
|
|
|
|
start := time.Now()
|
|
|
|
logger.Info("waiting for partitioned serf to be ready on all servers", "partitions", partitions)
|
|
|
|
for _, node := range cluster.Nodes {
|
|
|
|
if !node.IsServer() || node.Disabled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
|
|
|
for {
|
|
|
|
buf.Reset()
|
|
|
|
|
|
|
|
err := s.runner.DockerExec(ctx, []string{
|
|
|
|
"logs", node.DockerName(),
|
|
|
|
}, &buf, nil)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not fetch docker logs from node %q: %w", node.ID(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
body = buf.String()
|
|
|
|
found []string
|
|
|
|
)
|
|
|
|
|
|
|
|
for partition, readyLog := range readyLogs {
|
|
|
|
if strings.Contains(body, readyLog) {
|
|
|
|
found = append(found, partition)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(found) == len(readyLogs) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.Info("partitioned serf is ready on all servers", "partitions", partitions, "elapsed", time.Since(start))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|