Skip to content

Commit

Permalink
feat(analytics): adding google analytics for ZFSPV
Browse files Browse the repository at this point in the history
Whenever a volume is provisioned and de-provisioned we will send a google event with mainly following details :
1.    pvName (will shown as app title in google analytics)
2.    size of the volume
3.    event type : volume-provision, volume-deprovision
4.    storage type zfs-localpv
5.    replicacount as 1
6.    ClientId as default namespace uuid

Apart from this, we send the event once in 24 hr, which will have some info like number of nodes, node type, kubernetes version etc.

This metric is cotrolled by OPENEBS_IO_ENABLE_ANALYTICS env. We can set it to false if we don't want to send the metrics.

Signed-off-by: Pawan <pawan@mayadata.io>
  • Loading branch information
pawanpraka1 authored and kmova committed Mar 2, 2020
1 parent 0fc86d8 commit d608dba
Show file tree
Hide file tree
Showing 28 changed files with 1,731 additions and 18 deletions.
4 changes: 2 additions & 2 deletions buildscripts/zfs-driver/Dockerfile
Expand Up @@ -4,9 +4,9 @@
#

FROM ubuntu:18.04
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN apt-get update; exit 0
RUN apt-get -y install rsyslog libssl-dev xfsprogs
#RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN apt-get -y install rsyslog libssl-dev xfsprogs ca-certificates

COPY zfs-driver /usr/local/bin/
COPY entrypoint.sh /usr/local/bin/
Expand Down
7 changes: 7 additions & 0 deletions deploy/zfs-operator.yaml
Expand Up @@ -512,6 +512,9 @@ rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["*"]
- apiGroups: [""]
resources: ["persistentvolumes", "services"]
verbs: ["get", "list", "watch", "create", "delete"]
Expand Down Expand Up @@ -647,6 +650,10 @@ spec:
value: unix:///var/lib/csi/sockets/pluginproxy/csi.sock
- name: OPENEBS_NAMESPACE
value: openebs
- name: OPENEBS_IO_INSTALLER_TYPE
value: "zfs-operator"
- name: OPENEBS_IO_ENABLE_ANALYTICS
value: "true"
args :
- "--endpoint=$(OPENEBS_CSI_ENDPOINT)"
- "--plugin=$(OPENEBS_CONTROLLER_DRIVER)"
Expand Down
43 changes: 43 additions & 0 deletions pkg/client/k8s/v1alpha1/clientset.go
@@ -0,0 +1,43 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
)

// ClientsetGetter abstracts fetching of kubernetes clientset
type ClientsetGetter interface {
Get() (*kubernetes.Clientset, error)
}

type clientset struct{}

// Clientset returns a pointer to clientset struct
func Clientset() *clientset {
return &clientset{}
}

// Get returns a new instance of kubernetes clientset
func (c *clientset) Get() (*kubernetes.Clientset, error) {
config, err := Config().Get()
if err != nil {
return nil, errors.Wrap(err, "failed to get kubernetes clientset")
}
return kubernetes.NewForConfig(config)
}
38 changes: 38 additions & 0 deletions pkg/client/k8s/v1alpha1/clientset_test.go
@@ -0,0 +1,38 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"testing"
)

func TestClientsetGet(t *testing.T) {
tests := map[string]struct {
iserr bool
}{
"101": {true},
}

for name, mock := range tests {
t.Run(name, func(t *testing.T) {
_, err := Clientset().Get()
if !mock.iserr && err != nil {
t.Fatalf("Test '%s' failed: expected no error: actual '%s'", name, err)
}
})
}
}
99 changes: 99 additions & 0 deletions pkg/client/k8s/v1alpha1/config.go
@@ -0,0 +1,99 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/openebs/zfs-localpv/pkg/common/env"
"github.com/pkg/errors"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"strings"
)

// ConfigGetter abstracts fetching of kubernetes client config
type ConfigGetter interface {
Get() (*rest.Config, error)
Name() string
}

// configFromENV is an implementation of ConfigGetter
type configFromENV struct{}

// Name returns the name of this config getter instance
func (c *configFromENV) Name() string {
return "k8s-config-from-env"
}

// Get returns kubernetes rest config based on kubernetes environment values
func (c *configFromENV) Get() (*rest.Config, error) {
k8sMaster := env.Get(env.KubeMaster)
kubeConfig := env.Get(env.KubeConfig)

if len(strings.TrimSpace(k8sMaster)) == 0 && len(strings.TrimSpace(kubeConfig)) == 0 {
return nil, errors.New("missing kubernetes master as well as kubeconfig: failed to get kubernetes client config")
}

return clientcmd.BuildConfigFromFlags(k8sMaster, kubeConfig)
}

// configFromREST is an implementation of ConfigGetter
type configFromREST struct{}

// Name returns the name of this config getter instance
func (c *configFromREST) Name() string {
return "k8s-config-from-rest"
}

// Get returns kubernetes rest config based on in-cluster config implementation
func (c *configFromREST) Get() (*rest.Config, error) {
return rest.InClusterConfig()
}

// ConfigGetters holds a list of ConfigGetter instances
//
// NOTE:
// This is an implementation of ConfigGetter
type ConfigGetters []ConfigGetter

// Name returns the name of this config getter instance
func (c ConfigGetters) Name() string {
return "list-of-k8s-config-getter"
}

// Get fetches the kubernetes client config that is used to make kubernetes API
// calls. It makes use of its list of getter instances to fetch kubernetes
// config.
func (c ConfigGetters) Get() (config *rest.Config, err error) {
var errs []error
for _, g := range c {
config, err = g.Get()
if err == nil {
return
}
errs = append(errs, errors.Wrapf(err, "failed to get kubernetes client config via %s", g.Name()))
}
// at this point; all getters have failed
err = errors.Errorf("%+v", errs)
err = errors.Wrap(err, "failed to get kubernetes client config")
return
}

// Config provides appropriate config getter instances that help in fetching
// kubernetes client config to invoke kubernetes API calls
func Config() ConfigGetter {
return ConfigGetters{&configFromENV{}, &configFromREST{}}
}
74 changes: 74 additions & 0 deletions pkg/client/k8s/v1alpha1/config_test.go
@@ -0,0 +1,74 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/openebs/zfs-localpv/pkg/common/env"
"os"
"testing"
)

// test if configFromENV implements ConfigGetter interface
var _ ConfigGetter = &configFromENV{}

// test if configFromREST implements ConfigGetter interface
var _ ConfigGetter = &configFromREST{}

// test if ConfigGetters implements ConfigGetter interface
var _ ConfigGetter = ConfigGetters{}

func TestConfigFromENV(t *testing.T) {
tests := map[string]struct {
masterip string
kubeconfig string
iserr bool
}{
"101": {"", "", true},
"102": {"", "/etc/config/kubeconfig", true},
"103": {"0.0.0.0", "", false},
"104": {"0.0.0.0", "/etc/config/config", true},
}

// Sub tests is not used here as env key is set & unset to test. Since env
// is a global setting, the tests should run serially
for name, mock := range tests {
masterip := os.Getenv(string(env.KubeMaster))
defer os.Setenv(string(env.KubeMaster), masterip)

kubeconfig := os.Getenv(string(env.KubeConfig))
defer os.Setenv(string(env.KubeConfig), kubeconfig)

err := os.Setenv(string(env.KubeMaster), mock.masterip)
if err != nil {
t.Fatalf("Test '%s' failed: %s", name, err)
}
err = os.Setenv(string(env.KubeConfig), mock.kubeconfig)
if err != nil {
t.Fatalf("Test '%s' failed: %s", name, err)
}

c := &configFromENV{}
config, err := c.Get()

if !mock.iserr && config == nil {
t.Fatalf("Test '%s' failed: expected config: actual nil config", name)
}
if !mock.iserr && err != nil {
t.Fatalf("Test '%s' failed: expected no error: actual %s", name, err)
}
}
}
52 changes: 52 additions & 0 deletions pkg/client/k8s/v1alpha1/configmap.go
@@ -0,0 +1,52 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
)

// ConfigMapGetter abstracts fetching of ConfigMap instance from kubernetes
// cluster
type ConfigMapGetter interface {
Get(options metav1.GetOptions) (*corev1.ConfigMap, error)
}

type configmap struct {
namespace string // namespace where this configmap exists
name string // name of this configmap
}

// ConfigMap returns a new instance of configmap
func ConfigMap(namespace, name string) *configmap {
return &configmap{namespace: namespace, name: name}
}

// Get returns configmap instance from kubernetes cluster
func (c *configmap) Get(options metav1.GetOptions) (cm *corev1.ConfigMap, err error) {
if len(strings.TrimSpace(c.name)) == 0 {
return nil, errors.Errorf("missing config map name: failed to get config map from namespace %s", c.namespace)
}
cs, err := Clientset().Get()
if err != nil {
return nil, errors.Wrapf(err, "failed to get config map %s %s", c.namespace, c.name)
}
return cs.CoreV1().ConfigMaps(c.namespace).Get(c.name, options)
}
47 changes: 47 additions & 0 deletions pkg/client/k8s/v1alpha1/configmap_test.go
@@ -0,0 +1,47 @@
/*
Copyright 2020 The OpenEBS Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v1alpha1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"testing"
)

// test if configmap implements ConfigMapGetter interface
var _ ConfigMapGetter = &configmap{}

func TestConfigMapGet(t *testing.T) {
tests := map[string]struct {
namespace string
name string
options metav1.GetOptions
iserr bool
}{
"101": {"", "", metav1.GetOptions{}, true},
"102": {"default", "", metav1.GetOptions{}, true},
"103": {"default", "myconf", metav1.GetOptions{}, true},
}

for name, mock := range tests {
t.Run(name, func(t *testing.T) {
_, err := ConfigMap(mock.namespace, mock.name).Get(mock.options)
if !mock.iserr && err != nil {
t.Fatalf("Test '%s' failed: expected no error: actual '%s'", name, err)
}
})
}
}

0 comments on commit d608dba

Please sign in to comment.