fix sync streams and add diffs for annotations and owner references (#2728)
* extend and improve hasSlotsInSync unit test * fix sync streams and add diffs for annotations and owner references * incl. current annotations as desired where we do not fully control them * added one more unit test and fixed sub test names * pass maintenance windows to function and update unit test
This commit is contained in:
		
							parent
							
								
									aad03f71ea
								
							
						
					
					
						commit
						c7ee34ed12
					
				|  | @ -73,7 +73,7 @@ func (c *Cluster) majorVersionUpgrade() error { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if !c.isInMainternanceWindow() { | 	if !isInMainternanceWindow(c.Spec.MaintenanceWindows) { | ||||||
| 		c.logger.Infof("skipping major version upgrade, not in maintenance window") | 		c.logger.Infof("skipping major version upgrade, not in maintenance window") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -128,6 +128,8 @@ func (c *Cluster) syncPublication(dbName string, databaseSlotsList map[string]za | ||||||
| 			createPublications[slotName] = tableList | 			createPublications[slotName] = tableList | ||||||
| 		} else if currentTables != tableList { | 		} else if currentTables != tableList { | ||||||
| 			alterPublications[slotName] = tableList | 			alterPublications[slotName] = tableList | ||||||
|  | 		} else { | ||||||
|  | 			(*slotsToSync)[slotName] = slotAndPublication.Slot | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -142,30 +144,34 @@ func (c *Cluster) syncPublication(dbName string, databaseSlotsList map[string]za | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var errorMessage error = nil | 	errors := make([]string, 0) | ||||||
| 	for publicationName, tables := range createPublications { | 	for publicationName, tables := range createPublications { | ||||||
| 		if err = c.executeCreatePublication(publicationName, tables); err != nil { | 		if err = c.executeCreatePublication(publicationName, tables); err != nil { | ||||||
| 			errorMessage = fmt.Errorf("creation of publication %q failed: %v", publicationName, err) | 			errors = append(errors, fmt.Sprintf("creation of publication %q failed: %v", publicationName, err)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		(*slotsToSync)[publicationName] = databaseSlotsList[publicationName].Slot | 		(*slotsToSync)[publicationName] = databaseSlotsList[publicationName].Slot | ||||||
| 	} | 	} | ||||||
| 	for publicationName, tables := range alterPublications { | 	for publicationName, tables := range alterPublications { | ||||||
| 		if err = c.executeAlterPublication(publicationName, tables); err != nil { | 		if err = c.executeAlterPublication(publicationName, tables); err != nil { | ||||||
| 			errorMessage = fmt.Errorf("update of publication %q failed: %v", publicationName, err) | 			errors = append(errors, fmt.Sprintf("update of publication %q failed: %v", publicationName, err)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		(*slotsToSync)[publicationName] = databaseSlotsList[publicationName].Slot | 		(*slotsToSync)[publicationName] = databaseSlotsList[publicationName].Slot | ||||||
| 	} | 	} | ||||||
| 	for _, publicationName := range deletePublications { | 	for _, publicationName := range deletePublications { | ||||||
| 		if err = c.executeDropPublication(publicationName); err != nil { | 		if err = c.executeDropPublication(publicationName); err != nil { | ||||||
| 			errorMessage = fmt.Errorf("deletion of publication %q failed: %v", publicationName, err) | 			errors = append(errors, fmt.Sprintf("deletion of publication %q failed: %v", publicationName, err)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		(*slotsToSync)[publicationName] = nil | 		(*slotsToSync)[publicationName] = nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return errorMessage | 	if len(errors) > 0 { | ||||||
|  | 		return fmt.Errorf("%v", strings.Join(errors, `', '`)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) generateFabricEventStream(appId string) *zalandov1.FabricEventStream { | func (c *Cluster) generateFabricEventStream(appId string) *zalandov1.FabricEventStream { | ||||||
|  | @ -370,7 +376,7 @@ func (c *Cluster) syncStreams() error { | ||||||
| 	for dbName, databaseSlotsList := range databaseSlots { | 	for dbName, databaseSlotsList := range databaseSlots { | ||||||
| 		err := c.syncPublication(dbName, databaseSlotsList, &slotsToSync) | 		err := c.syncPublication(dbName, databaseSlotsList, &slotsToSync) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			c.logger.Warningf("could not sync publications in database %q: %v", dbName, err) | 			c.logger.Warningf("could not sync all publications in database %q: %v", dbName, err) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -398,7 +404,7 @@ func (c *Cluster) syncStreams() error { | ||||||
| 				c.logger.Warningf("could not sync event streams with applicationId %s: %v", appId, err) | 				c.logger.Warningf("could not sync event streams with applicationId %s: %v", appId, err) | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			c.logger.Warningf("database replication slots for streams with applicationId %s not in sync, skipping event stream sync", appId) | 			c.logger.Warningf("database replication slots %#v for streams with applicationId %s not in sync, skipping event stream sync", slotsToSync, appId) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -415,8 +421,9 @@ func hasSlotsInSync(appId string, databaseSlots map[string]map[string]zalandov1. | ||||||
| 	for dbName, slots := range databaseSlots { | 	for dbName, slots := range databaseSlots { | ||||||
| 		for slotName := range slots { | 		for slotName := range slots { | ||||||
| 			if slotName == getSlotName(dbName, appId) { | 			if slotName == getSlotName(dbName, appId) { | ||||||
| 				if _, exists := slotsToSync[slotName]; !exists { | 				if slot, exists := slotsToSync[slotName]; !exists || slot == nil { | ||||||
| 					allSlotsInSync = false | 					allSlotsInSync = false | ||||||
|  | 					continue | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | @ -432,7 +439,17 @@ func (c *Cluster) syncStream(appId string) error { | ||||||
| 		if appId == stream.Spec.ApplicationId { | 		if appId == stream.Spec.ApplicationId { | ||||||
| 			streamExists = true | 			streamExists = true | ||||||
| 			desiredStreams := c.generateFabricEventStream(appId) | 			desiredStreams := c.generateFabricEventStream(appId) | ||||||
| 			if match, reason := sameStreams(stream.Spec.EventStreams, desiredStreams.Spec.EventStreams); !match { | 			if !reflect.DeepEqual(stream.ObjectMeta.OwnerReferences, desiredStreams.ObjectMeta.OwnerReferences) { | ||||||
|  | 				c.logger.Infof("owner references of event streams with applicationId %s do not match the current ones", appId) | ||||||
|  | 				stream.ObjectMeta.OwnerReferences = desiredStreams.ObjectMeta.OwnerReferences | ||||||
|  | 				c.setProcessName("updating event streams with applicationId %s", appId) | ||||||
|  | 				stream, err := c.KubeClient.FabricEventStreams(stream.Namespace).Update(context.TODO(), stream, metav1.UpdateOptions{}) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return fmt.Errorf("could not update event streams with applicationId %s: %v", appId, err) | ||||||
|  | 				} | ||||||
|  | 				c.Streams[appId] = stream | ||||||
|  | 			} | ||||||
|  | 			if match, reason := c.compareStreams(stream, desiredStreams); !match { | ||||||
| 				c.logger.Debugf("updating event streams with applicationId %s: %s", appId, reason) | 				c.logger.Debugf("updating event streams with applicationId %s: %s", appId, reason) | ||||||
| 				desiredStreams.ObjectMeta = stream.ObjectMeta | 				desiredStreams.ObjectMeta = stream.ObjectMeta | ||||||
| 				updatedStream, err := c.updateStreams(desiredStreams) | 				updatedStream, err := c.updateStreams(desiredStreams) | ||||||
|  | @ -459,7 +476,26 @@ func (c *Cluster) syncStream(appId string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func sameStreams(curEventStreams, newEventStreams []zalandov1.EventStream) (match bool, reason string) { | func (c *Cluster) compareStreams(curEventStreams, newEventStreams *zalandov1.FabricEventStream) (match bool, reason string) { | ||||||
|  | 	reasons := make([]string, 0) | ||||||
|  | 	match = true | ||||||
|  | 
 | ||||||
|  | 	// stream operator can add extra annotations so incl. current annotations in desired annotations
 | ||||||
|  | 	desiredAnnotations := c.annotationsSet(curEventStreams.Annotations) | ||||||
|  | 	if changed, reason := c.compareAnnotations(curEventStreams.ObjectMeta.Annotations, desiredAnnotations); changed { | ||||||
|  | 		match = false | ||||||
|  | 		reasons = append(reasons, fmt.Sprintf("new streams annotations do not match: %s", reason)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if changed, reason := sameEventStreams(curEventStreams.Spec.EventStreams, newEventStreams.Spec.EventStreams); !changed { | ||||||
|  | 		match = false | ||||||
|  | 		reasons = append(reasons, fmt.Sprintf("new streams EventStreams array does not match : %s", reason)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return match, strings.Join(reasons, ", ") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func sameEventStreams(curEventStreams, newEventStreams []zalandov1.EventStream) (match bool, reason string) { | ||||||
| 	if len(newEventStreams) != len(curEventStreams) { | 	if len(newEventStreams) != len(curEventStreams) { | ||||||
| 		return false, "number of defined streams is different" | 		return false, "number of defined streams is different" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -18,29 +18,25 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	"k8s.io/client-go/kubernetes/fake" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func newFakeK8sStreamClient() (k8sutil.KubernetesClient, *fake.Clientset) { |  | ||||||
| 	zalandoClientSet := fakezalandov1.NewSimpleClientset() |  | ||||||
| 	clientSet := fake.NewSimpleClientset() |  | ||||||
| 
 |  | ||||||
| 	return k8sutil.KubernetesClient{ |  | ||||||
| 		FabricEventStreamsGetter: zalandoClientSet.ZalandoV1(), |  | ||||||
| 		PostgresqlsGetter:        zalandoClientSet.AcidV1(), |  | ||||||
| 		PodsGetter:               clientSet.CoreV1(), |  | ||||||
| 		StatefulSetsGetter:       clientSet.AppsV1(), |  | ||||||
| 	}, clientSet |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var ( | var ( | ||||||
| 	clusterName string = "acid-test-cluster" | 	clusterName string = "acid-stream-cluster" | ||||||
| 	namespace   string = "default" | 	namespace   string = "default" | ||||||
| 	appId       string = "test-app" | 	appId       string = "test-app" | ||||||
| 	dbName      string = "foo" | 	dbName      string = "foo" | ||||||
| 	fesUser     string = fmt.Sprintf("%s%s", constants.EventStreamSourceSlotPrefix, constants.UserRoleNameSuffix) | 	fesUser     string = fmt.Sprintf("%s%s", constants.EventStreamSourceSlotPrefix, constants.UserRoleNameSuffix) | ||||||
| 	slotName    string = fmt.Sprintf("%s_%s_%s", constants.EventStreamSourceSlotPrefix, dbName, strings.Replace(appId, "-", "_", -1)) | 	slotName    string = fmt.Sprintf("%s_%s_%s", constants.EventStreamSourceSlotPrefix, dbName, strings.Replace(appId, "-", "_", -1)) | ||||||
| 
 | 
 | ||||||
|  | 	zalandoClientSet = fakezalandov1.NewSimpleClientset() | ||||||
|  | 
 | ||||||
|  | 	client = k8sutil.KubernetesClient{ | ||||||
|  | 		FabricEventStreamsGetter: zalandoClientSet.ZalandoV1(), | ||||||
|  | 		PostgresqlsGetter:        zalandoClientSet.AcidV1(), | ||||||
|  | 		PodsGetter:               clientSet.CoreV1(), | ||||||
|  | 		StatefulSetsGetter:       clientSet.AppsV1(), | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	pg = acidv1.Postgresql{ | 	pg = acidv1.Postgresql{ | ||||||
| 		TypeMeta: metav1.TypeMeta{ | 		TypeMeta: metav1.TypeMeta{ | ||||||
| 			Kind:       "Postgresql", | 			Kind:       "Postgresql", | ||||||
|  | @ -181,6 +177,25 @@ var ( | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	cluster = New( | ||||||
|  | 		Config{ | ||||||
|  | 			OpConfig: config.Config{ | ||||||
|  | 				Auth: config.Auth{ | ||||||
|  | 					SecretNameTemplate: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}", | ||||||
|  | 				}, | ||||||
|  | 				PodManagementPolicy: "ordered_ready", | ||||||
|  | 				Resources: config.Resources{ | ||||||
|  | 					ClusterLabels:        map[string]string{"application": "spilo"}, | ||||||
|  | 					ClusterNameLabel:     "cluster-name", | ||||||
|  | 					DefaultCPURequest:    "300m", | ||||||
|  | 					DefaultCPULimit:      "300m", | ||||||
|  | 					DefaultMemoryRequest: "300Mi", | ||||||
|  | 					DefaultMemoryLimit:   "300Mi", | ||||||
|  | 					PodRoleLabel:         "spilo-role", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, client, pg, logger, eventRecorder) | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestGatherApplicationIds(t *testing.T) { | func TestGatherApplicationIds(t *testing.T) { | ||||||
|  | @ -193,15 +208,24 @@ func TestGatherApplicationIds(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestHasSlotsInSync(t *testing.T) { | func TestHasSlotsInSync(t *testing.T) { | ||||||
|  | 	cluster.Name = clusterName | ||||||
|  | 	cluster.Namespace = namespace | ||||||
|  | 
 | ||||||
|  | 	appId2 := fmt.Sprintf("%s-2", appId) | ||||||
|  | 	dbNotExists := "dbnotexists" | ||||||
|  | 	slotNotExists := fmt.Sprintf("%s_%s_%s", constants.EventStreamSourceSlotPrefix, dbNotExists, strings.Replace(appId, "-", "_", -1)) | ||||||
|  | 	slotNotExistsAppId2 := fmt.Sprintf("%s_%s_%s", constants.EventStreamSourceSlotPrefix, dbNotExists, strings.Replace(appId2, "-", "_", -1)) | ||||||
| 
 | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		subTest       string | 		subTest       string | ||||||
|  | 		applicationId string | ||||||
| 		expectedSlots map[string]map[string]zalandov1.Slot | 		expectedSlots map[string]map[string]zalandov1.Slot | ||||||
| 		actualSlots   map[string]map[string]string | 		actualSlots   map[string]map[string]string | ||||||
| 		slotsInSync   bool | 		slotsInSync   bool | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			subTest: "slots are in sync", | 			subTest:       fmt.Sprintf("slots in sync for applicationId %s", appId), | ||||||
|  | 			applicationId: appId, | ||||||
| 			expectedSlots: map[string]map[string]zalandov1.Slot{ | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
| 				dbName: { | 				dbName: { | ||||||
| 					slotName: zalandov1.Slot{ | 					slotName: zalandov1.Slot{ | ||||||
|  | @ -227,7 +251,52 @@ func TestHasSlotsInSync(t *testing.T) { | ||||||
| 			}, | 			}, | ||||||
| 			slotsInSync: true, | 			slotsInSync: true, | ||||||
| 		}, { | 		}, { | ||||||
| 			subTest: "slots are not in sync", | 			subTest:       fmt.Sprintf("slots empty for applicationId %s after create or update of publication failed", appId), | ||||||
|  | 			applicationId: appId, | ||||||
|  | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
|  | 				dbNotExists: { | ||||||
|  | 					slotNotExists: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": dbName, | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test1": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-a", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			actualSlots: map[string]map[string]string{}, | ||||||
|  | 			slotsInSync: false, | ||||||
|  | 		}, { | ||||||
|  | 			subTest:       fmt.Sprintf("slot with empty definition for applicationId %s after publication git deleted", appId), | ||||||
|  | 			applicationId: appId, | ||||||
|  | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
|  | 				dbNotExists: { | ||||||
|  | 					slotNotExists: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": dbName, | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test1": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-a", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			actualSlots: map[string]map[string]string{ | ||||||
|  | 				slotName: nil, | ||||||
|  | 			}, | ||||||
|  | 			slotsInSync: false, | ||||||
|  | 		}, { | ||||||
|  | 			subTest:       fmt.Sprintf("one slot not in sync for applicationId %s because database does not exist", appId), | ||||||
|  | 			applicationId: appId, | ||||||
| 			expectedSlots: map[string]map[string]zalandov1.Slot{ | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
| 				dbName: { | 				dbName: { | ||||||
| 					slotName: zalandov1.Slot{ | 					slotName: zalandov1.Slot{ | ||||||
|  | @ -243,8 +312,90 @@ func TestHasSlotsInSync(t *testing.T) { | ||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				"dbnotexists": { | 				dbNotExists: { | ||||||
|  | 					slotNotExists: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": "dbnotexists", | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test2": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-b", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			actualSlots: map[string]map[string]string{ | ||||||
|  | 				slotName: map[string]string{ | ||||||
|  | 					"databases": dbName, | ||||||
|  | 					"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 					"type":      "logical", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			slotsInSync: false, | ||||||
|  | 		}, { | ||||||
|  | 			subTest:       fmt.Sprintf("slots in sync for applicationId %s, but not for %s - checking %s should return true", appId, appId2, appId), | ||||||
|  | 			applicationId: appId, | ||||||
|  | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
|  | 				dbName: { | ||||||
| 					slotName: zalandov1.Slot{ | 					slotName: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": dbName, | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test1": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-a", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				dbNotExists: { | ||||||
|  | 					slotNotExistsAppId2: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": "dbnotexists", | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test2": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-b", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			actualSlots: map[string]map[string]string{ | ||||||
|  | 				slotName: map[string]string{ | ||||||
|  | 					"databases": dbName, | ||||||
|  | 					"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 					"type":      "logical", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			slotsInSync: true, | ||||||
|  | 		}, { | ||||||
|  | 			subTest:       fmt.Sprintf("slots in sync for applicationId %s, but not for %s - checking %s should return false", appId, appId2, appId2), | ||||||
|  | 			applicationId: appId2, | ||||||
|  | 			expectedSlots: map[string]map[string]zalandov1.Slot{ | ||||||
|  | 				dbName: { | ||||||
|  | 					slotName: zalandov1.Slot{ | ||||||
|  | 						Slot: map[string]string{ | ||||||
|  | 							"databases": dbName, | ||||||
|  | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | 							"type":      "logical", | ||||||
|  | 						}, | ||||||
|  | 						Publication: map[string]acidv1.StreamTable{ | ||||||
|  | 							"test1": acidv1.StreamTable{ | ||||||
|  | 								EventType: "stream-type-a", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				dbNotExists: { | ||||||
|  | 					slotNotExistsAppId2: zalandov1.Slot{ | ||||||
| 						Slot: map[string]string{ | 						Slot: map[string]string{ | ||||||
| 							"databases": "dbnotexists", | 							"databases": "dbnotexists", | ||||||
| 							"plugin":    constants.EventStreamSourcePluginType, | 							"plugin":    constants.EventStreamSourcePluginType, | ||||||
|  | @ -270,35 +421,14 @@ func TestHasSlotsInSync(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
| 		result := hasSlotsInSync(appId, tt.expectedSlots, tt.actualSlots) | 		result := hasSlotsInSync(tt.applicationId, tt.expectedSlots, tt.actualSlots) | ||||||
| 		if !result { | 		if result != tt.slotsInSync { | ||||||
| 			t.Errorf("slots are not in sync, expected %#v, got %#v", tt.expectedSlots, tt.actualSlots) | 			t.Errorf("%s: unexpected result for slot test of applicationId: %v, expected slots %#v, actual slots %#v", tt.subTest, tt.applicationId, tt.expectedSlots, tt.actualSlots) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestGenerateFabricEventStream(t *testing.T) { | func TestGenerateFabricEventStream(t *testing.T) { | ||||||
| 	client, _ := newFakeK8sStreamClient() |  | ||||||
| 
 |  | ||||||
| 	var cluster = New( |  | ||||||
| 		Config{ |  | ||||||
| 			OpConfig: config.Config{ |  | ||||||
| 				Auth: config.Auth{ |  | ||||||
| 					SecretNameTemplate: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}", |  | ||||||
| 				}, |  | ||||||
| 				PodManagementPolicy: "ordered_ready", |  | ||||||
| 				Resources: config.Resources{ |  | ||||||
| 					ClusterLabels:        map[string]string{"application": "spilo"}, |  | ||||||
| 					ClusterNameLabel:     "cluster-name", |  | ||||||
| 					DefaultCPURequest:    "300m", |  | ||||||
| 					DefaultCPULimit:      "300m", |  | ||||||
| 					DefaultMemoryRequest: "300Mi", |  | ||||||
| 					DefaultMemoryLimit:   "300Mi", |  | ||||||
| 					PodRoleLabel:         "spilo-role", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, client, pg, logger, eventRecorder) |  | ||||||
| 
 |  | ||||||
| 	cluster.Name = clusterName | 	cluster.Name = clusterName | ||||||
| 	cluster.Namespace = namespace | 	cluster.Namespace = namespace | ||||||
| 
 | 
 | ||||||
|  | @ -312,7 +442,7 @@ func TestGenerateFabricEventStream(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	// compare generated stream with expected stream
 | 	// compare generated stream with expected stream
 | ||||||
| 	result := cluster.generateFabricEventStream(appId) | 	result := cluster.generateFabricEventStream(appId) | ||||||
| 	if match, _ := sameStreams(result.Spec.EventStreams, fes.Spec.EventStreams); !match { | 	if match, _ := cluster.compareStreams(result, fes); !match { | ||||||
| 		t.Errorf("malformed FabricEventStream, expected %#v, got %#v", fes, result) | 		t.Errorf("malformed FabricEventStream, expected %#v, got %#v", fes, result) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -328,7 +458,7 @@ func TestGenerateFabricEventStream(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// compare stream returned from API with expected stream
 | 	// compare stream returned from API with expected stream
 | ||||||
| 	if match, _ := sameStreams(streams.Items[0].Spec.EventStreams, fes.Spec.EventStreams); !match { | 	if match, _ := cluster.compareStreams(&streams.Items[0], fes); !match { | ||||||
| 		t.Errorf("malformed FabricEventStream returned from API, expected %#v, got %#v", fes, streams.Items[0]) | 		t.Errorf("malformed FabricEventStream returned from API, expected %#v, got %#v", fes, streams.Items[0]) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -345,13 +475,28 @@ func TestGenerateFabricEventStream(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// compare stream resturned from API with generated stream
 | 	// compare stream resturned from API with generated stream
 | ||||||
| 	if match, _ := sameStreams(streams.Items[0].Spec.EventStreams, result.Spec.EventStreams); !match { | 	if match, _ := cluster.compareStreams(&streams.Items[0], result); !match { | ||||||
| 		t.Errorf("returned FabricEventStream differs from generated one, expected %#v, got %#v", result, streams.Items[0]) | 		t.Errorf("returned FabricEventStream differs from generated one, expected %#v, got %#v", result, streams.Items[0]) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func newFabricEventStream(streams []zalandov1.EventStream, annotations map[string]string) *zalandov1.FabricEventStream { | ||||||
|  | 	return &zalandov1.FabricEventStream{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:        fmt.Sprintf("%s-12345", clusterName), | ||||||
|  | 			Annotations: annotations, | ||||||
|  | 		}, | ||||||
|  | 		Spec: zalandov1.FabricEventStreamSpec{ | ||||||
|  | 			ApplicationId: appId, | ||||||
|  | 			EventStreams:  streams, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func TestSameStreams(t *testing.T) { | func TestSameStreams(t *testing.T) { | ||||||
| 	testName := "TestSameStreams" | 	testName := "TestSameStreams" | ||||||
|  | 	annotationsA := map[string]string{"owned-by": "acid"} | ||||||
|  | 	annotationsB := map[string]string{"owned-by": "foo"} | ||||||
| 
 | 
 | ||||||
| 	stream1 := zalandov1.EventStream{ | 	stream1 := zalandov1.EventStream{ | ||||||
| 		EventStreamFlow:     zalandov1.EventStreamFlow{}, | 		EventStreamFlow:     zalandov1.EventStreamFlow{}, | ||||||
|  | @ -396,57 +541,64 @@ func TestSameStreams(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		subTest  string | 		subTest  string | ||||||
| 		streamsA []zalandov1.EventStream | 		streamsA *zalandov1.FabricEventStream | ||||||
| 		streamsB []zalandov1.EventStream | 		streamsB *zalandov1.FabricEventStream | ||||||
| 		match    bool | 		match    bool | ||||||
| 		reason   string | 		reason   string | ||||||
| 	}{ | 	}{ | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "identical streams", | 			subTest:  "identical streams", | ||||||
| 			streamsA: []zalandov1.EventStream{stream1, stream2}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, annotationsA), | ||||||
| 			streamsB: []zalandov1.EventStream{stream1, stream2}, | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, annotationsA), | ||||||
| 			match:    true, | 			match:    true, | ||||||
| 			reason:   "", | 			reason:   "", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "same streams different order", | 			subTest:  "same streams different order", | ||||||
| 			streamsA: []zalandov1.EventStream{stream1, stream2}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, nil), | ||||||
| 			streamsB: []zalandov1.EventStream{stream2, stream1}, | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream2, stream1}, nil), | ||||||
| 			match:    true, | 			match:    true, | ||||||
| 			reason:   "", | 			reason:   "", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "same streams different order", | 			subTest:  "same streams different order", | ||||||
| 			streamsA: []zalandov1.EventStream{stream1}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream1}, nil), | ||||||
| 			streamsB: []zalandov1.EventStream{stream1, stream2}, | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, nil), | ||||||
| 			match:    false, | 			match:    false, | ||||||
| 			reason:   "number of defined streams is different", | 			reason:   "number of defined streams is different", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "different number of streams", | 			subTest:  "different number of streams", | ||||||
| 			streamsA: []zalandov1.EventStream{stream1}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream1}, nil), | ||||||
| 			streamsB: []zalandov1.EventStream{stream1, stream2}, | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, nil), | ||||||
| 			match:    false, | 			match:    false, | ||||||
| 			reason:   "number of defined streams is different", | 			reason:   "number of defined streams is different", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "event stream specs differ", | 			subTest:  "event stream specs differ", | ||||||
| 			streamsA: []zalandov1.EventStream{stream1, stream2}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream1, stream2}, nil), | ||||||
| 			streamsB: fes.Spec.EventStreams, | 			streamsB: fes, | ||||||
| 			match:    false, | 			match:    false, | ||||||
| 			reason:   "number of defined streams is different", | 			reason:   "number of defined streams is different", | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			subTest:  "event stream recovery specs differ", | 			subTest:  "event stream recovery specs differ", | ||||||
| 			streamsA: []zalandov1.EventStream{stream2}, | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream2}, nil), | ||||||
| 			streamsB: []zalandov1.EventStream{stream3}, | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream3}, nil), | ||||||
|  | 			match:    false, | ||||||
|  | 			reason:   "event stream specs differ", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			subTest:  "event stream annotations differ", | ||||||
|  | 			streamsA: newFabricEventStream([]zalandov1.EventStream{stream2}, annotationsA), | ||||||
|  | 			streamsB: newFabricEventStream([]zalandov1.EventStream{stream3}, annotationsB), | ||||||
| 			match:    false, | 			match:    false, | ||||||
| 			reason:   "event stream specs differ", | 			reason:   "event stream specs differ", | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
| 		streamsMatch, matchReason := sameStreams(tt.streamsA, tt.streamsB) | 		streamsMatch, matchReason := cluster.compareStreams(tt.streamsA, tt.streamsB) | ||||||
| 		if streamsMatch != tt.match { | 		if streamsMatch != tt.match { | ||||||
| 			t.Errorf("%s %s: unexpected match result when comparing streams: got %s, epxected %s", | 			t.Errorf("%s %s: unexpected match result when comparing streams: got %s, epxected %s", | ||||||
| 				testName, tt.subTest, matchReason, tt.reason) | 				testName, tt.subTest, matchReason, tt.reason) | ||||||
|  | @ -455,8 +607,7 @@ func TestSameStreams(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestUpdateFabricEventStream(t *testing.T) { | func TestUpdateFabricEventStream(t *testing.T) { | ||||||
| 	client, _ := newFakeK8sStreamClient() | 	pg.Name = fmt.Sprintf("%s-2", pg.Name) | ||||||
| 
 |  | ||||||
| 	var cluster = New( | 	var cluster = New( | ||||||
| 		Config{ | 		Config{ | ||||||
| 			OpConfig: config.Config{ | 			OpConfig: config.Config{ | ||||||
|  | @ -502,7 +653,7 @@ func TestUpdateFabricEventStream(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| 	streams := patchPostgresqlStreams(t, cluster, &pg.Spec, listOptions) | 	streams := patchPostgresqlStreams(t, cluster, &pg.Spec, listOptions) | ||||||
| 	result := cluster.generateFabricEventStream(appId) | 	result := cluster.generateFabricEventStream(appId) | ||||||
| 	if match, _ := sameStreams(streams.Items[0].Spec.EventStreams, result.Spec.EventStreams); !match { | 	if match, _ := cluster.compareStreams(&streams.Items[0], result); !match { | ||||||
| 		t.Errorf("Malformed FabricEventStream after updating manifest, expected %#v, got %#v", streams.Items[0], result) | 		t.Errorf("Malformed FabricEventStream after updating manifest, expected %#v, got %#v", streams.Items[0], result) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -516,7 +667,7 @@ func TestUpdateFabricEventStream(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	streams = patchPostgresqlStreams(t, cluster, &pg.Spec, listOptions) | 	streams = patchPostgresqlStreams(t, cluster, &pg.Spec, listOptions) | ||||||
| 	result = cluster.generateFabricEventStream(appId) | 	result = cluster.generateFabricEventStream(appId) | ||||||
| 	if match, _ := sameStreams(streams.Items[0].Spec.EventStreams, result.Spec.EventStreams); !match { | 	if match, _ := cluster.compareStreams(&streams.Items[0], result); !match { | ||||||
| 		t.Errorf("Malformed FabricEventStream after disabling event recovery, expected %#v, got %#v", streams.Items[0], result) | 		t.Errorf("Malformed FabricEventStream after disabling event recovery, expected %#v, got %#v", streams.Items[0], result) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -228,6 +228,7 @@ func (c *Cluster) syncPatroniConfigMap(suffix string) error { | ||||||
| 		} | 		} | ||||||
| 		annotations := make(map[string]string) | 		annotations := make(map[string]string) | ||||||
| 		maps.Copy(annotations, cm.Annotations) | 		maps.Copy(annotations, cm.Annotations) | ||||||
|  | 		// Patroni can add extra annotations so incl. current annotations in desired annotations
 | ||||||
| 		desiredAnnotations := c.annotationsSet(cm.Annotations) | 		desiredAnnotations := c.annotationsSet(cm.Annotations) | ||||||
| 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | ||||||
| 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | ||||||
|  | @ -272,6 +273,7 @@ func (c *Cluster) syncPatroniEndpoint(suffix string) error { | ||||||
| 		} | 		} | ||||||
| 		annotations := make(map[string]string) | 		annotations := make(map[string]string) | ||||||
| 		maps.Copy(annotations, ep.Annotations) | 		maps.Copy(annotations, ep.Annotations) | ||||||
|  | 		// Patroni can add extra annotations so incl. current annotations in desired annotations
 | ||||||
| 		desiredAnnotations := c.annotationsSet(ep.Annotations) | 		desiredAnnotations := c.annotationsSet(ep.Annotations) | ||||||
| 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | ||||||
| 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | ||||||
|  | @ -315,6 +317,7 @@ func (c *Cluster) syncPatroniService() error { | ||||||
| 		} | 		} | ||||||
| 		annotations := make(map[string]string) | 		annotations := make(map[string]string) | ||||||
| 		maps.Copy(annotations, svc.Annotations) | 		maps.Copy(annotations, svc.Annotations) | ||||||
|  | 		// Patroni can add extra annotations so incl. current annotations in desired annotations
 | ||||||
| 		desiredAnnotations := c.annotationsSet(svc.Annotations) | 		desiredAnnotations := c.annotationsSet(svc.Annotations) | ||||||
| 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | 		if changed, _ := c.compareAnnotations(annotations, desiredAnnotations); changed { | ||||||
| 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | 			patchData, err := metaAnnotationsPatch(desiredAnnotations) | ||||||
|  |  | ||||||
|  | @ -663,15 +663,15 @@ func parseResourceRequirements(resourcesRequirement v1.ResourceRequirements) (ac | ||||||
| 	return resources, nil | 	return resources, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Cluster) isInMainternanceWindow() bool { | func isInMainternanceWindow(specMaintenanceWindows []acidv1.MaintenanceWindow) bool { | ||||||
| 	if c.Spec.MaintenanceWindows == nil { | 	if len(specMaintenanceWindows) == 0 { | ||||||
| 		return true | 		return true | ||||||
| 	} | 	} | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 	currentDay := now.Weekday() | 	currentDay := now.Weekday() | ||||||
| 	currentTime := now.Format("15:04") | 	currentTime := now.Format("15:04") | ||||||
| 
 | 
 | ||||||
| 	for _, window := range c.Spec.MaintenanceWindows { | 	for _, window := range specMaintenanceWindows { | ||||||
| 		startTime := window.StartTime.Format("15:04") | 		startTime := window.StartTime.Format("15:04") | ||||||
| 		endTime := window.EndTime.Format("15:04") | 		endTime := window.EndTime.Format("15:04") | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -651,24 +651,6 @@ func Test_trimCronjobName(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestIsInMaintenanceWindow(t *testing.T) { | func TestIsInMaintenanceWindow(t *testing.T) { | ||||||
| 	client, _ := newFakeK8sStreamClient() |  | ||||||
| 
 |  | ||||||
| 	var cluster = New( |  | ||||||
| 		Config{ |  | ||||||
| 			OpConfig: config.Config{ |  | ||||||
| 				PodManagementPolicy: "ordered_ready", |  | ||||||
| 				Resources: config.Resources{ |  | ||||||
| 					ClusterLabels:        map[string]string{"application": "spilo"}, |  | ||||||
| 					ClusterNameLabel:     "cluster-name", |  | ||||||
| 					DefaultCPURequest:    "300m", |  | ||||||
| 					DefaultCPULimit:      "300m", |  | ||||||
| 					DefaultMemoryRequest: "300Mi", |  | ||||||
| 					DefaultMemoryLimit:   "300Mi", |  | ||||||
| 					PodRoleLabel:         "spilo-role", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, client, pg, logger, eventRecorder) |  | ||||||
| 
 |  | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 	futureTimeStart := now.Add(1 * time.Hour) | 	futureTimeStart := now.Add(1 * time.Hour) | ||||||
| 	futureTimeStartFormatted := futureTimeStart.Format("15:04") | 	futureTimeStartFormatted := futureTimeStart.Format("15:04") | ||||||
|  | @ -723,7 +705,7 @@ func TestIsInMaintenanceWindow(t *testing.T) { | ||||||
| 	for _, tt := range tests { | 	for _, tt := range tests { | ||||||
| 		t.Run(tt.name, func(t *testing.T) { | 		t.Run(tt.name, func(t *testing.T) { | ||||||
| 			cluster.Spec.MaintenanceWindows = tt.windows | 			cluster.Spec.MaintenanceWindows = tt.windows | ||||||
| 			if cluster.isInMainternanceWindow() != tt.expected { | 			if isInMainternanceWindow(cluster.Spec.MaintenanceWindows) != tt.expected { | ||||||
| 				t.Errorf("Expected isInMainternanceWindow to return %t", tt.expected) | 				t.Errorf("Expected isInMainternanceWindow to return %t", tt.expected) | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue