diff --git a/doc/design.adoc b/doc/design.adoc index 7c52d3196..1608d8002 100644 --- a/doc/design.adoc +++ b/doc/design.adoc @@ -91,7 +91,7 @@ This document describes the types introduced by the Infinispan Operator to be co [[infinispanservicespec]] #### `InfinispanServiceSpec` -`InfinispanCacheSpec` configures aspects related to the cache or datagrid service. +`InfinispanServiceSpec` configures aspects related to the cache or datagrid service. [options="header,footer"] |======================= diff --git a/pkg/apis/infinispan/v1/infinispan_types.go b/pkg/apis/infinispan/v1/infinispan_types.go index 022d5b476..d3c829e19 100644 --- a/pkg/apis/infinispan/v1/infinispan_types.go +++ b/pkg/apis/infinispan/v1/infinispan_types.go @@ -22,6 +22,17 @@ type EndpointEncryption struct { CertSecretName string `json:"certSecretName"` } +// InfinispanServiceContainerSpec resource requirements specific for service +type InfinispanServiceContainerSpec struct { + Storage string `json:"storage"` +} + +// InfinispanServiceSpec specify configuration for specific service +type InfinispanServiceSpec struct { + Type string `json:"type"` + Container InfinispanServiceContainerSpec `json:"container"` +} + // InfinispanContainerSpec specify resource requirements per container type InfinispanContainerSpec struct { ExtraJvmOpts string `json:"extraJvmOpts"` @@ -35,6 +46,7 @@ type InfinispanSpec struct { Image string `json:"image"` Security InfinispanSecurity `json:"security"` Container InfinispanContainerSpec `json:"container"` + Service InfinispanServiceSpec `json:"service"` } // InfinispanCondition define a condition of the cluster diff --git a/pkg/apis/infinispan/v1/zz_generated.deepcopy.go b/pkg/apis/infinispan/v1/zz_generated.deepcopy.go index dfe14bc12..6c2d74d90 100644 --- a/pkg/apis/infinispan/v1/zz_generated.deepcopy.go +++ b/pkg/apis/infinispan/v1/zz_generated.deepcopy.go @@ -150,11 +150,45 @@ func (in *InfinispanSecurity) DeepCopy() *InfinispanSecurity { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfinispanServiceContainerSpec) DeepCopyInto(out *InfinispanServiceContainerSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfinispanServiceContainerSpec. +func (in *InfinispanServiceContainerSpec) DeepCopy() *InfinispanServiceContainerSpec { + if in == nil { + return nil + } + out := new(InfinispanServiceContainerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *InfinispanServiceSpec) DeepCopyInto(out *InfinispanServiceSpec) { + *out = *in + out.Container = in.Container + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfinispanServiceSpec. +func (in *InfinispanServiceSpec) DeepCopy() *InfinispanServiceSpec { + if in == nil { + return nil + } + out := new(InfinispanServiceSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InfinispanSpec) DeepCopyInto(out *InfinispanSpec) { *out = *in out.Security = in.Security out.Container = in.Container + out.Service = in.Service return } diff --git a/pkg/controller/infinispan/infinispan_controller.go b/pkg/controller/infinispan/infinispan_controller.go index 91742af40..a3a734478 100644 --- a/pkg/controller/infinispan/infinispan_controller.go +++ b/pkg/controller/infinispan/infinispan_controller.go @@ -36,6 +36,9 @@ var log = logf.Log.WithName("controller_infinispan") // DefaultImageName is used if a specific image name is not provided var DefaultImageName = getEnvWithDefault("DEFAULT_IMAGE", "registry.hub.docker.com/infinispan/server") +// DefaultPVSize default size for persistent volume +var DefaultPVSize = resource.MustParse("1Gi") + // Kubernetes object var kubernetes *ispnutil.Kubernetes @@ -148,7 +151,11 @@ func (r *ReconcileInfinispan) Reconcile(request reconcile.Request) (reconcile.Re } // Define a new deployment - dep := r.deploymentForInfinispan(infinispan, secret, configMap) + dep, err := r.deploymentForInfinispan(infinispan, secret, configMap) + if err != nil { + reqLogger.Error(err, "failed to configure new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return reconcile.Result{}, err + } reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) err = r.client.Create(context.TODO(), dep) if err != nil { @@ -323,7 +330,7 @@ func (r *ReconcileInfinispan) Reconcile(request reconcile.Request) (reconcile.Re } // deploymentForInfinispan returns an infinispan Deployment object -func (r *ReconcileInfinispan) deploymentForInfinispan(m *infinispanv1.Infinispan, secret *corev1.Secret, configMap *corev1.ConfigMap) *appsv1beta1.StatefulSet { +func (r *ReconcileInfinispan) deploymentForInfinispan(m *infinispanv1.Infinispan, secret *corev1.Secret, configMap *corev1.ConfigMap) (*appsv1beta1.StatefulSet, error) { // This field specifies the flavor of the // Infinispan cluster. "" is plain community edition (vanilla) ls := labelsForInfinispan(m.ObjectMeta.Name) @@ -367,7 +374,7 @@ func (r *ReconcileInfinispan) deploymentForInfinispan(m *infinispanv1.Infinispan } } } - + replicas := m.Spec.Replicas dep := &appsv1beta1.StatefulSet{ TypeMeta: metav1.TypeMeta{ @@ -389,6 +396,7 @@ func (r *ReconcileInfinispan) deploymentForInfinispan(m *infinispanv1.Infinispan Selector: &metav1.LabelSelector{ MatchLabels: ls, }, + Replicas: &replicas, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: ls, @@ -446,12 +454,40 @@ func (r *ReconcileInfinispan) deploymentForInfinispan(m *infinispanv1.Infinispan }, }, } + pvSize := DefaultPVSize + if m.Spec.Service.Type == "Data Grid" && m.Spec.Service.Container.Storage != "" { + var pvErr error + pvSize, pvErr = resource.ParseQuantity(m.Spec.Service.Container.Storage) + if pvErr != nil { + return nil, pvErr + } + } + dep.Spec.VolumeClaimTemplates = []corev1.PersistentVolumeClaim{{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.ObjectMeta.Name, + Namespace: m.ObjectMeta.Namespace, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{ + "ReadWriteOnce", + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: pvSize, + }, + }, + }}} + v := &dep.Spec.Template.Spec.Containers[0].VolumeMounts + *v = append(*v, corev1.VolumeMount{ + Name: m.ObjectMeta.Name, + MountPath: "/opt/infinispan/server/data", + }) setupVolumesForEncryption(m, dep) // appendVolumes(m, dep) // Set Infinispan instance as the owner and controller controllerutil.SetControllerReference(m, dep, r.scheme) - return dep + return dep, nil } func getEncryptionSecretName(m *infinispanv1.Infinispan) string { diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 4fa5018a6..c2e88c199 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -35,6 +35,8 @@ var Memory = getEnvWithDefault("INFINISPAN_MEMORY", "512Mi") var kubernetes = testutil.NewTestKubernetes() var cluster = util.NewCluster(kubernetes.Kubernetes) +var DefaultClusterName = "test-node-startup" + func TestMain(m *testing.M) { namespace := strings.ToLower(Namespace) kubernetes.DeleteNamespace(namespace) @@ -52,7 +54,6 @@ func TestSimple(t *testing.T) { fmt.Printf("%s\n", kubernetes.PublicIP()) } -var DefaultClusterName = "test-node-startup" var DefaultSpec = ispnv1.Infinispan{ TypeMeta: metav1.TypeMeta{ APIVersion: "infinispan.org/v1", @@ -241,6 +242,69 @@ func genericTestForContainerUpdated(modifier func(*ispnv1.Infinispan), verifier verifier(&ss) } +// TestPermanentCache creates a permanent caches the stop/start +// the cluster and checks that the cache is still there +func TestPermanentCache(t *testing.T) { + name := "test-permament-cache" + usr := "developer" + cacheName := "test" + // Define function for the generic stop/start test procedure + var createPermanentCache = func(ispn *ispnv1.Infinispan) { + pass, err := cluster.GetPassword(usr, util.GetSecretName(name), Namespace) + testutil.ExpectNoError(err) + routeName := fmt.Sprintf("%s-external", name) + client := &http.Client{} + hostAddr := kubernetes.WaitForExternalService(routeName, RouteTimeout, client, Namespace) + createCache(cacheName, usr, pass, hostAddr, "PERMANENT", client) + } + + var usePermanentCache = func(ispn *ispnv1.Infinispan) { + pass, err := cluster.GetPassword(usr, util.GetSecretName(name), Namespace) + testutil.ExpectNoError(err) + routeName := fmt.Sprintf("%s-external", name) + client := &http.Client{} + hostAddr := kubernetes.WaitForExternalService(routeName, RouteTimeout, client, Namespace) + key := "test" + value := "test-operator" + keyURL := fmt.Sprintf("%v/%v", cacheURL(cacheName, hostAddr), key) + putViaRoute(keyURL, value, client, usr, pass) + actual := getViaRoute(keyURL, client, usr, pass) + if actual != value { + panic(fmt.Errorf("unexpected actual returned: %v (value %v)", actual, value)) + } + deleteCache(cacheName, usr, pass, hostAddr, client) + } + + genericTestForPersistenceVolume(name, createPermanentCache, usePermanentCache) +} + +// Test if single node working correctly +func genericTestForPersistenceVolume(clusterName string, modifier func(*ispnv1.Infinispan), verifier func(*ispnv1.Infinispan)) { + // Create a resource without passing any config + // Register it + spec := DefaultSpec.DeepCopy() + spec.ObjectMeta.Name = clusterName + kubernetes.CreateInfinispan(spec, Namespace) + defer kubernetes.DeleteInfinispan(spec, SinglePodTimeout) + waitForPodsOrFail(clusterName, "http", 1) + + // Do something that needs to be permanent + modifier(spec) + + // Delete the cluster + kubernetes.DeleteInfinispan(spec, SinglePodTimeout) + + // Restart cluster + spec = DefaultSpec.DeepCopy() + spec.ObjectMeta.Name = clusterName + kubernetes.CreateInfinispan(spec, Namespace) + + waitForPodsOrFail(clusterName, "http", 1) + + // Do something that checks that permanent changes are there again + verifier(spec) +} + func waitForPodsOrFail(name, protocol string, num int) { // Wait that "num" pods are up kubernetes.WaitForPods("app=infinispan-pod", num, SinglePodTimeout, Namespace) @@ -311,7 +375,7 @@ func TestExternalService(t *testing.T) { hostAddr := kubernetes.WaitForExternalService(routeName, RouteTimeout, client, usr, pass, Namespace) cacheName := "test" - createCache(cacheName, usr, pass, hostAddr, client) + createCache(cacheName, usr, pass, hostAddr, "", client) defer deleteCache(cacheName, usr, pass, hostAddr, client) key := "test" @@ -408,7 +472,7 @@ func testAuthentication(name, usr, pass string) { cacheName := "test" createCacheBadCreds(cacheName, "badUser", "badPass", hostAddr, client) - createCache(cacheName, usr, pass, hostAddr, client) + createCache(cacheName, usr, pass, hostAddr, "", client) defer deleteCache(cacheName, usr, pass, hostAddr, client) key := "test" @@ -434,10 +498,10 @@ func (e *httpError) Error() string { return fmt.Sprintf("unexpected response %v", e.status) } -func createCache(cacheName, usr, pass, hostAddr string, client *http.Client) { +func createCache(cacheName, usr, pass, hostAddr string, flags string, client *http.Client) { httpURL := cacheURL(cacheName, hostAddr) fmt.Printf("Create cache: %v\n", httpURL) - httpEmpty(httpURL, "POST", usr, pass, client) + httpEmpty(httpURL, "POST", usr, pass, flags, client) } func createCacheBadCreds(cacheName, usr, pass, hostAddr string, client *http.Client) { @@ -451,17 +515,20 @@ func createCacheBadCreds(cacheName, usr, pass, hostAddr string, client *http.Cli panic(err) } }() - createCache(cacheName, usr, pass, hostAddr, client) + createCache(cacheName, usr, pass, hostAddr, "", client) } func deleteCache(cacheName, usr, pass, hostAddr string, client *http.Client) { httpURL := cacheURL(cacheName, hostAddr) fmt.Printf("Delete cache: %v\n", httpURL) - httpEmpty(httpURL, "DELETE", usr, pass, client) + httpEmpty(httpURL, "DELETE", usr, pass, "", client) } -func httpEmpty(httpURL string, method string, usr string, pass string, client *http.Client) { +func httpEmpty(httpURL string, method string, usr string, pass string, flags string, client *http.Client) { req, err := http.NewRequest(method, httpURL, nil) + if flags != "" { + req.Header.Set("flags", flags) + } testutil.ExpectNoError(err) req.SetBasicAuth(usr, pass)