Merge pull request #4230 from RolandMa1986/fix-gateway-upgrade

feat: upgrade gateway with all legacy settings
This commit is contained in:
KubeSphere CI Bot
2021-09-15 10:35:51 +08:00
committed by GitHub
2 changed files with 259 additions and 16 deletions

View File

@@ -40,10 +40,11 @@ import (
)
const (
SidecarInject = "sidecar.istio.io/inject"
gatewayPrefix = "kubesphere-router-"
workingNamespace = "kubesphere-controls-system"
globalGatewayname = gatewayPrefix + "kubesphere-system"
helmPatch = `{"metadata":{"annotations":{"meta.helm.sh/release-name":"%s-ingress","meta.helm.sh/release-namespace":"%s"},"labels":{"helm.sh/chart":"ingress-nginx-3.35.0","app.kubernetes.io/managed-by":"Helm","app":null,"component":null,"tier":null}}}`
helmPatch = `{"metadata":{"annotations":{"meta.helm.sh/release-name":"%s-ingress","meta.helm.sh/release-namespace":"%s"},"labels":{"helm.sh/chart":"ingress-nginx-3.35.0","app.kubernetes.io/managed-by":"Helm","app":null,"component":null,"tier":null}},"spec":{"selector":null}}`
)
type GatewayOperator interface {
@@ -121,12 +122,15 @@ func (c *gatewayOperator) getLegacyGateway(namespace string) *v1alpha1.Gateway {
// create a fake Gateway object when legacy service exists
if len(s.Items) > 0 {
return c.convert(namespace, &s.Items[0])
d := &appsv1.Deployment{}
c.client.Get(context.TODO(), client.ObjectKeyFromObject(&s.Items[0]), d)
return c.convert(namespace, &s.Items[0], d)
}
return nil
}
func (c *gatewayOperator) convert(namespace string, svc *corev1.Service) *v1alpha1.Gateway {
func (c *gatewayOperator) convert(namespace string, svc *corev1.Service, deploy *appsv1.Deployment) *v1alpha1.Gateway {
legacy := v1alpha1.Gateway{
TypeMeta: v1.TypeMeta{
Kind: "",
@@ -147,8 +151,15 @@ func (c *gatewayOperator) convert(namespace string, svc *corev1.Service) *v1alph
Annotations: svc.Annotations,
Type: svc.Spec.Type,
},
Deployment: v1alpha1.DeploymentSpec{
Replicas: deploy.Spec.Replicas,
},
},
}
if an, ok := deploy.Annotations[SidecarInject]; ok {
legacy.Spec.Deployment.Annotations = make(map[string]string)
legacy.Spec.Deployment.Annotations[SidecarInject] = an
}
return &legacy
}
@@ -228,14 +239,25 @@ func (c *gatewayOperator) UpgradeGateway(namespace string) (*v1alpha1.Gateway, e
return nil, fmt.Errorf("invalid operation, can't upgrade legacy gateway when working namespace changed")
}
// Get legency gateway's config from configmap
cm := &corev1.ConfigMap{}
err := c.client.Get(context.TODO(), client.ObjectKey{Namespace: l.Namespace, Name: fmt.Sprintf("%s-nginx", l.Name)}, cm)
if err == nil {
l.Spec.Conroller.Config = cm.Data
defer func() {
c.client.Delete(context.TODO(), cm)
}()
}
// Delete old deployment, because it's not compatile with the deployment in the helm chart.
// We can't defer here, there's a potential race condition causing gateway operator fails.
d := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Namespace: l.Namespace,
Name: l.Name,
},
}
err := c.client.Delete(context.TODO(), d)
err = c.client.Delete(context.TODO(), d)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
@@ -259,14 +281,14 @@ func (c *gatewayOperator) UpgradeGateway(namespace string) (*v1alpha1.Gateway, e
}
func (c *gatewayOperator) ListGateways(query *query.Query) (*api.ListResult, error) {
applications := v1alpha1.GatewayList{}
err := c.cache.List(context.TODO(), &applications, &client.ListOptions{LabelSelector: query.Selector()})
gateways := v1alpha1.GatewayList{}
err := c.cache.List(context.TODO(), &gateways, &client.ListOptions{LabelSelector: query.Selector()})
if err != nil {
return nil, err
}
var result []runtime.Object
for i := range applications.Items {
result = append(result, &applications.Items[i])
for i := range gateways.Items {
result = append(result, &gateways.Items[i])
}
services := &corev1.ServiceList{}
@@ -282,29 +304,40 @@ func (c *gatewayOperator) ListGateways(query *query.Query) (*api.ListResult, err
})
for _, s := range services.Items {
g := c.convert(s.Labels["project"], &s)
result = append(result, g)
result = append(result, &s)
}
return v1alpha3.DefaultList(result, query, c.compare, c.filter), nil
return v1alpha3.DefaultList(result, query, c.compare, c.filter, c.transform), nil
}
func (d *gatewayOperator) compare(left runtime.Object, right runtime.Object, field query.Field) bool {
func (c *gatewayOperator) transform(obj runtime.Object) runtime.Object {
if g, ok := obj.(*v1alpha1.Gateway); ok {
return g
}
if s, ok := obj.(*corev1.Service); ok {
d := &appsv1.Deployment{}
c.client.Get(context.TODO(), client.ObjectKeyFromObject(s), d)
return c.convert(s.Labels["project"], s, d)
}
return nil
}
leftApplication, ok := left.(*v1alpha1.Gateway)
func (c *gatewayOperator) compare(left runtime.Object, right runtime.Object, field query.Field) bool {
leftGateway, ok := left.(*v1alpha1.Gateway)
if !ok {
return false
}
rightApplication, ok := right.(*v1alpha1.Gateway)
rightGateway, ok := right.(*v1alpha1.Gateway)
if !ok {
return false
}
return v1alpha3.DefaultObjectMetaCompare(leftApplication.ObjectMeta, rightApplication.ObjectMeta, field)
return v1alpha3.DefaultObjectMetaCompare(leftGateway.ObjectMeta, rightGateway.ObjectMeta, field)
}
func (d *gatewayOperator) filter(object runtime.Object, filter query.Filter) bool {
func (c *gatewayOperator) filter(object runtime.Object, filter query.Filter) bool {
gateway, ok := object.(*v1alpha1.Gateway)
if !ok {
return false

View File

@@ -26,11 +26,15 @@ import (
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/diff"
"kubesphere.io/api/gateway/v1alpha1"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/query"
"kubesphere.io/kubesphere/pkg/simple/client/gateway"
)
@@ -239,6 +243,45 @@ func create_LegacyGateway(c client.Client, namespace string) {
},
}
c.Create(context.TODO(), s)
d := &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprint(gatewayPrefix, namespace),
Namespace: workingNamespace,
Annotations: map[string]string{
SidecarInject: "true",
},
Labels: map[string]string{
"app": "kubesphere",
"component": "ks-router",
"tier": "backend",
"project": namespace,
},
},
Spec: appsv1.DeploymentSpec{
Replicas: &[]int32{1}[0],
},
}
c.Create(context.TODO(), d)
}
func create_LegacyGatewayConfigMap(c client.Client, namespace string) {
s := &corev1.ConfigMap{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprint(gatewayPrefix, namespace, "-nginx"),
Namespace: workingNamespace,
Labels: map[string]string{
"app": "kubesphere",
"component": "ks-router",
"tier": "backend",
"project": namespace,
},
},
Data: map[string]string{
"fake": "true",
},
}
c.Create(context.TODO(), s)
}
func Test_gatewayOperator_CreateGateway(t *testing.T) {
@@ -508,6 +551,7 @@ func Test_gatewayOperator_UpgradeGateway(t *testing.T) {
appsv1.AddToScheme(Scheme)
client2 := fake.NewFakeClientWithScheme(Scheme)
create_LegacyGateway(client2, "project2")
create_LegacyGatewayConfigMap(client2, "project2")
tests := []struct {
name string
@@ -552,6 +596,9 @@ func Test_gatewayOperator_UpgradeGateway(t *testing.T) {
Enabled: true,
Namespace: "project2",
},
Config: map[string]string{
"fake": "true",
},
},
Service: v1alpha1.ServiceSpec{
Annotations: map[string]string{
@@ -559,6 +606,12 @@ func Test_gatewayOperator_UpgradeGateway(t *testing.T) {
},
Type: corev1.ServiceTypeNodePort,
},
Deployment: v1alpha1.DeploymentSpec{
Replicas: &[]int32{1}[0],
Annotations: map[string]string{
"sidecar.istio.io/inject": "true",
},
},
},
},
},
@@ -580,3 +633,160 @@ func Test_gatewayOperator_UpgradeGateway(t *testing.T) {
})
}
}
func Test_gatewayOperator_ListGateways(t *testing.T) {
type fields struct {
client client.Client
cache cache.Cache
options *gateway.Options
}
type args struct {
query *query.Query
}
var Scheme = runtime.NewScheme()
v1alpha1.AddToScheme(Scheme)
corev1.AddToScheme(Scheme)
appsv1.AddToScheme(Scheme)
client := fake.NewFakeClientWithScheme(Scheme)
create_LegacyGateway(client, "project2")
client.Create(context.TODO(), &v1alpha1.Gateway{
ObjectMeta: v1.ObjectMeta{
Name: "kubesphere-router-project1",
Namespace: "project1",
},
})
gates := []*v1alpha1.Gateway{
{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprint(gatewayPrefix, "project2"),
Namespace: "kubesphere-controls-system",
},
Spec: v1alpha1.GatewaySpec{
Conroller: v1alpha1.ControllerSpec{
Scope: v1alpha1.Scope{
Enabled: true,
Namespace: "project2",
},
},
Service: v1alpha1.ServiceSpec{
Annotations: map[string]string{
"fake": "true",
},
Type: corev1.ServiceTypeNodePort,
},
Deployment: v1alpha1.DeploymentSpec{
Replicas: &[]int32{1}[0],
Annotations: map[string]string{
SidecarInject: "true",
},
},
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: fmt.Sprint(gatewayPrefix, "project1"),
Namespace: "project1",
ResourceVersion: "1",
},
},
}
items := make([]interface{}, 0)
for _, obj := range gates {
items = append(items, obj)
}
tests := []struct {
name string
fields fields
args args
want *api.ListResult
wantErr bool
}{
{
name: "list all gateways",
fields: fields{
client: client,
cache: &fakeClient{Client: client},
options: &gateway.Options{
Namespace: "kubesphere-controls-system",
},
},
args: args{
query: &query.Query{},
},
want: &api.ListResult{
TotalItems: 2,
Items: items,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &gatewayOperator{
client: tt.fields.client,
cache: tt.fields.cache,
options: tt.fields.options,
}
got, err := c.ListGateways(tt.args.query)
if (err != nil) != tt.wantErr {
t.Errorf("gatewayOperator.ListGateways() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("gatewayOperator.ListGateways() has wrong object\nDiff:\n %s", diff.ObjectGoPrintSideBySide(tt.want, got))
}
})
}
}
type fakeClient struct {
Client client.Client
}
// Get retrieves an obj for the given object key from the Kubernetes Cluster.
// obj must be a struct pointer so that obj can be updated with the response
// returned by the Server.
func (f *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
return f.Client.Get(ctx, key, obj)
}
// List retrieves list of objects for a given namespace and list options. On a
// successful call, Items field in the list will be populated with the
// result returned from the server.
func (f *fakeClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
return f.Client.List(ctx, list, opts...)
}
// GetInformer fetches or constructs an informer for the given object that corresponds to a single
// API kind and resource.
func (f *fakeClient) GetInformer(ctx context.Context, obj client.Object) (cache.Informer, error) {
return nil, nil
}
// GetInformerForKind is similar to GetInformer, except that it takes a group-version-kind, instead
// of the underlying object.
func (f *fakeClient) GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind) (cache.Informer, error) {
return nil, nil
}
// Start runs all the informers known to this cache until the context is closed.
// It blocks.
func (f *fakeClient) Start(ctx context.Context) error {
return nil
}
// WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache.
func (f *fakeClient) WaitForCacheSync(ctx context.Context) bool {
return false
}
func (f *fakeClient) IndexField(ctx context.Context, obj client.Object, field string, extractValue client.IndexerFunc) error {
return nil
}