Skip to content

Commit

Permalink
Add metric and traffic plugins
Browse files Browse the repository at this point in the history
Signed-off-by: Rizwana777 <rizwananaaz177@gmail.com>
  • Loading branch information
Rizwana777 committed Sep 13, 2024
1 parent a96aa79 commit 3de255e
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 79 deletions.
14 changes: 14 additions & 0 deletions api/v1alpha1/argorollouts_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ type RolloutManagerSpec struct {

// SkipNotificationSecretDeployment lets you specify if the argo notification secret should be deployed
SkipNotificationSecretDeployment bool `json:"skipNotificationSecretDeployment,omitempty"`

// Plugins specify the traffic and metric plugins in Argo Rollout
Plugins Plugins `json:"plugins,omitempty"`
}

type Plugin struct {
Name string `json:"name"`
Location string `json:"location"`
SHA256 string `json:"sha256,omitempty"`
}

type Plugins struct {
TrafficManagement []Plugin `json:"trafficManagement,omitempty"`
Metric []Plugin `json:"metric,omitempty"`
}

// ArgoRolloutsNodePlacementSpec is used to specify NodeSelector and Tolerations for Rollouts workloads
Expand Down
41 changes: 41 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 31 additions & 0 deletions config/crd/bases/argoproj.io_rolloutmanagers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,37 @@ spec:
type: object
type: array
type: object
plugins:
properties:
metric:
items:
properties:
location:
type: string
name:
type: string
sha256:
type: string
required:
- location
- name
type: object
type: array
trafficManagement:
items:
properties:
location:
type: string
name:
type: string
sha256:
type: string
required:
- location
- name
type: object
type: array
type: object
skipNotificationSecretDeployment:
description: SkipNotificationSecretDeployment lets you specify if
the argo notification secret should be deployed
Expand Down
141 changes: 110 additions & 31 deletions controllers/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@ package rollouts

import (
"context"

"fmt"
"reflect"

rolloutsmanagerv1alpha1 "github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
"gopkg.in/yaml.v2"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"

"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// From https://argo-rollouts.readthedocs.io/en/stable/features/traffic-management/plugins/
const TrafficRouterPluginConfigMapKey = "trafficRouterPlugins"
const MetricPluginConfigMapKey = "metricPlugins"

// Reconcile the Rollouts Default Config Map.
func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr rolloutsmanagerv1alpha1.RolloutManager) error {
Expand All @@ -33,18 +40,66 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr ro

setRolloutsLabelsAndAnnotationsToObject(&desiredConfigMap.ObjectMeta, cr)

trafficRouterPlugins := []pluginItem{
{
trafficRouterPluginsMap := map[string]pluginItem{
OpenShiftRolloutPluginName: {
Name: OpenShiftRolloutPluginName,
Location: r.OpenShiftRoutePluginLocation,
},
}
pluginString, err := yaml.Marshal(trafficRouterPlugins)

// Append traffic management plugins specified in RolloutManager CR
for _, plugin := range cr.Spec.Plugins.TrafficManagement {
// Prevent adding or modifying the OpenShiftRoutePluginName through the CR
if plugin.Name == OpenShiftRolloutPluginName {
return fmt.Errorf("the plugin %s cannot be modified or added through the RolloutManager CR", OpenShiftRolloutPluginName)
}
// Check for duplicate traffic plugins
if _, exists := trafficRouterPluginsMap[plugin.Name]; !exists {
trafficRouterPluginsMap[plugin.Name] = pluginItem{
Name: plugin.Name,
Location: plugin.Location,
}
}
}

// Convert traffic plugins map to slice
trafficRouterPlugins := make([]pluginItem, 0, len(trafficRouterPluginsMap))
for _, plugin := range trafficRouterPluginsMap {
trafficRouterPlugins = append(trafficRouterPlugins, plugin)
}

// Append metric plugins specified in RolloutManager CR
metricPluginsMap := map[string]pluginItem{}
for _, plugin := range cr.Spec.Plugins.Metric {
// Check for duplicate metric plugins
if _, exists := metricPluginsMap[plugin.Name]; !exists {
metricPluginsMap[plugin.Name] = pluginItem{
Name: plugin.Name,
Location: plugin.Location,
Sha256: plugin.SHA256,
}
}
}

// Convert metric plugins map to slice
metricPlugins := make([]pluginItem, 0, len(metricPluginsMap))
for _, plugin := range metricPluginsMap {
metricPlugins = append(metricPlugins, plugin)
}

desiredTrafficRouterPluginString, err := yaml.Marshal(trafficRouterPlugins)
if err != nil {
return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err)
}

desiredMetricPluginString, err := yaml.Marshal(metricPlugins)
if err != nil {
return fmt.Errorf("error marshalling metricPlugins to string %s", err)
}

desiredConfigMap.Data = map[string]string{
TrafficRouterPluginConfigMapKey: string(pluginString),
TrafficRouterPluginConfigMapKey: string(desiredTrafficRouterPluginString),
MetricPluginConfigMapKey: string(desiredMetricPluginString),
}

actualConfigMap := &corev1.ConfigMap{}
Expand All @@ -58,41 +113,65 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr ro
return fmt.Errorf("failed to get the serviceAccount associated with %s: %w", desiredConfigMap.Name, err)
}

var actualTrafficRouterPlugins []pluginItem
// Unmarshal the existing plugin data from the actual ConfigMap
var actualTrafficRouterPlugins, actualMetricPlugins []pluginItem
if err = yaml.Unmarshal([]byte(actualConfigMap.Data[TrafficRouterPluginConfigMapKey]), &actualTrafficRouterPlugins); err != nil {
return fmt.Errorf("failed to unmarshal traffic router plugins from ConfigMap: %s", err)
return fmt.Errorf("failed to unmarshal traffic router plugins: %s", err)
}
if err = yaml.Unmarshal([]byte(actualConfigMap.Data[MetricPluginConfigMapKey]), &actualMetricPlugins); err != nil {
return fmt.Errorf("failed to unmarshal metric plugins: %s", err)
}

// Check if the plugin already exists and if the URL is different, update the ConfigMap
for i, plugin := range actualTrafficRouterPlugins {
if plugin.Name == OpenShiftRolloutPluginName {
if plugin.Location != r.OpenShiftRoutePluginLocation {
actualTrafficRouterPlugins[i].Location = r.OpenShiftRoutePluginLocation
pluginBytes, err := yaml.Marshal(actualTrafficRouterPlugins)
if err != nil {
return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err)
}

actualConfigMap.Data = map[string]string{
TrafficRouterPluginConfigMapKey: string(pluginBytes),
}

return r.Client.Update(ctx, actualConfigMap)
} else {
// Plugin URL is the same, nothing to do
return nil
}
// Check if an update is needed by comparing desired and actual plugin configurations
updateNeeded := !reflect.DeepEqual(actualTrafficRouterPlugins, trafficRouterPlugins) || !reflect.DeepEqual(actualMetricPlugins, metricPlugins)

if updateNeeded {
// Update the ConfigMap's plugin data with the new values
actualConfigMap.Data[TrafficRouterPluginConfigMapKey] = string(desiredTrafficRouterPluginString)
actualConfigMap.Data[MetricPluginConfigMapKey] = string(desiredMetricPluginString)

// Update the ConfigMap in the cluster
if err := r.Client.Update(ctx, actualConfigMap); err != nil {
return fmt.Errorf("failed to update ConfigMap: %v", err)
}
log.Info("ConfigMap updated successfully")

// Restarting rollouts pod only if configMap is updated
if err := r.restartRolloutsPod(ctx, cr.Namespace); err != nil {
return err
}
}
log.Info("No changes detected in ConfigMap, skipping update and pod restart")
return nil
}

updatedTrafficRouterPlugins := append(actualTrafficRouterPlugins, trafficRouterPlugins...)
// restartRolloutsPod deletes the Rollouts Pod to trigger a restart
func (r *RolloutManagerReconciler) restartRolloutsPod(ctx context.Context, namespace string) error {
deployment := &appsv1.Deployment{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: DefaultArgoRolloutsResourceName, Namespace: namespace}, deployment); err != nil {
return fmt.Errorf("failed to get deployment: %w", err)
}

pluginString, err = yaml.Marshal(updatedTrafficRouterPlugins)
if err != nil {
return fmt.Errorf("error marshalling trafficRouterPlugin to string %w", err)
podList := &corev1.PodList{}
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels(deployment.Spec.Selector.MatchLabels),
}
if err := r.Client.List(ctx, podList, listOpts...); err != nil {
return fmt.Errorf("failed to list Rollouts Pods: %w", err)
}

actualConfigMap.Data[TrafficRouterPluginConfigMapKey] = string(pluginString)
for _, pod := range podList.Items {
log.Info("Deleting Rollouts Pod", "podName", pod.Name)
if err := r.Client.Delete(ctx, &pod); err != nil {
if errors.IsNotFound(err) {
log.Info(fmt.Sprintf("Pod %s already deleted", pod.Name))
continue
}
return fmt.Errorf("failed to delete Rollouts Pod %s: %w", pod.Name, err)
}
log.Info("Rollouts Pod deleted successfully", "podName", pod.Name)
}

return r.Client.Update(ctx, actualConfigMap)
return nil
}
Loading

0 comments on commit 3de255e

Please sign in to comment.