Skip to content

Commit

Permalink
feat(usage) Add configurable, regular ping event (#862)
Browse files Browse the repository at this point in the history
Detail:

- If the user consents to share anonymous data with us, the m-apiserver
  besides the regular ones will also send a `ping`.
  The `ping` will be a time based regular event, denoting that openebs version
  `X` is running in a version `Y` k8s cluster with `Z` nodes.
- The duration can be configured with some restrictions by setting
  `OPENEBS_IO_ANALYTICS_PING_INTERVAL` in m-apiserver pod.

Other minor refactor:

- force update of versions is made possible
- enhancement(constants) cs are defined in pkg/usage/const.go
- (enhancement)To better manage ENV variables a `GetOrDefault` func has been added
- parsing of ping interval is done by time.ParseDuration
- (bugfix)helper funcs to set replica-count to current m-apiserver default i.e. `3`

improvement: openebs/openebs#2257

Signed-off-by: Harsh Vardhan <[email protected]>
  • Loading branch information
Harsh Vardhan authored and kmova committed Dec 6, 2018
1 parent 046b9db commit 9124db1
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 22 deletions.
3 changes: 2 additions & 1 deletion cmd/maya-apiserver/app/command/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ func Run(cmd *cobra.Command, c *CmdStartOptions) error {
}()

if env.Truthy(env.OpenEBSEnableAnalytics) {
usage.New().Build().InstallBuilder().Send()
usage.New().Build().InstallBuilder(true).Send()
go usage.PingCheck()
}

// Wait for exit
Expand Down
9 changes: 5 additions & 4 deletions cmd/maya-apiserver/app/server/volume_endpoint_v1alpha1.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ type volumeAPIOpsV1alpha1 struct {
resp http.ResponseWriter
}

// volumeEvents sends anonymous volume (de)-provision events
func volumeEvents(cvol *v1alpha1.CASVolume, method string) {
if menv.Truthy(menv.OpenEBSEnableAnalytics) && cvol != nil {
usage.New().Build().ApplicationBuilder().
SetApplicationName(cvol.Spec.CasType).
SetDocumentTitle(cvol.ObjectMeta.Name).
SetLabel("capacity").
SetAction("replica:" + cvol.Spec.Replicas).
SetLabel(usage.EventLabelCapacity).
SetReplicaCount(cvol.Spec.Replicas, method).
SetCategory(method).
SetVolumeCapacity(cvol.Spec.Capacity).Send()
}
Expand Down Expand Up @@ -122,7 +123,7 @@ func (v *volumeAPIOpsV1alpha1) create() (*v1alpha1.CASVolume, error) {
return nil, CodedError(400, err.Error())
}
cvol, err := vOps.Create()
volumeEvents(vol, "volume-provision")
volumeEvents(vol, usage.VolumeProvision)
if err != nil {
glog.Errorf("failed to create cas template based volume: error '%s'", err.Error())
return nil, CodedError(500, err.Error())
Expand Down Expand Up @@ -213,7 +214,7 @@ func (v *volumeAPIOpsV1alpha1) delete(volumeName string) (*v1alpha1.CASVolume, e
}

cvol, err := vOps.Delete()
volumeEvents(vol, "volume-deprovision")
volumeEvents(vol, usage.VolumeDeprovision)
if err != nil {
glog.Errorf("failed to delete cas template based volume: error '%s'", err.Error())
if isNotFound(err) {
Expand Down
14 changes: 14 additions & 0 deletions pkg/env/v1alpha1/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,20 @@ func Get(envKey ENVKey) (value string) {
return getEnv(string(envKey))
}

// GetOrDefault fetches value from the provided environment variable
// which on empty returns the defaultValue
// NOTE: os.Getenv is used here instead of os.LookupEnv because it is
// not required to know if the environment variable is defined on the system
func GetOrDefault(e ENVKey, defaultValue string) (value string) {
envValue := Get(e)
if len(envValue) == 0 {
// ENV not defined or set to ""
return defaultValue
} else {
return envValue
}
}

// Lookup looks up an environment variable
//
// NOTE:
Expand Down
43 changes: 43 additions & 0 deletions pkg/usage/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
Copyright 2018 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 usage

const (
// GAclientID is the unique code of OpenEBS project in Google Analytics
GAclientID string = "UA-127388617-1"

// supported events categories

// Install event is sent on pod starts
InstallEvent string = "install"
// Ping event is sent periodically
Ping string = "ping"
// VolumeProvision event is sent when a volume is created
VolumeProvision string = "volume-provision"
//VolumeDeprovision event is sent when a volume is deleted
VolumeDeprovision string = "volume-deprovision"

AppName string = "OpenEBS"

// Event labels
RunningStatus string = "running"
EventLabelNode string = "nodes"
EventLabelCapacity string = "capacity"

// Event action
Replica string = "replica:"
DefaultReplicaCount string = "replica:3"
)
63 changes: 63 additions & 0 deletions pkg/usage/ping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2018 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 usage

import (
"time"

menv "github.com/openebs/maya/pkg/env/v1alpha1"
)

var OpenEBSPingPeriod menv.ENVKey = "OPENEBS_IO_ANALYTICS_PING_INTERVAL"

const (
// defaultPingPeriod sets the default ping heartbeat interval
defaultPingPeriod time.Duration = 24 * time.Hour
// minimumPingPeriod sets the minimum possible configurable
// heartbeat period, if a value lower than this will be set, the
// defaultPingPeriod will be used
minimumPingPeriod time.Duration = 1 * time.Hour
)

// PingCheck sends ping events to Google Analytics
func PingCheck() {
// Create a new usage field
u := New()
duration := getPingPeriod()
ticker := time.NewTicker(duration)
for _ = range ticker.C {
u.Build().
InstallBuilder(true).
SetCategory(Ping).
Send()
}
}

// getPingPeriod sets the duration of health events, defaults to 24
func getPingPeriod() time.Duration {
value := menv.GetOrDefault(OpenEBSPingPeriod, string(defaultPingPeriod))
duration, _ := time.ParseDuration(value)
// Sanitychecks for setting time duration of health events
// This way, we are checking for negative and zero time duration and we
// also have a minimum possible configurable time duration between health events
if duration < minimumPingPeriod {
// Avoid corner case when the ENV value is undesirable
return time.Duration(defaultPingPeriod)
} else {
return time.Duration(duration)
}
}
42 changes: 42 additions & 0 deletions pkg/usage/ping_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package usage

import (
"os"
"testing"
"time"
)

func TestGetPingPeriod(t *testing.T) {
beforeFunc := func(value string) {
if err := os.Setenv(string(OpenEBSPingPeriod), value); err != nil {
t.Logf("Unable to set environment variable")
}
}
afterFunc := func() {
if err := os.Unsetenv(string(OpenEBSPingPeriod)); err != nil {
t.Logf("Unable to unset environment variable")
}
}
testSuite := map[string]struct {
OpenEBSPingPeriodValue string
ExpectedPeriodValue time.Duration
}{
"24 seconds": {"24s", 86400000000000},
"24 minutes": {"24m", 86400000000000},
"24 hours": {"24h", 86400000000000},
"Negative 24 hours": {"-24h", 86400000000000},
"Random string input": {"Apache", 86400000000000},
"Two hours": {"2h", 7200000000000},
"Three hundred hours": {"300h", 1080000000000000},
"Fifty two seconds": {"52000000000ns", 86400000000000},
"Empty env value": {"", 86400000000000},
}
for testKey, testData := range testSuite {
beforeFunc(testData.OpenEBSPingPeriodValue)
evaluatedValue := getPingPeriod()
if evaluatedValue != testData.ExpectedPeriodValue {
t.Fatalf("Tests failed for %s, expected=%d, got=%d", testKey, testData.ExpectedPeriodValue, evaluatedValue)
}
afterFunc()
}
}
1 change: 1 addition & 0 deletions pkg/usage/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package usage
import units "github.com/docker/go-units"

// toGigaUnits converts a size from xB to bytes where x={k,m,g,t,p...}
// and return the number of Gigabytes as an integer
// 1 gigabyte=1000 megabyte
func toGigaUnits(size string) (int64, error) {
sizeInBytes, err := units.FromHumanSize(size)
Expand Down
35 changes: 23 additions & 12 deletions pkg/usage/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ import (
k8sapi "github.com/openebs/maya/pkg/client/k8s/v1alpha1"
)

const (
// GAclientID is the unique code of OpenEBS project in Google Analytics
GAclientID = "UA-127388617-1"
)

// Usage struct represents all information about a usage metric sent to
// Google Analytics with respect to the application
type Usage struct {
Expand Down Expand Up @@ -178,8 +173,8 @@ func (u *Usage) SetValue(v int64) *Usage {
func (u *Usage) Build() *Usage {
// Default ApplicationID for openebs project is OpenEBS
v := NewVersion()
v.getVersion()
u.SetApplicationID("OpenEBS").
v.getVersion(false)
u.SetApplicationID(AppName).
SetTrackingID(GAclientID).
SetClientID(v.id)
// TODO: Add condition for version over-ride
Expand All @@ -191,7 +186,7 @@ func (u *Usage) Build() *Usage {
// for non install events
func (u *Usage) ApplicationBuilder() *Usage {
v := NewVersion()
v.getVersion()
v.getVersion(false)
u.SetApplicationVersion(v.openebsVersion).
SetApplicationName(v.k8sArch).
SetApplicationInstallerID(v.k8sVersion).
Expand All @@ -206,17 +201,33 @@ func (u *Usage) SetVolumeCapacity(volCapG string) *Usage {
return u
}

// Wrapper for setting replica count for volume events
// NOTE: This doesn't get the replica count in a volume de-provision event.
// TODO: Pick the current value of replica-count from the CAS-engine
func (u *Usage) SetReplicaCount(count, method string) *Usage {
if method == VolumeProvision && count == "" {
// Case: When volume-provision the replica count isn't specified
// it is set to three by default by the m-apiserver
u.SetAction(DefaultReplicaCount)
} else {
// Catch all case for volume-deprovision event and
// volume-provision event with an overriden replica-count
u.SetAction(Replica + count)
}
return u
}

// InstallBuilder is a concrete builder for install events
func (u *Usage) InstallBuilder() *Usage {
func (u *Usage) InstallBuilder(override bool) *Usage {
v := NewVersion()
clusterSize, _ := k8sapi.NumberOfNodes()
v.getVersion()
v.getVersion(override)
u.SetApplicationVersion(v.openebsVersion).
SetApplicationName(v.k8sArch).
SetApplicationInstallerID(v.k8sVersion).
SetDataSource(v.nodeType).
SetDocumentTitle(v.id).
SetApplicationID("OpenEBS").
NewEvent("install", "running", "nodes", int64(clusterSize))
SetApplicationID(AppName).
NewEvent(InstallEvent, RunningStatus, EventLabelNode, int64(clusterSize))
return u
}
10 changes: 5 additions & 5 deletions pkg/usage/versionset.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,16 @@ func (v *versionSet) fetchAndSetVersion() error {
}

// getVersion is a wrapper over fetchAndSetVersion
func (v *versionSet) getVersion() error {
// If ENVs aren't set, fetch the required values from the
// K8s APIserver
if _, present := env.Lookup(openEBSversion); !present {
func (v *versionSet) getVersion(override bool) error {
// If ENVs aren't set or the override is true, fetch the required
// values from the K8s APIserver
if _, present := env.Lookup(openEBSversion); !present || override {
if err := v.fetchAndSetVersion(); err != nil {
glog.Error(err.Error())
return err
}
}
// Fetch data from ENV instead
// Fetch data from ENV
v.id = env.Get(clusterUUID)
v.k8sArch = env.Get(clusterArch)
v.k8sVersion = env.Get(clusterVersion)
Expand Down

0 comments on commit 9124db1

Please sign in to comment.