blob: 391307a345b5b7defa016251bf1adfb444589424 [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package namespace
import (
"context"
"fmt"
"io"
"math/rand"
"strings"
"sync"
"time"
)
import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-multierror"
"istio.io/api/label"
"istio.io/pkg/log"
kubeApiCore "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
import (
"github.com/apache/dubbo-go-pixiu/pkg/test/framework/resource"
kube2 "github.com/apache/dubbo-go-pixiu/pkg/test/kube"
"github.com/apache/dubbo-go-pixiu/pkg/test/scopes"
)
var (
idctr int64
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
mu sync.Mutex
)
// kubeNamespace represents a Kubernetes namespace. It is tracked as a resource.
type kubeNamespace struct {
id resource.ID
name string
prefix string
ctx resource.Context
}
func (n *kubeNamespace) Dump(ctx resource.Context) {
scopes.Framework.Errorf("=== Dumping Namespace %s State...", n.name)
d, err := ctx.CreateTmpDirectory(n.name + "-state")
if err != nil {
scopes.Framework.Errorf("Unable to create directory for dumping %s contents: %v", n.name, err)
return
}
kube2.DumpPods(n.ctx, d, n.name, []string{})
kube2.DumpDeployments(n.ctx, d, n.name)
}
var (
_ Instance = &kubeNamespace{}
_ io.Closer = &kubeNamespace{}
_ resource.Resource = &kubeNamespace{}
_ resource.Dumper = &kubeNamespace{}
)
func (n *kubeNamespace) Name() string {
return n.name
}
func (n *kubeNamespace) Prefix() string {
return n.prefix
}
func (n *kubeNamespace) Labels() (map[string]string, error) {
perCluster := make([]map[string]string, len(n.ctx.Clusters()))
errG := multierror.Group{}
for i, cluster := range n.ctx.Clusters() {
i, cluster := i, cluster
errG.Go(func() error {
ns, err := cluster.CoreV1().Namespaces().Get(context.TODO(), n.Name(), metav1.GetOptions{})
if err != nil {
return err
}
perCluster[i] = ns.Labels
return nil
})
}
if err := errG.Wait().ErrorOrNil(); err != nil {
return nil, err
}
for i, clusterLabels := range perCluster {
if i == 0 {
continue
}
if diff := cmp.Diff(perCluster[0], clusterLabels); diff != "" {
log.Warnf("namespace labels are different across clusters:\n%s", diff)
}
}
return perCluster[0], nil
}
func (n *kubeNamespace) SetLabel(key, value string) error {
return n.setNamespaceLabel(key, value)
}
func (n *kubeNamespace) RemoveLabel(key string) error {
return n.removeNamespaceLabel(key)
}
func (n *kubeNamespace) ID() resource.ID {
return n.id
}
// Close implements io.Closer
func (n *kubeNamespace) Close() (err error) {
if n.name != "" {
scopes.Framework.Debugf("%s deleting namespace", n.id)
ns := n.name
n.name = ""
for _, c := range n.ctx.Clusters().Kube() {
err = c.CoreV1().Namespaces().Delete(context.TODO(), ns, kube2.DeleteOptionsForeground())
}
}
scopes.Framework.Debugf("%s close complete (err:%v)", n.id, err)
return
}
func claimKube(ctx resource.Context, nsConfig *Config) (Instance, error) {
for _, cluster := range ctx.Clusters().Kube() {
if !kube2.NamespaceExists(cluster, nsConfig.Prefix) {
if _, err := cluster.CoreV1().Namespaces().Create(context.TODO(), &kubeApiCore.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: nsConfig.Prefix,
Labels: createNamespaceLabels(ctx, nsConfig),
},
}, metav1.CreateOptions{}); err != nil {
return nil, err
}
}
}
return &kubeNamespace{prefix: nsConfig.Prefix, name: nsConfig.Prefix, ctx: ctx}, nil
}
// setNamespaceLabel labels a namespace with the given key, value pair
func (n *kubeNamespace) setNamespaceLabel(key, value string) error {
// need to convert '/' to '~1' as per the JSON patch spec http://jsonpatch.com/#operations
jsonPatchEscapedKey := strings.ReplaceAll(key, "/", "~1")
for _, cluster := range n.ctx.Clusters().Kube() {
nsLabelPatch := fmt.Sprintf(`[{"op":"replace","path":"/metadata/labels/%s","value":"%s"}]`, jsonPatchEscapedKey, value)
if _, err := cluster.CoreV1().Namespaces().Patch(context.TODO(), n.name, types.JSONPatchType, []byte(nsLabelPatch), metav1.PatchOptions{}); err != nil {
return err
}
}
return nil
}
// removeNamespaceLabel removes namespace label with the given key
func (n *kubeNamespace) removeNamespaceLabel(key string) error {
// need to convert '/' to '~1' as per the JSON patch spec http://jsonpatch.com/#operations
jsonPatchEscapedKey := strings.ReplaceAll(key, "/", "~1")
for _, cluster := range n.ctx.Clusters().Kube() {
nsLabelPatch := fmt.Sprintf(`[{"op":"remove","path":"/metadata/labels/%s"}]`, jsonPatchEscapedKey)
if _, err := cluster.CoreV1().Namespaces().Patch(context.TODO(), n.name, types.JSONPatchType, []byte(nsLabelPatch), metav1.PatchOptions{}); err != nil {
return err
}
}
return nil
}
// NewNamespace allocates a new testing namespace.
func newKube(ctx resource.Context, nsConfig *Config) (Instance, error) {
mu.Lock()
idctr++
nsid := idctr
r := rnd.Intn(99999)
mu.Unlock()
ns := fmt.Sprintf("%s-%d-%d", nsConfig.Prefix, nsid, r)
n := &kubeNamespace{
name: ns,
prefix: nsConfig.Prefix,
ctx: ctx,
}
id := ctx.TrackResource(n)
n.id = id
s := ctx.Settings()
for _, cluster := range n.ctx.Clusters().Kube() {
if _, err := cluster.CoreV1().Namespaces().Create(context.TODO(), &kubeApiCore.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: ns,
Labels: createNamespaceLabels(ctx, nsConfig),
},
}, metav1.CreateOptions{}); err != nil {
return nil, err
}
if s.Image.PullSecret != "" {
if err := cluster.ApplyYAMLFiles(n.name, s.Image.PullSecret); err != nil {
return nil, err
}
}
}
return n, nil
}
// createNamespaceLabels will take a namespace config and generate the proper k8s labels
func createNamespaceLabels(ctx resource.Context, cfg *Config) map[string]string {
l := make(map[string]string)
l["istio-testing"] = "istio-test"
if cfg.Inject {
// do not add namespace labels when running compatibility tests since
// this disables the necessary object selectors
if !ctx.Settings().Compatibility {
if cfg.Revision != "" {
l[label.IoIstioRev.Name] = cfg.Revision
} else {
l["istio-injection"] = "enabled"
}
}
} else {
// if we're running compatibility tests, disable injection in the namespace
// explicitly so that object selectors are ignored
if ctx.Settings().Compatibility {
l["istio-injection"] = "disabled"
}
}
// bring over supplied labels
for k, v := range cfg.Labels {
l[k] = v
}
return l
}