blob: 7fbd43de2502c0d57d3ae78ab0639f6cd0ba59aa [file] [log] [blame]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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 operator
import (
"context"
"fmt"
"sort"
"strings"
"time"
"github.com/go-logr/logr"
core "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/util/retry"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
runtimelog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
operatorv1alpha1 "github.com/apache/skywalking-swck/operator/apis/operator/v1alpha1"
"github.com/apache/skywalking-swck/operator/pkg/kubernetes"
"github.com/apache/skywalking-swck/operator/pkg/operator/injector"
)
// JavaAgentReconciler reconciles a JavaAgent object
type JavaAgentReconciler struct {
client.Client
Scheme *runtime.Scheme
FileRepo kubernetes.Repo
}
// +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=javaagents,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=javaagents/status,verbs=get;update;patch;delete
// +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;update;patch
// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch
func (r *JavaAgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := runtimelog.FromContext(ctx)
log.Info("=====================javaagent reconcile started================================")
pod := &core.Pod{}
err := r.Client.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, pod)
if err != nil && !apierrors.IsNotFound(err) {
log.Error(err, "failed to get pod")
return ctrl.Result{}, err
}
configmap := &core.ConfigMap{}
configmapName := ""
for i := range pod.Spec.Volumes {
if pod.Spec.Volumes[i].ConfigMap != nil {
configmapName = pod.Spec.Volumes[i].ConfigMap.Name
}
}
// get pods' OwnerReferences
if len(pod.OwnerReferences) == 0 {
log.Error(err, "the pod isn't created by workloads")
return ctrl.Result{}, err
}
ownerReference := pod.OwnerReferences[0]
// get configmap from the volume of configmap
if len(configmapName) > 0 {
err = r.Client.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: configmapName}, configmap)
if err != nil && !apierrors.IsNotFound(err) {
log.Error(err, "failed to get configmap")
return ctrl.Result{}, err
}
} else {
log.Info("No configmap mounted.")
}
swAgentList := &operatorv1alpha1.SwAgentList{}
var lastMatchedSwAgent *operatorv1alpha1.SwAgent
if lastMatchedSwAgent, err = r.getSwAgent(ctx, req, swAgentList, pod); err != nil {
log.Error(err, "get SwAgent error")
return ctrl.Result{}, err
}
config := map[string]string{}
r.injectConfigBySwAgent(lastMatchedSwAgent, config)
injector.GetInjectedAgentConfig(&pod.Annotations, &config)
// only get the first selector label from labels as podselector
labels := pod.Labels
keys := []string{}
for k := range labels {
if !strings.Contains(k, injector.ActiveInjectorLabel) {
keys = append(keys, k)
}
}
if len(keys) == 0 {
log.Error(err, "the pod doesn't contain the pod selector")
return ctrl.Result{}, err
}
sort.Strings(keys)
selectorname := strings.Join([]string{keys[0], labels[keys[0]]}, "-")
podselector := strings.Join([]string{keys[0], labels[keys[0]]}, "=")
app := kubernetes.Application{
Client: r.Client,
FileRepo: r.FileRepo,
CR: pod,
GVK: core.SchemeGroupVersion.WithKind("Pod"),
TmplFunc: map[string]interface{}{
"config": func() map[string]string {
return config
},
"labelKey": func() string {
return keys[0]
},
"labelValue": func() string {
return labels[keys[0]]
},
"ownerReference": func() metav1.OwnerReference {
return ownerReference
},
"SelectorName": func() string {
return selectorname
},
"Namespace": func() string {
return req.Namespace
},
"PodSelector": func() string {
return podselector
},
"ServiceName": func() string {
return operatorv1alpha1.GetServiceName(&config)
},
"BackendService": func() string {
return operatorv1alpha1.GetBackendService(&config)
},
},
}
// false means not to compose , such as ownerReferences , as we compose it as template
_, err = app.Apply(ctx, "injector/templates/javaagent.yaml", log, false)
if err != nil {
log.Error(err, "failed to apply javaagent")
return ctrl.Result{}, err
}
if err := r.UpdateStatus(ctx, log, req.Namespace, selectorname, podselector); err != nil {
log.Error(err, "failed to update javaagent's status")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
func (r *JavaAgentReconciler) injectConfigBySwAgent(lastMatchedSwAgent *operatorv1alpha1.SwAgent, config map[string]string) {
if nil != lastMatchedSwAgent && len(lastMatchedSwAgent.Spec.JavaSidecar.Env) > 0 {
for _, env := range lastMatchedSwAgent.Spec.JavaSidecar.Env {
if strings.EqualFold(env.Name, "SW_AGENT_COLLECTOR_BACKEND_SERVICES") {
config[operatorv1alpha1.BackendService] = env.Value
} else if strings.EqualFold(env.Name, "SW_AGENT_NAME") {
config[operatorv1alpha1.ServiceName] = env.Value
}
}
}
}
func (r *JavaAgentReconciler) getSwAgent(ctx context.Context, req ctrl.Request,
swAgentList *operatorv1alpha1.SwAgentList, pod *core.Pod) (*operatorv1alpha1.SwAgent, error) {
if err := r.Client.List(ctx, swAgentList, client.InNamespace(req.Namespace)); err != nil {
return nil, err
}
// selector
var lastMatchedSwAgent *operatorv1alpha1.SwAgent
for _, swAgent := range swAgentList.Items {
isMatch := true
if len(swAgent.Spec.Selector) != 0 {
for k, v := range swAgent.Spec.Selector {
if !strings.EqualFold(v, pod.Labels[k]) {
isMatch = false
}
}
}
if isMatch {
lastMatchedSwAgent = &swAgent
}
}
return lastMatchedSwAgent, nil
}
func (r *JavaAgentReconciler) UpdateStatus(ctx context.Context, log logr.Logger, namespace, selectorname, podselector string) error {
errCol := new(kubernetes.ErrorCollector)
// get javaagent by selectorname
javaagent := &operatorv1alpha1.JavaAgent{}
err := r.Client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: selectorname + "-javaagent"}, javaagent)
if err != nil && !apierrors.IsNotFound(err) {
errCol.Collect(fmt.Errorf("failed to get javaagent: %w", err))
}
// avoid printing error info when the javaagent is creating
if javaagent.Name == "" {
log.Info("javaagent is creating...", "name", selectorname+"-javaagent")
return errCol.Error()
}
// return all pods in the request namespace with the podselector
podList := &core.PodList{}
label := strings.Split(podselector, "=")
opts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{label[0]: label[1]},
}
if err := r.Client.List(ctx, podList, opts...); err != nil && !apierrors.IsNotFound(err) {
errCol.Collect(fmt.Errorf("failed to list pod: %w", err))
}
// get the pod's number that need to be injected
expectedInjectedNum := 0
// get the pod's number that injected successfully
realInjectedNum := 0
for i := range podList.Items {
labels := podList.Items[i].Labels
annotations := podList.Items[i].Annotations
if labels != nil && strings.EqualFold(strings.ToLower(labels[injector.ActiveInjectorLabel]), "true") {
expectedInjectedNum++
}
if annotations != nil && strings.EqualFold(strings.ToLower(annotations[injector.SidecarInjectSucceedAnno]), "true") {
realInjectedNum++
}
}
javaagent.Status.ExpectedInjectedNum = expectedInjectedNum
javaagent.Status.RealInjectedNum = realInjectedNum
nilTime := metav1.Time{}
now := metav1.NewTime(time.Now())
if javaagent.Status.CreationTime == nilTime {
javaagent.Status.CreationTime = now
javaagent.Status.LastUpdateTime = now
} else {
javaagent.Status.LastUpdateTime = now
}
overlay := javaagent.Status
if err := r.updateStatus(ctx, javaagent, overlay, errCol); err != nil {
errCol.Collect(fmt.Errorf("failed to update java status: %w", err))
}
log.Info("updated javaagent's status")
return errCol.Error()
}
func (r *JavaAgentReconciler) updateStatus(ctx context.Context, javaagent *operatorv1alpha1.JavaAgent,
overlay operatorv1alpha1.JavaAgentStatus, errCol *kubernetes.ErrorCollector) error {
// avoid resource conflict
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
if err := r.Client.Get(ctx, client.ObjectKey{Name: javaagent.Name, Namespace: javaagent.Namespace}, javaagent); err != nil {
errCol.Collect(fmt.Errorf("failed to get javaagent: %w", err))
}
javaagent.Status = overlay
javaagent.Kind = "JavaAgent"
if err := kubernetes.ApplyOverlay(javaagent, &operatorv1alpha1.JavaAgent{Status: overlay}); err != nil {
errCol.Collect(fmt.Errorf("failed to apply overlay: %w", err))
}
if err := r.Status().Update(ctx, javaagent); err != nil {
errCol.Collect(fmt.Errorf("failed to update status of JavaAgent: %w", err))
}
return errCol.Error()
})
}
func (r *JavaAgentReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&core.Pod{}).
WithEventFilter(predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
annotations := e.Object.GetAnnotations()
if annotations != nil && strings.ToLower(annotations[injector.SidecarInjectSucceedAnno]) == "true" {
return true
}
return false
},
// avoid calling Reconcile when the pod's workload is deleted
UpdateFunc: func(e event.UpdateEvent) bool {
annotations := e.ObjectNew.GetAnnotations()
if annotations != nil && strings.ToLower(annotations[injector.SidecarInjectSucceedAnno]) == "true" {
return e.ObjectNew.GetDeletionTimestamp() == nil
}
return false
},
DeleteFunc: func(e event.DeleteEvent) bool {
return false
},
}).
Owns(&operatorv1alpha1.JavaAgent{}).
Complete(r)
}