Skip to content

Commit

Permalink
feat: ResourceConsist Controller (#24)
Browse files Browse the repository at this point in the history
* feat: ResourceConsist Controller
  • Loading branch information
WeichengWang1 committed Aug 28, 2023
1 parent 19eef7c commit cc066e5
Show file tree
Hide file tree
Showing 24 changed files with 2,638 additions and 13 deletions.
2 changes: 1 addition & 1 deletion apis/apps/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ package v1alpha1

// +kubebuilder:object:generate=false
type PodAvailableConditions struct {
ExpectedFinalizers []string `json:"expectedFinalizers,omitempty"` // indicate the expected finalizers of a pod
ExpectedFinalizers map[string]string `json:"expectedFinalizers,omitempty"` // indicate the expected finalizers of a pod
}
2 changes: 2 additions & 0 deletions apis/apps/v1alpha1/well_known_labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package v1alpha1

// pod ops lifecyle labels
const (
ControlledByKusionStackLabelKey = "kusionstack.io/control"

ControlledByPodOpsLifecycle = "podopslifecycle.kusionstack.io/control" // indicate a pod is controlled by podopslifecycle

PodOperatingLabelPrefix = "operating.podopslifecycle.kusionstack.io" // indicate a pod is operating
Expand Down
24 changes: 19 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module kusionstack.io/kafed
go 1.19

require (
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.4
github.com/alibabacloud-go/slb-20140515/v4 v4.0.3
github.com/alibabacloud-go/tea v1.2.1
github.com/alibabacloud-go/tea-utils/v2 v2.0.4
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.8.1+incompatible
github.com/go-logr/logr v1.2.3
Expand All @@ -13,7 +17,7 @@ require (
github.com/prometheus/client_golang v1.14.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.1
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/net v0.11.0
k8s.io/api v0.22.6
k8s.io/apimachinery v0.22.6
k8s.io/client-go v0.22.6
Expand All @@ -32,8 +36,16 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
github.com/alibabacloud-go/tea-xml v1.1.2 // indirect
github.com/aliyun/credentials-go v1.1.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/clbanning/mxj/v2 v2.5.5 // indirect
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
Expand All @@ -55,19 +67,21 @@ require (
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/tjfoc/gmsm v1.3.2 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.0 // indirect
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/time v0.3.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.56.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
86 changes: 80 additions & 6 deletions go.sum

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions pkg/controllers/add_alibaba_cloud_slb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2023 The KusionStack 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 controllers

import (
"sigs.k8s.io/controller-runtime/pkg/manager"

"kusionstack.io/kafed/pkg/controllers/alibaba_cloud_slb"
"kusionstack.io/kafed/pkg/controllers/resourceconsist"
)

func init() {
AddToManagerFuncs = append(AddToManagerFuncs, Add)
}

func Add(manager manager.Manager) error {
return resourceconsist.Add(manager, alibaba_cloud_slb.NewReconcileAdapter(manager.GetClient()))
}
94 changes: 94 additions & 0 deletions pkg/controllers/alibaba_cloud_slb/alibaba_cloud_slb_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
Copyright 2023 The KusionStack 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 alibaba_cloud_slb

import (
"fmt"
"os"

openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
slb20140515 "github.com/alibabacloud-go/slb-20140515/v4/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
)

var (
slbAccessKeyID string
slbAccessKeySecret string
slbEndpoint string

alibabaCloudSlbClient *AlibabaCloudSlbClient
)

type AlibabaCloudSlbClient struct {
*slb20140515.Client
}

func NewAlibabaCloudSlbClient() (*AlibabaCloudSlbClient, error) {
if slbAccessKeyID == "" || slbAccessKeySecret == "" {
return nil, fmt.Errorf("slbAccessKeyID or slbAccessKeySecret is empty")
}
config := &openapi.Config{
AccessKeyId: tea.String(slbAccessKeyID),
AccessKeySecret: tea.String(slbAccessKeySecret),
// Endpoint, refer: https://api.aliyun.com/product/Slb
Endpoint: tea.String(slbEndpoint),
}

slbClient, err := slb20140515.NewClient(config)
if err != nil {
return nil, err
}
return &AlibabaCloudSlbClient{
slbClient,
}, nil
}

func (c *AlibabaCloudSlbClient) GetBackendServers(lbID string) ([]string, error) {
describeHealthStatusRequest := &slb20140515.DescribeHealthStatusRequest{
LoadBalancerId: tea.String(lbID),
}
runtime := &util.RuntimeOptions{}

resp, err := c.DescribeHealthStatusWithOptions(describeHealthStatusRequest, runtime)
if err != nil {
return nil, err
}
if resp == nil || resp.Body == nil || resp.Body.BackendServers == nil {
return nil, fmt.Errorf("get backend servers list faield, resp is nil")
}

backendServers := make([]string, len(resp.Body.BackendServers.BackendServer))
for idx, bs := range resp.Body.BackendServers.BackendServer {
backendServers[idx] = *bs.ServerIp
}
return backendServers, nil
}

func init() {
if os.Getenv("ALIYUN_SLB_AK_KEY") != "" {
slbAccessKeyID = os.Getenv("ALIYUN_SLB_AK_KEY")
}
if os.Getenv("ALIYUN_SLB_AK_SECRET") != "" {
slbAccessKeySecret = os.Getenv("ALIYUN_SLB_AK_SECRET")
}
if os.Getenv("ALIYUN_SLB_ENDPOINT") != "" {
slbEndpoint = os.Getenv("ALIYUN_SLB_ENDPOINT")
}

alibabaCloudSlbClient, _ = NewAlibabaCloudSlbClient()
}
191 changes: 191 additions & 0 deletions pkg/controllers/alibaba_cloud_slb/alibaba_cloud_slb_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
Copyright 2023 The KusionStack 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 alibaba_cloud_slb

import (
"context"
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"

"kusionstack.io/kafed/pkg/controllers/resourceconsist"
)

var _ resourceconsist.ReconcileAdapter = &ReconcileAdapter{}

type ReconcileAdapter struct {
client.Client
}

func NewReconcileAdapter(c client.Client) *ReconcileAdapter {
return &ReconcileAdapter{
Client: c,
}
}

func (r *ReconcileAdapter) GetControllerName() string {
return "alibaba-cloud-slb-controller"
}

func (r *ReconcileAdapter) NotFollowPodOpsLifeCycle() bool {
return false
}

func (r *ReconcileAdapter) GetExpectEmployer(ctx context.Context, employer client.Object) ([]resourceconsist.IEmployer, error) {
return nil, nil
}

func (r *ReconcileAdapter) GetSelectedEmployeeNames(ctx context.Context, employer client.Object) ([]string, error) {
svc, ok := employer.(*corev1.Service)
if !ok {
return nil, fmt.Errorf("expect employer kind is Service")
}
selector := labels.Set(svc.Spec.Selector).AsSelectorPreValidated()
var podList corev1.PodList
err := r.List(ctx, &podList, &client.ListOptions{Namespace: svc.Namespace, LabelSelector: selector})
if err != nil {
return nil, err
}

selected := make([]string, len(podList.Items))
for idx, pod := range podList.Items {
selected[idx] = pod.Name
}

return selected, nil
}

func (r *ReconcileAdapter) GetCurrentEmployer(ctx context.Context, employer client.Object) ([]resourceconsist.IEmployer, error) {
return nil, nil
}

func (r *ReconcileAdapter) CreateEmployer(employer client.Object, toCreate []resourceconsist.IEmployer) ([]resourceconsist.IEmployer, []resourceconsist.IEmployer, error) {
return nil, nil, nil
}

func (r *ReconcileAdapter) UpdateEmployer(employer client.Object, toUpdate []resourceconsist.IEmployer) ([]resourceconsist.IEmployer, []resourceconsist.IEmployer, error) {
return nil, nil, nil
}

func (r *ReconcileAdapter) DeleteEmployer(employer client.Object, toDelete []resourceconsist.IEmployer) ([]resourceconsist.IEmployer, []resourceconsist.IEmployer, error) {
return nil, nil, nil
}

func (r *ReconcileAdapter) RecordEmployer(succCreate, succUpdate, succDelete []resourceconsist.IEmployer) error {
return nil
}

func (r *ReconcileAdapter) GetExpectEmployee(ctx context.Context, employer client.Object) ([]resourceconsist.IEmployee, error) {
svc, ok := employer.(*corev1.Service)
if !ok {
return nil, fmt.Errorf("expect employer kind is Service")
}
selector := labels.Set(svc.Spec.Selector).AsSelectorPreValidated()
var podList corev1.PodList
err := r.List(ctx, &podList, &client.ListOptions{Namespace: svc.Namespace, LabelSelector: selector})
if err != nil {
return nil, err
}

expected := make([]resourceconsist.IEmployee, len(podList.Items))
for idx, pod := range podList.Items {
status := AlibabaSlbPodStatus{
EmployeeID: pod.Status.PodIP,
EmployeeName: pod.Name,
}
employeeStatuses, err := resourceconsist.GetCommonPodEmployeeStatus(&pod)
if err != nil {
return nil, err
}
extraStatus := PodExtraStatus{}
if employeeStatuses.LifecycleReady {
extraStatus.TrafficOn = true
} else {
extraStatus.TrafficOn = false
}
employeeStatuses.ExtraStatus = extraStatus
status.EmployeeStatuses = employeeStatuses
expected[idx] = status
}

return expected, nil
}

func (r *ReconcileAdapter) GetCurrentEmployee(ctx context.Context, employer client.Object) ([]resourceconsist.IEmployee, error) {
svc, ok := employer.(*corev1.Service)
if !ok {
return nil, fmt.Errorf("expect employer kind is Service")
}
selector := labels.Set(svc.Spec.Selector).AsSelectorPreValidated()
var podList corev1.PodList
err := r.List(ctx, &podList, &client.ListOptions{Namespace: svc.Namespace, LabelSelector: selector})
if err != nil {
return nil, err
}

lbID := svc.GetLabels()[alibabaCloudSlbLbIdLabelKey]
bsExistUnderSlb := make(map[string]bool)
if lbID != "" {
backendServers, err := alibabaCloudSlbClient.GetBackendServers(lbID)
if err != nil {
return nil, fmt.Errorf("get backend servers of slb failed, err: %s", err.Error())
}
for _, bs := range backendServers {
bsExistUnderSlb[bs] = true
}
}

current := make([]resourceconsist.IEmployee, len(podList.Items))
for idx, pod := range podList.Items {
status := AlibabaSlbPodStatus{
EmployeeID: pod.Status.PodIP,
EmployeeName: pod.Name,
}
employeeStatuses, err := resourceconsist.GetCommonPodEmployeeStatus(&pod)
if err != nil {
return nil, err
}
extraStatus := PodExtraStatus{}
if !bsExistUnderSlb[status.EmployeeID] {
extraStatus.TrafficOn = false
} else {
extraStatus.TrafficOn = true
}
employeeStatuses.ExtraStatus = extraStatus
status.EmployeeStatuses = employeeStatuses
current[idx] = status
}

return current, nil
}

// CreateEmployees returns (nil, toCreate, nil) since CCM of ACK will sync bs of slb
func (r *ReconcileAdapter) CreateEmployees(employer client.Object, toCreate []resourceconsist.IEmployee) ([]resourceconsist.IEmployee, []resourceconsist.IEmployee, error) {
return nil, toCreate, nil
}

// UpdateEmployees returns (nil, toUpdate, nil) since CCM of ACK will sync bs of slb
func (r *ReconcileAdapter) UpdateEmployees(employer client.Object, toUpdate []resourceconsist.IEmployee) ([]resourceconsist.IEmployee, []resourceconsist.IEmployee, error) {
return nil, toUpdate, nil
}

// DeleteEmployees returns (nil, toDelete, nil) since CCM of ACK will sync bs of slb
func (r *ReconcileAdapter) DeleteEmployees(employer client.Object, toDelete []resourceconsist.IEmployee) ([]resourceconsist.IEmployee, []resourceconsist.IEmployee, error) {
return nil, toDelete, nil
}
Loading

0 comments on commit cc066e5

Please sign in to comment.