Skip to content

Commit

Permalink
Allow the idler to exclude some deployments from idling #fabric8-serv…
Browse files Browse the repository at this point in the history
  • Loading branch information
pradeepti123 committed Jan 29, 2019
1 parent 1c1c1e1 commit b2fccf5
Show file tree
Hide file tree
Showing 16 changed files with 206 additions and 136 deletions.
133 changes: 14 additions & 119 deletions Gopkg.lock

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions cmd/fabric8-jenkins-idler/idler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"github.com/fabric8-services/fabric8-jenkins-idler/internal/configuration"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/model"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/status"

"github.com/fabric8-services/fabric8-jenkins-idler/internal/openshift"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/toggles"
Expand Down Expand Up @@ -37,6 +38,7 @@ type Idler struct {
tenantService tenant.Service
clusterView cluster.View
config configuration.Configuration
statusService status.Service
}

// struct used to pass in cancelable task
Expand All @@ -47,12 +49,14 @@ type task struct {
}

// NewIdler creates a new instance of Idler. The configuration as well as feature toggle handler needs to be passed.
func NewIdler(features toggles.Features, tenantService tenant.Service, clusterView cluster.View, config configuration.Configuration) *Idler {
func NewIdler(features toggles.Features, tenantService tenant.Service, clusterView cluster.View,
config configuration.Configuration, status status.Service) *Idler {
return &Idler{
featureService: features,
tenantService: tenantService,
clusterView: clusterView,
config: config,
statusService: status,
}
}

Expand Down Expand Up @@ -82,7 +86,7 @@ func (idler *Idler) startWorkers(t *task, addProfiler bool) {
// Start API router
go func() {
// Create and start a Router instance to serve the REST API
idlerAPI := api.NewIdlerAPI(userIdlers, idler.clusterView, idler.tenantService)
idlerAPI := api.NewIdlerAPI(userIdlers, idler.clusterView, idler.tenantService, idler.statusService)
apirouter := router.CreateAPIRouter(idlerAPI)
router := router.NewRouter(apirouter)
router.AddMetrics(apirouter)
Expand Down Expand Up @@ -113,6 +117,7 @@ func (idler *Idler) watchOpenshiftEvents(t *task, userIdlers *openshift.UserIdle
idler.config,
t.wg,
t.cancel,
idler.statusService,
)

t.wg.Add(2)
Expand Down
4 changes: 3 additions & 1 deletion cmd/fabric8-jenkins-idler/idler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/fabric8-services/fabric8-jenkins-idler/internal/cluster"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/configuration"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/status"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/testutils/mock"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
Expand Down Expand Up @@ -44,7 +45,8 @@ func Test_graceful_shutdown(t *testing.T) {
hook := test.NewGlobal()

config, _ := configuration.New("")
idler := NewIdler(&mockFeatureToggle{}, &mock.TenantService{}, &mockClusterView{}, config)
status, _ := status.NewUserStatus()
idler := NewIdler(&mockFeatureToggle{}, &mock.TenantService{}, &mockClusterView{}, config, status)

go func() {
// Send SIGTERM after two seconds
Expand Down
7 changes: 6 additions & 1 deletion cmd/fabric8-jenkins-idler/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"os"

"context"

"github.com/fabric8-services/fabric8-jenkins-idler/internal/cluster"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/configuration"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/status"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/tenant"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/toggles"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/token"
Expand Down Expand Up @@ -55,7 +57,10 @@ func main() {
// Create Tenant Service
tenantService := tenant.NewTenantService(config.GetTenantURL(), osioToken)

idler := NewIdler(featuresService, tenantService, clusterView, config)
// Create Status service
statusService, _ := status.NewUserStatus()

idler := NewIdler(featuresService, tenantService, clusterView, config, statusService)
idler.Run()
}

Expand Down
24 changes: 23 additions & 1 deletion internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"github.com/fabric8-services/fabric8-jenkins-idler/internal/model"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/openshift"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/openshift/client"
ss "github.com/fabric8-services/fabric8-jenkins-idler/internal/status"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/tenant"

"github.com/fabric8-services/fabric8-jenkins-idler/metric"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -59,6 +61,9 @@ type IdlerAPI interface {

// Reset deletes a pod and starts a new one
Reset(w http.ResponseWriter, r *http.Request, ps httprouter.Params)

// SetUserStatus set users status for idler.
SetUserStatus(w http.ResponseWriter, r *http.Request, ps httprouter.Params)
}

type idler struct {
Expand All @@ -67,21 +72,27 @@ type idler struct {
openShiftClient client.OpenShiftClient
controller openshift.Controller
tenantService tenant.Service
statusService ss.Service
}

type status struct {
IsIdle bool `json:"is_idle"`
}

type userStatus struct {
UserStatusMap map[string]bool `json:"user_map"`
}

// NewIdlerAPI creates a new instance of IdlerAPI.
func NewIdlerAPI(userIdlers *openshift.UserIdlerMap, clusterView cluster.View, ts tenant.Service) IdlerAPI {
func NewIdlerAPI(userIdlers *openshift.UserIdlerMap, clusterView cluster.View, ts tenant.Service, ss ss.Service) IdlerAPI {
// Initialize metrics
Recorder.Initialize()
return &idler{
userIdlers: userIdlers,
clusterView: clusterView,
openShiftClient: client.NewOpenShift(),
tenantService: ts,
statusService: ss,
}
}

Expand Down Expand Up @@ -324,3 +335,14 @@ func writeResponse(w http.ResponseWriter, status int, response any) {
return
}
}

func (api *idler) SetUserStatus(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
decoder := json.NewDecoder(r.Body)
var t userStatus
err := decoder.Decode(&t)
if err != nil {
fmt.Println("Error while json decode")
}
api.statusService.AddUserStatus(t.UserStatusMap)
w.WriteHeader(http.StatusOK)
}
30 changes: 29 additions & 1 deletion internal/idler/user_idler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/fabric8-services/fabric8-jenkins-idler/internal/configuration"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/model"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/openshift/client"
userstatus "github.com/fabric8-services/fabric8-jenkins-idler/internal/status"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/tenant"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/toggles"
logrus "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -46,6 +47,7 @@ type UserIdler struct {
config configuration.Configuration
features toggles.Features
tenantService tenant.Service
statusService userstatus.Service
}

// NewUserIdler creates an instance of UserIdler.
Expand All @@ -55,7 +57,8 @@ func NewUserIdler(
openShiftAPI, openShiftBearerToken string,
config configuration.Configuration,
features toggles.Features,
tenantService tenant.Service) *UserIdler {
tenantService tenant.Service,
statusService userstatus.Service) *UserIdler {

logEntry := logger.WithFields(logrus.Fields{
"name": user.Name,
Expand All @@ -82,6 +85,7 @@ func NewUserIdler(
config: config,
features: features,
tenantService: tenantService,
statusService: statusService,
}
return &userIdler
}
Expand Down Expand Up @@ -110,6 +114,15 @@ func (idler *UserIdler) checkIdle() error {
idler.logger.Warnf("idler disabled for user %s - skipping", idler.user.Name)
return nil
}
uStatus, err := idler.checkUserStatus()
if err != nil {
idler.logger.Errorf("Failed to check status of user: %s", err)
}

if uStatus {
idler.logger.Warnf("Status disabled for user - %s", idler.user.Name)
return nil
}

idler.logger.Infof("Evaluating conditions for user %s", idler.user.Name)

Expand Down Expand Up @@ -351,3 +364,18 @@ func createWatchConditions(proxyURL string, idleAfter int, idleLongBuild int, lo

return &conditions
}

func (idler *UserIdler) checkUserStatus() (bool, error) {
disable, err := idler.statusService.CheckUserStatus(idler.user.Name)
if err != nil {
return false, err
}
log := idler.logger.WithFields(logrus.Fields{
"user": idler.user.Name,
})
if disable {
log.Debug("User disabled for Idler : ", idler.user.Name)
return disable, nil
}
return disable, nil
}
11 changes: 8 additions & 3 deletions internal/idler/user_idler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func Test_idle_check_skipped_if_feature_not_enabled(t *testing.T) {
user, "", "", &mock.Config{},
mock.NewMockFeatureToggle([]string{"42"}),
&mock.TenantService{},
&mock.UserStatus{},
)

err := userIdler.checkIdle()
Expand All @@ -59,6 +60,7 @@ func Test_idle_check_returns_error_on_evaluation_failure(t *testing.T) {
user, "", "", &mock.Config{},
mock.NewMockFeatureToggle([]string{"42"}),
&mock.TenantService{},
&mock.UserStatus{},
)
userIdler.Conditions.Add("error", &ErrorCondition{})

Expand All @@ -76,7 +78,8 @@ func Test_idle_check_occurs_even_without_openshift_events(t *testing.T) {
config := &mock.Config{}
features := mock.NewMockFeatureToggle([]string{"42"})
tenantService := &mock.TenantService{}
userIdler := NewUserIdler(user, "", "", config, features, tenantService)
statusService := &mock.UserStatus{}
userIdler := NewUserIdler(user, "", "", config, features, tenantService, statusService)

var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
Expand Down Expand Up @@ -130,7 +133,8 @@ func Test_number_of_idle_calls_are_capped(t *testing.T) {
config.MaxRetries = maxRetry
features := mock.NewMockFeatureToggle([]string{"42"})
tenantService := &mock.TenantService{}
userIdler := NewUserIdler(user, "", "", config, features, tenantService)
statusService := &mock.UserStatus{}
userIdler := NewUserIdler(user, "", "", config, features, tenantService, statusService)
userIdler.openShiftClient = openShiftClient

var wg sync.WaitGroup
Expand Down Expand Up @@ -178,8 +182,9 @@ func Test_number_of_unidle_calls_are_capped(t *testing.T) {

features := mock.NewMockFeatureToggle([]string{"42"})
tenantService := &mock.TenantService{}
statusService := &mock.UserStatus{}

userIdler := NewUserIdler(user, "", "", config, features, tenantService)
userIdler := NewUserIdler(user, "", "", config, features, tenantService, statusService)
userIdler.openShiftClient = openShiftClient
conditions := condition.NewConditions()
conditions.Add("unidle", &UnIdleCondition{})
Expand Down
9 changes: 7 additions & 2 deletions internal/openshift/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/fabric8-services/fabric8-jenkins-idler/internal/configuration"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/idler"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/model"
userstatus "github.com/fabric8-services/fabric8-jenkins-idler/internal/status"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/tenant"
"github.com/fabric8-services/fabric8-jenkins-idler/internal/toggles"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -44,6 +45,7 @@ type controllerImpl struct {
ctx context.Context
cancel context.CancelFunc
unknownUsers *UnknownUsersMap
statusService userstatus.Service
}

// NewController creates an instance of controllerImpl.
Expand All @@ -54,7 +56,8 @@ func NewController(
t tenant.Service,
features toggles.Features,
config configuration.Configuration,
wg *sync.WaitGroup, cancel context.CancelFunc) Controller {
wg *sync.WaitGroup, cancel context.CancelFunc,
statusService userstatus.Service) Controller {

logger.WithField("cluster", openshiftURL).Info("Creating new controller instance")

Expand All @@ -69,6 +72,7 @@ func NewController(
ctx: ctx,
cancel: cancel,
unknownUsers: NewUnknownUsersMap(),
statusService: statusService,
}

return &controller
Expand Down Expand Up @@ -251,7 +255,8 @@ func (c *controllerImpl) createIfNotExist(ns string) (bool, error) {

userIdler := idler.NewUserIdler(
user, c.openshiftURL, c.osBearerToken,
c.config, c.features, c.tenantService)
c.config, c.features, c.tenantService,
c.statusService)

c.userIdlers.Store(ns, userIdler)

Expand Down
3 changes: 2 additions & 1 deletion internal/openshift/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,14 @@ func setUp(t *testing.T) {
tenantService := tenant.NewTenantService(tenantService.URL, "")

features := &mockFeatureToggle{}
statusService := mock.NewMockUserStatus()

var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

userIdlers := NewUserIdlerMap()
controller = NewController(ctx, "", "", userIdlers, tenantService, features, &mock.Config{}, &wg, cancel)
controller = NewController(ctx, "", "", userIdlers, tenantService, features, &mock.Config{}, &wg, cancel, statusService)
}

func emptyChannel(ch chan model.User) {
Expand Down
3 changes: 3 additions & 0 deletions internal/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,8 @@ func CreateAPIRouter(api api.IdlerAPI) *httprouter.Router {
router.POST("/api/idler/reset/:namespace", api.Reset)
router.POST("/api/idler/reset/:namespace/", api.Reset)

router.POST("/api/idler/userstatus", api.SetUserStatus)
router.POST("/api/idler/userstatus/", api.SetUserStatus)

return router
}
22 changes: 17 additions & 5 deletions internal/router/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,26 @@ func Test_all_routes_are_setup(t *testing.T) {
{"/api/idler/isidle/my-namepace/", "IsIdle"},
{"/api/idler/cluster", "GetClusterDNSView"},
{"/api/idler/cluster/", "GetClusterDNSView"},
{"/api/idler/userstatus", "SetUserStatus"},
{"/api/idler/userstatus/", "SetUserStatus"},

{"/api/idler/foo", "404 page not found\n"},
{"/api/idler/builds/foo/bar", "404 page not found\n"},
}

for _, testRoute := range routes {
w := new(mock.ResponseWriter)
if testRoute.target == "SetUserStatus" {
req, _ := http.NewRequest("POST", testRoute.route, nil)
router.ServeHTTP(w, req)

req, _ := http.NewRequest("GET", testRoute.route, nil)
router.ServeHTTP(w, req)
assert.Equal(t, testRoute.target, w.GetBody(), fmt.Sprintf("Routing failed for %s", testRoute.route))
} else {
req, _ := http.NewRequest("GET", testRoute.route, nil)
router.ServeHTTP(w, req)

assert.Equal(t, testRoute.target, w.GetBody(), fmt.Sprintf("Routing failed for %s", testRoute.route))
assert.Equal(t, testRoute.target, w.GetBody(), fmt.Sprintf("Routing failed for %s", testRoute.route))
}
}
}

Expand Down Expand Up @@ -111,7 +119,9 @@ func Test_cluster_dns_view(t *testing.T) {
tenantService, cleanup := stubTenantService()
defer cleanup()

idlerAPI := api.NewIdlerAPI(openshift.NewUserIdlerMap(), clusterView, tenantService)
statusService := &mock.UserStatus{}

idlerAPI := api.NewIdlerAPI(openshift.NewUserIdlerMap(), clusterView, tenantService, statusService)
router := NewRouterWithPort(CreateAPIRouter(idlerAPI), testPort)

var wg sync.WaitGroup
Expand Down Expand Up @@ -160,7 +170,9 @@ func Test_openshift_url_parameter_is_used(t *testing.T) {
}

clusterView := cluster.NewView([]cluster.Cluster{dummyCluster})
idlerAPI := api.NewIdlerAPI(openshift.NewUserIdlerMap(), clusterView, tenantService)
statusService := &mock.UserStatus{}

idlerAPI := api.NewIdlerAPI(openshift.NewUserIdlerMap(), clusterView, tenantService, statusService)
router := NewRouterWithPort(CreateAPIRouter(idlerAPI), testPort)

// start the router
Expand Down
7 changes: 7 additions & 0 deletions internal/status/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package status

//Service is an interface responsible to add/update user status to idler.
type Service interface {
CheckUserStatus(user string) (bool, error)
AddUserStatus(user map[string]bool)
}
Loading

0 comments on commit b2fccf5

Please sign in to comment.