karmada/operator/pkg/tasks/init/component.go

323 lines
9.5 KiB
Go

/*
Copyright 2023 The Karmada 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 tasks
import (
"encoding/base64"
"errors"
"fmt"
"time"
"k8s.io/klog/v2"
"github.com/karmada-io/karmada/operator/pkg/constants"
"github.com/karmada-io/karmada/operator/pkg/controlplane"
"github.com/karmada-io/karmada/operator/pkg/controlplane/metricsadapter"
"github.com/karmada-io/karmada/operator/pkg/controlplane/search"
"github.com/karmada-io/karmada/operator/pkg/controlplane/webhook"
"github.com/karmada-io/karmada/operator/pkg/karmadaresource/apiservice"
"github.com/karmada-io/karmada/operator/pkg/util/apiclient"
"github.com/karmada-io/karmada/operator/pkg/workflow"
)
// NewComponentTask init a components task
func NewComponentTask() workflow.Task {
return workflow.Task{
Name: "components",
Run: runComponents,
RunSubTasks: true,
Tasks: []workflow.Task{
newComponentSubTask(constants.KubeControllerManagerComponent),
newComponentSubTask(constants.KarmadaControllerManagerComponent),
newComponentSubTask(constants.KarmadaSchedulerComponent),
{
Name: constants.KarmadaWebhookComponent,
Run: runKarmadaWebhook,
},
newComponentSubTask(constants.KarmadaDeschedulerComponent),
newKarmadaMetricsAdapterSubTask(),
newKarmadaSearchSubTask(),
},
}
}
func runComponents(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("components task invoked with an invalid data struct")
}
klog.V(4).InfoS("[components] Running components task", "karmada", klog.KObj(data))
return nil
}
func newComponentSubTask(component string) workflow.Task {
return workflow.Task{
Name: component,
Run: runComponentSubTask(component),
}
}
func runComponentSubTask(component string) func(r workflow.RunData) error {
return func(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("components task invoked with an invalid data struct")
}
err := controlplane.EnsureControlPlaneComponent(
component,
data.GetName(),
data.GetNamespace(),
data.FeatureGates(),
data.RemoteClient(),
data.Components(),
)
if err != nil {
return fmt.Errorf("failed to apply component %s, err: %w", component, err)
}
klog.V(2).InfoS("[components] Successfully applied component", "component", component, "karmada", klog.KObj(data))
return nil
}
}
func runKarmadaWebhook(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("KarmadaWebhook task invoked with an invalid data struct")
}
cfg := data.Components()
if cfg.KarmadaWebhook == nil {
return errors.New("skip install karmada webhook")
}
err := webhook.EnsureKarmadaWebhook(
data.RemoteClient(),
cfg.KarmadaWebhook,
data.GetName(),
data.GetNamespace(),
data.FeatureGates(),
)
if err != nil {
return fmt.Errorf("failed to apply karmada webhook, err: %w", err)
}
klog.V(2).InfoS("[KarmadaWebhook] Successfully applied karmada webhook component", "karmada", klog.KObj(data))
return nil
}
func newKarmadaMetricsAdapterSubTask() workflow.Task {
return workflow.Task{
Name: constants.KarmadaMetricsAdapterComponent,
Run: runKarmadaMetricsAdapter,
RunSubTasks: true,
Tasks: []workflow.Task{
{
Name: "DeployMetricAdapter",
Run: runDeployMetricAdapter,
},
{
Name: "DeployMetricAdapterAPIService",
Run: runDeployMetricAdapterAPIService,
},
},
}
}
func runKarmadaMetricsAdapter(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("karmadaMetricsAdapter task invoked with an invalid data struct")
}
klog.V(4).InfoS("[karmadaMetricsAdapter] Running karmadaMetricsAdapter task", "karmada", klog.KObj(data))
return nil
}
func runDeployMetricAdapter(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("DeployMetricAdapter task invoked with an invalid data struct")
}
cfg := data.Components()
if cfg.KarmadaMetricsAdapter == nil {
klog.V(2).InfoS("[karmadaMetricsAdapter] Skip install karmada-metrics-adapter component")
return nil
}
err := metricsadapter.EnsureKarmadaMetricAdapter(
data.RemoteClient(),
cfg.KarmadaMetricsAdapter,
data.GetName(),
data.GetNamespace(),
)
if err != nil {
return fmt.Errorf("failed to apply karmada-metrics-adapter, err: %w", err)
}
klog.V(2).InfoS("[DeployMetricAdapter] Successfully applied karmada-metrics-adapter component", "karmada", klog.KObj(data))
if *cfg.KarmadaMetricsAdapter.Replicas != 0 {
waiter := apiclient.NewKarmadaWaiter(data.ControlplaneConfig(), data.RemoteClient(), time.Second*30)
if err = waiter.WaitForSomePods(karmadaMetricAdapterLabels.String(), data.GetNamespace(), 1); err != nil {
return fmt.Errorf("waiting for karmada-metrics-adapter to ready timeout, err: %w", err)
}
klog.V(2).InfoS("[DeployMetricAdapter] the karmada-metrics-adapter is ready", "karmada", klog.KObj(data))
}
return nil
}
func runDeployMetricAdapterAPIService(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("DeployMetricAdapterAPIService task invoked with an invalid data struct")
}
cfg := data.Components()
if cfg.KarmadaMetricsAdapter == nil {
klog.V(2).InfoS("[karmadaMetricsAdapter] Skip install karmada-metrics-adapter APIService")
return nil
}
config := data.ControlplaneConfig()
client, err := apiclient.NewAPIRegistrationClient(config)
if err != nil {
return err
}
cert := data.GetCert(constants.CaCertAndKeyName)
if len(cert.CertData()) == 0 {
return errors.New("unexpected empty ca cert data for aggregatedAPIService")
}
caBase64 := base64.StdEncoding.EncodeToString(cert.CertData())
err = apiservice.EnsureMetricsAdapterAPIService(client, data.KarmadaClient(), data.GetName(), constants.KarmadaSystemNamespace, data.GetName(), data.GetNamespace(), caBase64)
if err != nil {
return fmt.Errorf("failed to apply karmada-metrics-adapter APIService resource to karmada controlplane, err: %w", err)
}
if *cfg.KarmadaMetricsAdapter.Replicas != 0 {
waiter := apiclient.NewKarmadaWaiter(config, nil, time.Second*20)
for _, gv := range constants.KarmadaMetricsAdapterAPIServices {
apiServiceName := fmt.Sprintf("%s.%s", gv.Version, gv.Group)
if err := waiter.WaitForAPIService(apiServiceName); err != nil {
return fmt.Errorf("the APIService %s is unhealthy, err: %w", apiServiceName, err)
}
}
klog.V(2).InfoS("[DeployMetricAdapterAPIService] all karmada-metrics-adapter APIServices status is ready ", "karmada", klog.KObj(data))
}
return nil
}
func newKarmadaSearchSubTask() workflow.Task {
return workflow.Task{
Name: constants.KarmadaSearchComponent,
Run: runKarmadaResources,
RunSubTasks: true,
Tasks: []workflow.Task{
{
Name: "DeployKarmadaSearch",
Run: runKarmadaSearch,
},
{
Name: "DeployKarmadaSearchAPIService",
Run: runDeployKarmadaSearchAPIService,
},
},
}
}
func runKarmadaSearch(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("KarmadaSearch task invoked with an invalid data struct")
}
cfg := data.Components()
if cfg.KarmadaSearch == nil {
klog.Infof("Skip installing component (%s/%s)", data.GetNamespace(), constants.KarmadaSearchComponent)
return nil
}
err := search.EnsureKarmadaSearch(
data.RemoteClient(),
cfg.KarmadaSearch,
data.GetName(),
data.GetNamespace(),
data.FeatureGates(),
)
if err != nil {
return fmt.Errorf("failed to apply karmada search, err: %w", err)
}
klog.V(2).InfoS("[KarmadaSearch] Successfully applied karmada search component", "karmada", klog.KObj(data))
return nil
}
func runDeployKarmadaSearchAPIService(r workflow.RunData) error {
data, ok := r.(InitData)
if !ok {
return errors.New("DeploySearchAPIService task invoked with an invalid data struct")
}
cfg := data.Components()
if cfg.KarmadaSearch == nil {
klog.V(2).InfoS("[karmadaSearch] Skip install karmada-search APIService")
return nil
}
config := data.ControlplaneConfig()
client, err := apiclient.NewAPIRegistrationClient(config)
if err != nil {
return err
}
cert := data.GetCert(constants.CaCertAndKeyName)
if len(cert.CertData()) == 0 {
return errors.New("unexpected empty ca cert data for search")
}
caBase64 := base64.StdEncoding.EncodeToString(cert.CertData())
err = apiservice.EnsureSearchAPIService(client, data.KarmadaClient(), data.GetName(), constants.KarmadaSystemNamespace, data.GetName(), data.GetNamespace(), caBase64)
if err != nil {
return fmt.Errorf("failed to apply karmada-metrics-adapter APIService resource to karmada controlplane, err: %w", err)
}
if *cfg.KarmadaSearch.Replicas != 0 {
waiter := apiclient.NewKarmadaWaiter(config, nil, time.Second*20)
for _, gv := range constants.KarmadaSearchAPIServices {
apiServiceName := fmt.Sprintf("%s.%s", gv.Version, gv.Group)
if err := waiter.WaitForAPIService(apiServiceName); err != nil {
return fmt.Errorf("the APIService %s is unhealthy, err: %w", apiServiceName, err)
}
}
klog.V(2).InfoS("[DeploySearchAPIService] all karmada-search APIServices status is ready ", "karmada", klog.KObj(data))
}
return nil
}