diff --git a/chart/searchlight/templates/secret.yaml b/chart/searchlight/templates/secret.yaml index 9f91603a0..302ce7440 100644 --- a/chart/searchlight/templates/secret.yaml +++ b/chart/searchlight/templates/secret.yaml @@ -13,6 +13,10 @@ data: ICINGA_WEB_UI_PASSWORD: {{ .Values.icinga2web.password | b64enc | quote }} {{ else }} ICINGA_WEB_UI_PASSWORD: {{ randAlphaNum 10 | b64enc | quote }} + {{- if .Values.icinga2.password }} + ICINGA_API_PASSWORD: {{ .Values.icinga2.password | b64enc | quote }} + {{ else }} + ICINGA_API_PASSWORD: {{ randAlphaNum 10 | b64enc | quote }} {{ end -}} {{- if .Values.notifier.hipchat.authToken }} HIPCHAT_AUTH_TOKEN: {{ .Values.notifier.hipchat.authToken | b64enc | quote }} diff --git a/chart/searchlight/values.yaml b/chart/searchlight/values.yaml index 46ea3ea4b..187ff4579 100644 --- a/chart/searchlight/values.yaml +++ b/chart/searchlight/values.yaml @@ -42,6 +42,9 @@ logLevel: 3 icinga2web: password: changeit +icinga2: + password: + notifier: hipchat: authToken: '' diff --git a/docs/setup/install.md b/docs/setup/install.md index 227600475..76cb94b96 100644 --- a/docs/setup/install.md +++ b/docs/setup/install.md @@ -52,6 +52,7 @@ options: --image-pull-secret name of secret used to pull searchlight operator images --run-on-master run searchlight operator on master --enable-validating-webhook enable/disable validating webhooks for Searchlight CRD + --icinga-api-password password used by icinga2 api (if unset, a random password will be generated and used) --enable-analytics send usage events to Google Analytics (default: true) --uninstall uninstall searchlight --purge purges searchlight crd objects and crds diff --git a/hack/deploy/operator.yaml b/hack/deploy/operator.yaml index 333bf4084..b66a8412a 100644 --- a/hack/deploy/operator.yaml +++ b/hack/deploy/operator.yaml @@ -7,6 +7,7 @@ metadata: app: searchlight data: ICINGA_WEB_UI_PASSWORD: Y2hhbmdlaXQ= + ${SEARCHLIGHT_ICINGA_API_PASSWORD} --- apiVersion: apps/v1beta1 kind: Deployment diff --git a/hack/deploy/searchlight.sh b/hack/deploy/searchlight.sh index 07d4fd0ab..2db4f1e71 100755 --- a/hack/deploy/searchlight.sh +++ b/hack/deploy/searchlight.sh @@ -91,6 +91,7 @@ export SEARCHLIGHT_NAMESPACE=kube-system export SEARCHLIGHT_SERVICE_ACCOUNT=searchlight-operator export SEARCHLIGHT_ENABLE_RBAC=true export SEARCHLIGHT_RUN_ON_MASTER=0 +export SEARCHLIGHT_ICINGA_API_PASSWORD= export SEARCHLIGHT_ENABLE_VALIDATING_WEBHOOK=false export SEARCHLIGHT_DOCKER_REGISTRY=appscode export SEARCHLIGHT_OPERATOR_TAG=7.0.0-rc.0 @@ -127,6 +128,7 @@ show_help() { echo " --image-pull-secret name of secret used to pull searchlight operator images" echo " --run-on-master run searchlight operator on master" echo " --enable-validating-webhook enable/disable validating webhooks for Searchlight CRDs" + echo " --icinga-api-password password used by icinga2 api (if unset, a random password will be generated and used)" echo " --enable-analytics send usage events to Google Analytics (default: true)" echo " --uninstall uninstall searchlight" echo " --purge purges searchlight crd objects and crds" @@ -187,6 +189,12 @@ while test $# -gt 0; do export SEARCHLIGHT_RUN_ON_MASTER=1 shift ;; + --icinga-api-password*) + pass=`echo $1 | sed -e 's/^[^=]*=//g'` + pass_b64=`echo -n $pass | $ONESSL base64` + export SEARCHLIGHT_ICINGA_API_PASSWORD="ICINGA_API_PASSWORD: '$pass_b64'" + shift + ;; --uninstall) export SEARCHLIGHT_UNINSTALL=1 shift diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index b4ed99a15..8afb46bf7 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -13,7 +13,6 @@ import ( "github.com/appscode/searchlight/pkg/icinga" "github.com/appscode/searchlight/pkg/operator" "github.com/appscode/searchlight/test/e2e/framework" - . "github.com/appscode/searchlight/test/e2e/matcher" . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/reporters" . "github.com/onsi/gomega" @@ -26,15 +25,15 @@ import ( ) var ( - provider string - storageClass string - providedIcinga string + provider string + storageClass string + searchlightService string ) func init() { flag.StringVar(&provider, "provider", "minikube", "Kubernetes cloud provider") flag.StringVar(&storageClass, "storageclass", "", "Kubernetes StorageClass name") - flag.StringVar(&providedIcinga, "provided-icinga", "", "Running Icinga reference") + flag.StringVar(&searchlightService, "searchlight-service", "", "Running searchlight reference") } const ( @@ -56,6 +55,9 @@ func TestE2e(t *testing.T) { } var _ = BeforeSuite(func() { + + Expect(searchlightService).ShouldNot(BeEmpty()) + // Kubernetes config kubeconfigPath := filepath.Join(homedir.HomeDir(), ".kube/config") By("Using kubeconfig from " + kubeconfigPath) @@ -75,28 +77,13 @@ var _ = BeforeSuite(func() { err = root.CreateNamespace() Expect(err).NotTo(HaveOccurred()) - var slService *core.Service - if providedIcinga == "" { - // Create Searchlight deployment - slDeployment := root.Invoke().DeploymentSearchlight() - err = root.CreateDeployment(slDeployment) - Expect(err).NotTo(HaveOccurred()) - By("Waiting for Running pods") - root.EventuallyDeployment(slDeployment.ObjectMeta).Should(HaveRunningPods(*slDeployment.Spec.Replicas)) - // Create Searchlight service - slService = root.Invoke().ServiceSearchlight() - err = root.CreateService(slService) - Expect(err).NotTo(HaveOccurred()) - root.EventuallyServiceLoadBalancer(slService.ObjectMeta, "icinga").Should(BeTrue()) - - } else { - parts := strings.Split(providedIcinga, "@") - om := metav1.ObjectMeta{ - Name: parts[0], - Namespace: parts[1], - } - slService = &core.Service{ObjectMeta: om} + parts := strings.Split(searchlightService, "@") + Expect(len(parts)).Should(BeIdenticalTo(2)) + om := metav1.ObjectMeta{ + Name: parts[0], + Namespace: parts[1], } + slService := &core.Service{ObjectMeta: om} // Get Icinga Ingress Hostname endpoint, err := root.GetServiceEndpoint(slService.ObjectMeta, "icinga") @@ -109,7 +96,8 @@ var _ = BeforeSuite(func() { } cfg.BasicAuth.Username = ICINGA_API_USER - cfg.BasicAuth.Password = ICINGA_API_PASSWORD + cfg.BasicAuth.Password, err = root.Invoke().GetIcingaApiPassword(om) + Expect(err).NotTo(HaveOccurred()) // Icinga Client icingaClient := icinga.NewClient(*cfg) @@ -123,21 +111,6 @@ var _ = BeforeSuite(func() { fmt.Println("Login password: ", ICINGA_WEB_UI_PASSWORD) fmt.Println() - opc := &operator.OperatorConfig{ - Config: operator.Config{ - MaxNumRequeues: 3, - NumThreads: 3, - Verbosity: "6", - }, - KubeClient: kubeClient, - CRDClient: apiExtKubeClient, - ExtClient: extClient, - IcingaClient: icingaClient, - } - // Controller - op, err = opc.New() - Expect(err).NotTo(HaveOccurred()) - go op.RunWatchers(nil) }) var _ = AfterSuite(func() { diff --git a/test/e2e/framework/httpserver.go b/test/e2e/framework/httpserver.go index d6e7d8e81..a6de261e8 100644 --- a/test/e2e/framework/httpserver.go +++ b/test/e2e/framework/httpserver.go @@ -40,14 +40,9 @@ func (f *Framework) EventuallyHTTPServerResponse(serverURL string) GomegaAsyncAs return Eventually( func() string { resp, err := http.Get(serverURL) - if err != nil { - return err.Error() - } - + Expect(err).NotTo(HaveOccurred()) data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err.Error() - } + Expect(err).NotTo(HaveOccurred()) return string(data) }, diff --git a/test/e2e/framework/notification.go b/test/e2e/framework/notification.go index 8e52eb282..22e3b5fb2 100644 --- a/test/e2e/framework/notification.go +++ b/test/e2e/framework/notification.go @@ -3,8 +3,12 @@ package framework import ( "encoding/json" "fmt" + "time" + incident_api "github.com/appscode/searchlight/apis/incidents/v1alpha1" + api "github.com/appscode/searchlight/apis/monitoring/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" ) func (f *Framework) ForceCheckClusterAlert(meta metav1.ObjectMeta, hostname string, times int) error { @@ -37,15 +41,41 @@ func (f *Framework) SendClusterAlertCustomNotification(meta metav1.ObjectMeta, h } func (f *Framework) AcknowledgeClusterAlertNotification(meta metav1.ObjectMeta, hostname string) error { - mp := make(map[string]interface{}) - mp["type"] = "Service" - mp["filter"] = fmt.Sprintf(`service.name == "%s" && host.name == "%s"`, meta.Name, hostname) - mp["author"] = "e2e" - mp["comment"] = "test" - mp["notify"] = true - ack, err := json.Marshal(mp) + + labelMap := map[string]string{ + api.LabelKeyAlert: meta.Name, + api.LabelKeyObjectName: hostname, + api.LabelKeyProblemRecovered: "false", + } + + incidentList, err := f.extClient.MonitoringV1alpha1().Incidents(meta.Namespace).List(metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(labelMap).String(), + }) if err != nil { return err } - return f.icingaClient.Actions("acknowledge-problem").Update([]string{}, string(ack)).Do().Err + + var lastCreationTimestamp time.Time + var incident *api.Incident + for _, item := range incidentList.Items { + if item.CreationTimestamp.After(lastCreationTimestamp) { + lastCreationTimestamp = item.CreationTimestamp.Time + incident = &item + } + } + + _, err = f.extClient.IncidentsV1alpha1().Acknowledgements(incident.Namespace).Create(&incident_api.Acknowledgement{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: incident.Namespace, + Name: incident.Name, + }, + Request: incident_api.AcknowledgementRequest{ + Comment: "test", + }, + }) + if err != nil { + return err + } + + return nil } diff --git a/test/e2e/framework/searchlight.go b/test/e2e/framework/searchlight.go deleted file mode 100644 index 9b8f91228..000000000 --- a/test/e2e/framework/searchlight.go +++ /dev/null @@ -1,166 +0,0 @@ -package framework - -import ( - "github.com/appscode/go/types" - apps "k8s.io/api/apps/v1beta1" - core "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -func (f *Invocation) DeploymentSearchlight() *apps.Deployment { - return &apps.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: f.name, - Namespace: f.namespace, - Labels: map[string]string{ - "app": "searchlight", - }, - }, - Spec: apps.DeploymentSpec{ - Replicas: types.Int32P(1), - Template: f.getSearchlightPodTemplate(), - }, - } -} - -func (f *Invocation) ServiceSearchlight() *core.Service { - return &core.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: f.name, - Namespace: f.namespace, - Labels: map[string]string{ - "app": "searchlight", - }, - }, - Spec: core.ServiceSpec{ - Selector: map[string]string{ - "app": "searchlight", - }, - Type: core.ServiceTypeLoadBalancer, - Ports: []core.ServicePort{ - { - Name: "icinga", - Port: 5665, - TargetPort: intstr.Parse("icinga"), - }, - { - Name: "ui", - Port: 80, - TargetPort: intstr.Parse("ui"), - }, - }, - }, - } -} - -func (f *Invocation) getSearchlightPodTemplate() core.PodTemplateSpec { - return core.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "app": "searchlight", - }, - }, - Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: "icinga", - Image: "appscode/icinga:7.0.0-rc.0-k8s", - ImagePullPolicy: core.PullIfNotPresent, - Ports: []core.ContainerPort{ - { - ContainerPort: 5665, - Name: "icinga", - }, - { - ContainerPort: 60006, - Name: "ui", - }, - }, - LivenessProbe: &core.Probe{ - Handler: core.Handler{ - HTTPGet: &core.HTTPGetAction{ - Scheme: core.URISchemeHTTPS, - Port: intstr.FromInt(5665), - Path: "/v1/status", - HTTPHeaders: []core.HTTPHeader{ - { - Name: "Authorization", - Value: "Basic c3RhdHVzdXNlcjpzdGF0dXNwYXNz", - }, - }, - }, - }, - InitialDelaySeconds: 300, - PeriodSeconds: 120, - }, - VolumeMounts: []core.VolumeMount{ - { - Name: "data", - MountPath: "/srv", - }, - }, - }, - { - Name: "ido", - Image: "appscode/postgres:9.5-alpine", - ImagePullPolicy: core.PullIfNotPresent, - Env: []core.EnvVar{ - { - Name: "PGDATA", - Value: "/var/lib/postgresql/data/pgdata", - }, - }, - Ports: []core.ContainerPort{ - { - ContainerPort: 5432, - Name: "ido", - }, - }, - VolumeMounts: []core.VolumeMount{ - { - Name: "data", - MountPath: "/var/lib/postgresql/data", - }, - }, - }, - { - Name: "busybox", - Image: "busybox", - Command: []string{ - "/bin/sh", - "-c", - "cp -rf /var/searchlight /srv/searchlight && sleep 1d", - }, - VolumeMounts: []core.VolumeMount{ - { - Name: "data", - MountPath: "/srv", - }, - { - Name: "icingaconfig", - MountPath: "/var/", - }, - }, - }, - }, - Volumes: []core.Volume{ - { - Name: "data", - VolumeSource: core.VolumeSource{ - EmptyDir: &core.EmptyDirVolumeSource{}, - }, - }, - { - Name: "icingaconfig", - VolumeSource: core.VolumeSource{ - GitRepo: &core.GitRepoVolumeSource{ - Repository: "https://github.com/appscode/icinga-testconfig.git", - Directory: ".", - }, - }, - }, - }, - }, - } -} diff --git a/test/e2e/framework/secret.go b/test/e2e/framework/secret.go index 39f855494..529317bff 100644 --- a/test/e2e/framework/secret.go +++ b/test/e2e/framework/secret.go @@ -1,7 +1,10 @@ package framework import ( + "fmt" + "github.com/appscode/go/crypto/rand" + "github.com/appscode/searchlight/pkg/icinga" core_v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -20,3 +23,17 @@ func (f *Framework) CreateWebHookSecret(obj *core_v1.Secret) error { _, err := f.kubeClient.CoreV1().Secrets(obj.Namespace).Create(obj) return err } + +func (f *Invocation) GetIcingaApiPassword(objectMeta metav1.ObjectMeta) (string, error) { + secret, err := f.kubeClient.CoreV1().Secrets(objectMeta.Namespace).Get(objectMeta.Name, metav1.GetOptions{}) + if err != nil { + return "", err + } + + pass, found := secret.Data[icinga.ICINGA_API_PASSWORD] + if !found { + return "", fmt.Errorf(`key "%s" is not found in Secret "%s/%s"`, icinga.ICINGA_API_PASSWORD, objectMeta.Namespace, objectMeta.Name) + } + + return string(pass), nil +} diff --git a/test/e2e/matcher/string_matcher.go b/test/e2e/matcher/string_matcher.go new file mode 100644 index 000000000..5065c1db6 --- /dev/null +++ b/test/e2e/matcher/string_matcher.go @@ -0,0 +1,41 @@ +package matcher + +import ( + "fmt" + "regexp" + "strings" + + "github.com/onsi/gomega/types" +) + +func ReceiveNotification(expected string) types.GomegaMatcher { + return ¬ificationMatcher{ + expected: expected, + } +} + +func ReceiveNotificationWithExp(expected string) types.GomegaMatcher { + return ¬ificationMatcher{ + expected: strings.Replace(expected, "[", `\[`, -1), + } +} + +type notificationMatcher struct { + expected string +} + +func (matcher *notificationMatcher) Match(actual interface{}) (success bool, err error) { + regexpExpected, err := regexp.Compile(matcher.expected) + if err != nil { + return false, err + } + return regexpExpected.MatchString(actual.(string)), nil +} + +func (matcher *notificationMatcher) FailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Found notification message: %v", actual) +} + +func (matcher *notificationMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Found notification message: %v", actual) +} diff --git a/test/e2e/notification_test.go b/test/e2e/notification_test.go index 8471289d7..dd9042328 100644 --- a/test/e2e/notification_test.go +++ b/test/e2e/notification_test.go @@ -32,6 +32,10 @@ var _ = Describe("notification", func() { ) BeforeEach(func() { + if root.Provider != "minikube" { + Skip("notification test is only allowed in minikube") + } + f = root.Invoke() rs = f.ReplicaSet() clusterAlert = f.ClusterAlert() @@ -75,7 +79,6 @@ var _ = Describe("notification", func() { server.Close() }) It("with webhook receiver", func() { - By("Create notifier secret: " + secret.Name) err := f.CreateWebHookSecret(secret) Expect(err).NotTo(HaveOccurred()) @@ -117,16 +120,18 @@ var _ = Describe("notification", func() { sms.NotificationType = string(api.NotificationCustom) sms.Comment = "test" - sms.Author = "e2e" + // Used in regular expression to match any author + sms.Author = "(.*)" + By("Check received notification message") - f.EventuallyHTTPServerResponse(serverURL).Should(BeIdenticalTo(sms.Render())) + f.EventuallyHTTPServerResponse(serverURL).Should(ReceiveNotificationWithExp(sms.Render())) By("Acknowledge notification") f.AcknowledgeClusterAlertNotification(clusterAlert.ObjectMeta, hostname) sms.NotificationType = string(api.NotificationAcknowledgement) By("Check received notification message") - f.EventuallyHTTPServerResponse(serverURL).Should(BeIdenticalTo(sms.Render())) + f.EventuallyHTTPServerResponse(serverURL).Should(ReceiveNotificationWithExp(sms.Render())) By("Patch ReplicaSet to increate replicas") rs, _, err = kutil_ext.PatchReplicaSet(f.KubeClient(), rs, func(set *extensions.ReplicaSet) *extensions.ReplicaSet {