Skip to content

Commit 441044e

Browse files
authored
Merge branch 'main' into log-verbosity
2 parents be1c284 + 2787761 commit 441044e

File tree

14 files changed

+504
-118
lines changed

14 files changed

+504
-118
lines changed

config/manifests/bases/octavia-operator.clusterserviceversion.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,40 @@ spec:
2323
displayName: Octavia API
2424
kind: OctaviaAPI
2525
name: octaviaapis.octavia.openstack.org
26+
specDescriptors:
27+
- description: TLS - Parameters related to the TLS
28+
displayName: TLS
29+
path: tls
30+
- description: API tls type which encapsulates for API services
31+
displayName: API
32+
path: tls.api
33+
- description: Ovn GenericService - holds the secret for the OvnDb client cert
34+
displayName: Ovn
35+
path: tls.ovn
2636
version: v1beta1
2737
- description: Octavia is the Schema for the octavia API
2838
displayName: Octavia
2939
kind: Octavia
3040
name: octavias.octavia.openstack.org
41+
specDescriptors:
42+
- description: TLS - Parameters related to the TLS
43+
displayName: TLS
44+
path: octaviaAPI.tls
45+
- description: API tls type which encapsulates for API services
46+
displayName: API
47+
path: octaviaAPI.tls.api
48+
- description: Ovn GenericService - holds the secret for the OvnDb client cert
49+
displayName: Ovn
50+
path: octaviaAPI.tls.ovn
51+
- description: TLS - Parameters related to the TLS
52+
displayName: TLS
53+
path: octaviaHealthManager.tls
54+
- description: TLS - Parameters related to the TLS
55+
displayName: TLS
56+
path: octaviaHousekeeping.tls
57+
- description: TLS - Parameters related to the TLS
58+
displayName: TLS
59+
path: octaviaWorker.tls
3160
version: v1beta1
3261
description: Octavia Operator
3362
displayName: Octavia Operator

controllers/amphoracontroller_controller.go

Lines changed: 10 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package controllers
1919
import (
2020
"context"
2121
"fmt"
22-
"sort"
2322
"strings"
2423
"time"
2524

@@ -44,7 +43,6 @@ import (
4443
appsv1 "k8s.io/api/apps/v1"
4544
corev1 "k8s.io/api/core/v1"
4645
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
47-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4846
"k8s.io/apimachinery/pkg/fields"
4947
"k8s.io/apimachinery/pkg/runtime"
5048
"k8s.io/apimachinery/pkg/types"
@@ -578,46 +576,23 @@ func (r *OctaviaAmphoraControllerReconciler) generateServiceConfigMaps(
578576
err.Error()))
579577
return err
580578
}
579+
581580
//
582-
// TODO(beagles): Improve this with predictable IPs for the health managers because what is
583-
// going to happen on start up is that health managers will restart each time a new one is deployed.
584-
// The easiest strategy is to create a "hole" in the IP address range and control the
585-
// allocation and configuration of an additional IP on each network attached interface. We will
586-
// need a container in the Pod that has the ip command installed to do this however.
581+
// Get the predicatable IPs from the HmConfigMap
587582
//
588-
healthManagerIPs, err := getPodIPs(
589-
fmt.Sprintf("%s-%s", "octavia", octaviav1.HealthManager),
590-
instance.Namespace,
591-
r.Kclient,
592-
&r.Log,
593-
)
583+
hmMap := &corev1.ConfigMap{}
584+
err = helper.GetClient().Get(ctx, types.NamespacedName{Name: octavia.HmConfigMap, Namespace: instance.GetNamespace()}, hmMap)
594585
if err != nil {
595-
instance.Status.Conditions.Set(condition.FalseCondition(
596-
condition.InputReadyCondition,
597-
condition.ErrorReason,
598-
condition.SeverityWarning,
599-
condition.InputReadyErrorMessage,
600-
err.Error()))
601586
return err
602587
}
603-
604-
// TODO(beagles): come up with a way to preallocate or ensure
605-
// a stable list of IPs.
606-
607-
if instance.Spec.Role == octaviav1.HealthManager {
608-
// TODO(gthiemonge) This is fine to leave this list empty in the HM when
609-
// we use redis, because the HM doesn't create any LBs, but if we drop
610-
// redis, failovers will be triggered in the HM
611-
templateParameters["ControllerIPList"] = ""
612-
} else if len(healthManagerIPs) == 0 {
613-
return fmt.Errorf("Health manager ports are not ready yet")
614-
} else {
615-
withPorts := make([]string, len(healthManagerIPs))
616-
for idx, val := range healthManagerIPs {
617-
withPorts[idx] = fmt.Sprintf("%s:5555", val)
588+
var ipAddresses []string
589+
for key, val := range hmMap.Data {
590+
if strings.HasPrefix(key, "hm_") {
591+
ipAddresses = append(ipAddresses, fmt.Sprintf("%s:5555", val))
618592
}
619-
templateParameters["ControllerIPList"] = strings.Join(withPorts, ",")
620593
}
594+
ipAddressString := strings.Join(ipAddresses, ",")
595+
templateParameters["ControllerIPList"] = ipAddressString
621596

622597
spec := instance.Spec
623598
templateParameters["ServiceUser"] = spec.ServiceUser
@@ -727,46 +702,6 @@ func (r *OctaviaAmphoraControllerReconciler) SetupWithManager(mgr ctrl.Manager)
727702
Complete(r)
728703
}
729704

730-
func listHealthManagerPods(name string, ns string, client kubernetes.Interface, log *logr.Logger) (*corev1.PodList, error) {
731-
listOptions := metav1.ListOptions{
732-
LabelSelector: fmt.Sprintf("%s=%s", common.AppSelector, name),
733-
FieldSelector: "status.phase==Running",
734-
}
735-
log.V(1).Info(fmt.Sprintf("Listing pods using label selector %s and field selector %s", listOptions.LabelSelector, listOptions.FieldSelector))
736-
pods, err := client.CoreV1().Pods(ns).List(context.Background(), listOptions)
737-
if err != nil {
738-
return nil, err
739-
}
740-
return pods, nil
741-
}
742-
743-
func getPodIPs(name string, ns string, client kubernetes.Interface, log *logr.Logger) ([]string, error) {
744-
//
745-
// Get the IPs for the network attachments for these PODs.
746-
//
747-
var result []string
748-
pods, err := listHealthManagerPods(name, ns, client, log)
749-
if err != nil {
750-
return nil, err
751-
}
752-
for _, pod := range pods.Items {
753-
annotations := pod.GetAnnotations()
754-
networkStatusList, err := nad.GetNetworkStatusFromAnnotation(annotations)
755-
if err != nil {
756-
log.Error(err, fmt.Sprintf("Unable to get network annotations from %s", annotations))
757-
return nil, err
758-
}
759-
for _, networkStatus := range networkStatusList {
760-
netAttachName := fmt.Sprintf("%s/%s", ns, octavia.LbNetworkAttachmentName)
761-
if networkStatus.Name == netAttachName {
762-
result = append(result, networkStatus.IPs[0])
763-
}
764-
}
765-
}
766-
sort.Strings(result)
767-
return result, nil
768-
}
769-
770705
func (r *OctaviaAmphoraControllerReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request {
771706
requests := []reconcile.Request{}
772707

controllers/octavia_controller.go

Lines changed: 140 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1"
4444
"github.com/openstack-k8s-operators/octavia-operator/pkg/octavia"
4545
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
46+
"k8s.io/apimachinery/pkg/types"
4647
"k8s.io/apimachinery/pkg/util/intstr"
4748

4849
appsv1 "k8s.io/api/apps/v1"
@@ -186,7 +187,6 @@ func (r *OctaviaReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re
186187
condition.UnknownCondition(octaviav1.OctaviaAPIReadyCondition, condition.InitReason, octaviav1.OctaviaAPIReadyInitMessage),
187188
condition.UnknownCondition(condition.NetworkAttachmentsReadyCondition, condition.InitReason, condition.NetworkAttachmentsReadyInitMessage),
188189
condition.UnknownCondition(condition.ExposeServiceReadyCondition, condition.InitReason, condition.ExposeServiceReadyInitMessage),
189-
condition.UnknownCondition(condition.DeploymentReadyCondition, condition.InitReason, condition.DeploymentReadyInitMessage),
190190
condition.UnknownCondition(octaviav1.OctaviaAmphoraCertsReadyCondition, condition.InitReason, octaviav1.OctaviaAmphoraCertsReadyInitMessage),
191191
condition.UnknownCondition(octaviav1.OctaviaQuotasReadyCondition, condition.InitReason, octaviav1.OctaviaQuotasReadyInitMessage),
192192
condition.UnknownCondition(octaviav1.OctaviaAmphoraSSHReadyCondition, condition.InitReason, octaviav1.OctaviaAmphoraSSHReadyInitMessage),
@@ -504,9 +504,12 @@ func (r *OctaviaReconciler) reconcileNormal(ctx context.Context, instance *octav
504504
condition.InputReadyWaitingMessage))
505505
return ctrl.Result{RequeueAfter: time.Duration(10) * time.Second}, nil
506506
}
507+
instance.Status.Conditions.MarkTrue(
508+
condition.RabbitMqTransportURLReadyCondition,
509+
condition.RabbitMqTransportURLReadyMessage)
507510
instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage)
508511

509-
err = octavia.EnsureAmphoraCerts(ctx, instance, helper, &Log)
512+
err = octavia.EnsureAmphoraCerts(ctx, instance, helper)
510513
if err != nil {
511514
instance.Status.Conditions.Set(condition.FalseCondition(
512515
octaviav1.OctaviaAmphoraCertsReadyCondition,
@@ -673,6 +676,109 @@ func (r *OctaviaReconciler) reconcileNormal(ctx context.Context, instance *octav
673676
return ctrl.Result{}, err
674677
}
675678

679+
nodeConfigMap := &corev1.ConfigMap{
680+
ObjectMeta: metav1.ObjectMeta{
681+
Name: octavia.HmConfigMap,
682+
Namespace: instance.GetNamespace(),
683+
Labels: labels.GetLabels(instance, labels.GetGroupLabel(instance.ObjectMeta.Name), map[string]string{}),
684+
},
685+
Data: make(map[string]string),
686+
}
687+
688+
// Look for existing config map and if exists, read existing data and match
689+
// against nodes.
690+
foundMap := &corev1.ConfigMap{}
691+
err = helper.GetClient().Get(ctx, types.NamespacedName{Name: octavia.HmConfigMap, Namespace: instance.GetNamespace()},
692+
foundMap)
693+
if err != nil {
694+
if k8s_errors.IsNotFound(err) {
695+
Log.Info(fmt.Sprintf("Port map %s doesn't exist, creating.", octavia.HmConfigMap))
696+
} else {
697+
return ctrl.Result{}, err
698+
}
699+
} else {
700+
Log.Info("Retrieved existing map, updating..")
701+
nodeConfigMap.Data = foundMap.Data
702+
}
703+
704+
//
705+
// Predictable IPs.
706+
//
707+
// NOTE(beagles): refactoring this might be nice. This could also be
708+
// optimized but the data sets are small (nodes an IP ranges are less than
709+
// 100) so optimization might be a waste.
710+
//
711+
predictableIPParams, err := octavia.GetPredictableIPAM(networkParameters)
712+
if err != nil {
713+
return ctrl.Result{}, err
714+
}
715+
// Get a list of the nodes in the cluster
716+
717+
// TODO(beagles):
718+
// * confirm whether or not this lists only the nodes we want (i.e. ones
719+
// that will host the daemonset)
720+
// * do we want to provide a mechanism to temporarily disabling this list
721+
// for maintenance windows where nodes might be "coming and going"
722+
723+
nodes, _ := helper.GetKClient().CoreV1().Nodes().List(ctx, metav1.ListOptions{})
724+
updatedMap := make(map[string]string)
725+
allocatedIPs := make(map[string]bool)
726+
var predictableIPsRequired []string
727+
728+
// First scan existing allocations so we can keep existing allocations.
729+
// Keeping track of what's required and what already exists. If a node is
730+
// removed from the cluster, it's IPs will not be added to the allocated
731+
// list and are effectively recycled.
732+
for _, node := range nodes.Items {
733+
Log.Info(fmt.Sprintf("cluster node name %s", node.Name))
734+
portName := fmt.Sprintf("hm_%s", node.Name)
735+
if ipValue, ok := nodeConfigMap.Data[portName]; ok {
736+
updatedMap[portName] = ipValue
737+
allocatedIPs[ipValue] = true
738+
Log.Info(fmt.Sprintf("%s has IP mapping %s: %s", node.Name, portName, ipValue))
739+
} else {
740+
predictableIPsRequired = append(predictableIPsRequired, portName)
741+
}
742+
portName = fmt.Sprintf("rsyslog_%s", node.Name)
743+
if ipValue, ok := nodeConfigMap.Data[portName]; ok {
744+
updatedMap[portName] = ipValue
745+
allocatedIPs[ipValue] = true
746+
Log.Info(fmt.Sprintf("%s has IP mapping %s: %s", node.Name, portName, ipValue))
747+
} else {
748+
predictableIPsRequired = append(predictableIPsRequired, portName)
749+
}
750+
}
751+
// Get new IPs using the range from predictableIPParmas minus the
752+
// allocatedIPs captured above.
753+
Log.Info(fmt.Sprintf("Allocating %d predictable IPs", len(predictableIPsRequired)))
754+
for _, portName := range predictableIPsRequired {
755+
hmPort, err := octavia.GetNextIP(predictableIPParams, allocatedIPs)
756+
if err != nil {
757+
// An error here is really unexpected- it means either we have
758+
// messed up the allocatedIPs list or the range we are assuming is
759+
// too small for the number of health managers and rsyslog
760+
// containers.
761+
return ctrl.Result{}, err
762+
}
763+
updatedMap[portName] = hmPort
764+
}
765+
766+
mapLabels := labels.GetLabels(instance, labels.GetGroupLabel(instance.ObjectMeta.Name), map[string]string{})
767+
_, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), nodeConfigMap, func() error {
768+
nodeConfigMap.Labels = util.MergeStringMaps(nodeConfigMap.Labels, mapLabels)
769+
nodeConfigMap.Data = updatedMap
770+
err := controllerutil.SetControllerReference(instance, nodeConfigMap, helper.GetScheme())
771+
if err != nil {
772+
return err
773+
}
774+
return nil
775+
})
776+
777+
if err != nil {
778+
Log.Info("Unable to create config map for health manager ports...")
779+
return ctrl.Result{}, err
780+
}
781+
676782
octaviaHealthManager, op, err := r.amphoraControllerDaemonSetCreateOrUpdate(instance, networkInfo,
677783
ampImageOwnerID, instance.Spec.OctaviaHealthManager, octaviav1.HealthManager)
678784
if err != nil {
@@ -823,6 +929,8 @@ func (r *OctaviaReconciler) reconcileNormal(ctx context.Context, instance *octav
823929
octaviav1.OctaviaAmphoraImagesReadyCondition,
824930
octaviav1.OctaviaAmphoraImagesReadyCompleteMessage)
825931

932+
instance.Status.Conditions.MarkTrue(condition.ExposeServiceReadyCondition, condition.ExposeServiceReadyMessage)
933+
826934
// create Deployment - end
827935

828936
// We reached the end of the Reconcile, update the Ready condition based on
@@ -965,6 +1073,7 @@ func (r *OctaviaReconciler) reconcileAmphoraImages(
9651073
) (ctrl.Result, error) {
9661074
Log := r.GetLogger(ctx)
9671075

1076+
var ctrlResult ctrl.Result
9681077
if instance.Spec.AmphoraImageContainerImage == "" {
9691078
if instance.Status.Hash[octaviav1.ImageUploadHash] != "" {
9701079
Log.Info("Reseting image upload hash")
@@ -986,36 +1095,6 @@ func (r *OctaviaReconciler) reconcileAmphoraImages(
9861095
common.AppSelector: octavia.ServiceName + "-image",
9871096
}
9881097

989-
Log.Info("Initializing amphora image upload deployment")
990-
depl := deployment.NewDeployment(
991-
octavia.ImageUploadDeployment(instance, serviceLabels),
992-
time.Duration(5)*time.Second,
993-
)
994-
ctrlResult, err := depl.CreateOrPatch(ctx, helper)
995-
if err != nil {
996-
instance.Status.Conditions.Set(condition.FalseCondition(
997-
condition.DeploymentReadyCondition,
998-
condition.ErrorReason,
999-
condition.SeverityWarning,
1000-
condition.DeploymentReadyErrorMessage,
1001-
err.Error()))
1002-
return ctrlResult, err
1003-
} else if (ctrlResult != ctrl.Result{}) {
1004-
instance.Status.Conditions.Set(condition.FalseCondition(
1005-
condition.DeploymentReadyCondition,
1006-
condition.RequestedReason,
1007-
condition.SeverityInfo,
1008-
condition.DeploymentReadyRunningMessage))
1009-
return ctrlResult, nil
1010-
}
1011-
1012-
readyCount := depl.GetDeployment().Status.ReadyReplicas
1013-
if readyCount == 0 {
1014-
// Not ready, wait for the next loop
1015-
Log.Info("Image Upload Pod not ready")
1016-
return ctrl.Result{Requeue: true, RequeueAfter: 1 * time.Second}, nil
1017-
}
1018-
10191098
exportLabels := util.MergeStringMaps(
10201099
serviceLabels,
10211100
map[string]string{
@@ -1076,6 +1155,35 @@ func (r *OctaviaReconciler) reconcileAmphoraImages(
10761155
condition.ExposeServiceReadyRunningMessage))
10771156
return ctrlResult, nil
10781157
}
1158+
Log.Info("Initializing amphora image upload deployment")
1159+
depl := deployment.NewDeployment(
1160+
octavia.ImageUploadDeployment(instance, serviceLabels),
1161+
time.Duration(5)*time.Second,
1162+
)
1163+
ctrlResult, err = depl.CreateOrPatch(ctx, helper)
1164+
if err != nil {
1165+
instance.Status.Conditions.Set(condition.FalseCondition(
1166+
octaviav1.OctaviaAmphoraImagesReadyCondition,
1167+
condition.ErrorReason,
1168+
condition.SeverityWarning,
1169+
octaviav1.OctaviaAmphoraImagesReadyErrorMessage,
1170+
err.Error()))
1171+
return ctrlResult, err
1172+
} else if (ctrlResult != ctrl.Result{}) {
1173+
instance.Status.Conditions.Set(condition.FalseCondition(
1174+
octaviav1.OctaviaAmphoraImagesReadyCondition,
1175+
condition.ErrorReason,
1176+
condition.SeverityWarning,
1177+
octaviav1.OctaviaAmphoraImagesReadyErrorMessage,
1178+
err.Error()))
1179+
return ctrlResult, nil
1180+
}
1181+
readyCount := depl.GetDeployment().Status.ReadyReplicas
1182+
if readyCount == 0 {
1183+
// Not ready, wait for the next loop
1184+
Log.Info("Image Upload Pod not ready")
1185+
return ctrl.Result{Requeue: true, RequeueAfter: 1 * time.Second}, nil
1186+
}
10791187
endpoint, err := svc.GetAPIEndpoint(nil, nil, "")
10801188
if err != nil {
10811189
return ctrl.Result{}, err

0 commit comments

Comments
 (0)