Browse code

Merge pull request #2276 from derekwaynecarr/prune_deployments

Merged by openshift-bot

OpenShift Bot authored on 2015/05/19 10:16:10
Showing 8 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,155 @@
0
+package prune
1
+
2
+import (
3
+	"fmt"
4
+	"time"
5
+
6
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
+
10
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
11
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
12
+)
13
+
14
+// DeploymentByDeploymentConfigIndexFunc indexes Deployment items by their associated DeploymentConfig, if none, index with key "orphan"
15
+func DeploymentByDeploymentConfigIndexFunc(obj interface{}) (string, error) {
16
+	controller, ok := obj.(*kapi.ReplicationController)
17
+	if !ok {
18
+		return "", fmt.Errorf("not a replication controller: %v", obj)
19
+	}
20
+	name := deployutil.DeploymentConfigNameFor(controller)
21
+	if len(name) == 0 {
22
+		return "orphan", nil
23
+	}
24
+	return controller.Namespace + "/" + name, nil
25
+}
26
+
27
+// Filter filters the set of objects
28
+type Filter interface {
29
+	Filter(items []*kapi.ReplicationController) []*kapi.ReplicationController
30
+}
31
+
32
+// andFilter ands a set of predicate functions to know if it should be included in the return set
33
+type andFilter struct {
34
+	filterPredicates []FilterPredicate
35
+}
36
+
37
+// Filter ands the set of predicates evaluated against each item to make a filtered set
38
+func (a *andFilter) Filter(items []*kapi.ReplicationController) []*kapi.ReplicationController {
39
+	results := []*kapi.ReplicationController{}
40
+	for _, item := range items {
41
+		include := true
42
+		for _, filterPredicate := range a.filterPredicates {
43
+			include = include && filterPredicate(item)
44
+		}
45
+		if include {
46
+			results = append(results, item)
47
+		}
48
+	}
49
+	return results
50
+}
51
+
52
+// FilterPredicate is a function that returns true if the object should be included in the filtered set
53
+type FilterPredicate func(item *kapi.ReplicationController) bool
54
+
55
+// NewFilterBeforePredicate is a function that returns true if the build was created before the current time minus specified duration
56
+func NewFilterBeforePredicate(d time.Duration) FilterPredicate {
57
+	now := util.Now()
58
+	before := util.NewTime(now.Time.Add(-1 * d))
59
+	return func(item *kapi.ReplicationController) bool {
60
+		return item.CreationTimestamp.Before(before)
61
+	}
62
+}
63
+
64
+// FilterDeploymentsPredicate is a function that returns true if the replication controller is associated with a DeploymentConfig
65
+func FilterDeploymentsPredicate(item *kapi.ReplicationController) bool {
66
+	return len(deployutil.DeploymentConfigNameFor(item)) > 0
67
+}
68
+
69
+// FilterZeroReplicaSize is a function that returns true if the replication controller size is 0
70
+func FilterZeroReplicaSize(item *kapi.ReplicationController) bool {
71
+	return item.Spec.Replicas == 0 && item.Status.Replicas == 0
72
+}
73
+
74
+// DataSet provides functions for working with deployment data
75
+type DataSet interface {
76
+	GetDeploymentConfig(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, bool, error)
77
+	ListDeploymentConfigs() ([]*deployapi.DeploymentConfig, error)
78
+	ListDeployments() ([]*kapi.ReplicationController, error)
79
+	ListDeploymentsByDeploymentConfig(config *deployapi.DeploymentConfig) ([]*kapi.ReplicationController, error)
80
+}
81
+
82
+type dataSet struct {
83
+	deploymentConfigStore cache.Store
84
+	deploymentIndexer     cache.Indexer
85
+}
86
+
87
+// NewDataSet returns a DataSet over the specified items
88
+func NewDataSet(deploymentConfigs []*deployapi.DeploymentConfig, deployments []*kapi.ReplicationController) DataSet {
89
+	deploymentConfigStore := cache.NewStore(cache.MetaNamespaceKeyFunc)
90
+	for _, deploymentConfig := range deploymentConfigs {
91
+		deploymentConfigStore.Add(deploymentConfig)
92
+	}
93
+
94
+	deploymentIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{
95
+		"deploymentConfig": DeploymentByDeploymentConfigIndexFunc,
96
+	})
97
+	for _, deployment := range deployments {
98
+		deploymentIndexer.Add(deployment)
99
+	}
100
+
101
+	return &dataSet{
102
+		deploymentConfigStore: deploymentConfigStore,
103
+		deploymentIndexer:     deploymentIndexer,
104
+	}
105
+}
106
+
107
+func (d *dataSet) GetDeploymentConfig(controller *kapi.ReplicationController) (*deployapi.DeploymentConfig, bool, error) {
108
+	name := deployutil.DeploymentConfigNameFor(controller)
109
+	if len(name) == 0 {
110
+		return nil, false, nil
111
+	}
112
+
113
+	var deploymentConfig *deployapi.DeploymentConfig
114
+	key := &deployapi.DeploymentConfig{ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: controller.Namespace}}
115
+	item, exists, err := d.deploymentConfigStore.Get(key)
116
+	if exists {
117
+		deploymentConfig = item.(*deployapi.DeploymentConfig)
118
+	}
119
+	return deploymentConfig, exists, err
120
+}
121
+
122
+func (d *dataSet) ListDeploymentConfigs() ([]*deployapi.DeploymentConfig, error) {
123
+	results := []*deployapi.DeploymentConfig{}
124
+	for _, item := range d.deploymentConfigStore.List() {
125
+		results = append(results, item.(*deployapi.DeploymentConfig))
126
+	}
127
+	return results, nil
128
+}
129
+
130
+func (d *dataSet) ListDeployments() ([]*kapi.ReplicationController, error) {
131
+	results := []*kapi.ReplicationController{}
132
+	for _, item := range d.deploymentIndexer.List() {
133
+		results = append(results, item.(*kapi.ReplicationController))
134
+	}
135
+	return results, nil
136
+}
137
+
138
+func (d *dataSet) ListDeploymentsByDeploymentConfig(deploymentConfig *deployapi.DeploymentConfig) ([]*kapi.ReplicationController, error) {
139
+	results := []*kapi.ReplicationController{}
140
+	key := &kapi.ReplicationController{
141
+		ObjectMeta: kapi.ObjectMeta{
142
+			Namespace:   deploymentConfig.Namespace,
143
+			Annotations: map[string]string{deployapi.DeploymentConfigAnnotation: deploymentConfig.Name},
144
+		},
145
+	}
146
+	items, err := d.deploymentIndexer.Index("deploymentConfig", key)
147
+	if err != nil {
148
+		return nil, err
149
+	}
150
+	for _, item := range items {
151
+		results = append(results, item.(*kapi.ReplicationController))
152
+	}
153
+	return results, nil
154
+}
0 155
new file mode 100644
... ...
@@ -0,0 +1,164 @@
0
+package prune
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+
6
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
7
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
8
+
9
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
+)
11
+
12
+func mockDeploymentConfig(namespace, name string) *deployapi.DeploymentConfig {
13
+	return &deployapi.DeploymentConfig{ObjectMeta: kapi.ObjectMeta{Namespace: namespace, Name: name}}
14
+}
15
+
16
+func withSize(item *kapi.ReplicationController, replicas int) *kapi.ReplicationController {
17
+	item.Spec.Replicas = replicas
18
+	item.Status.Replicas = replicas
19
+	return item
20
+}
21
+
22
+func withCreated(item *kapi.ReplicationController, creationTimestamp util.Time) *kapi.ReplicationController {
23
+	item.CreationTimestamp = creationTimestamp
24
+	return item
25
+}
26
+
27
+func withStatus(item *kapi.ReplicationController, status deployapi.DeploymentStatus) *kapi.ReplicationController {
28
+	item.Annotations[deployapi.DeploymentStatusAnnotation] = string(status)
29
+	return item
30
+}
31
+
32
+func mockDeployment(namespace, name string, deploymentConfig *deployapi.DeploymentConfig) *kapi.ReplicationController {
33
+	item := &kapi.ReplicationController{ObjectMeta: kapi.ObjectMeta{Namespace: namespace, Name: name, Annotations: map[string]string{}}}
34
+	if deploymentConfig != nil {
35
+		item.Annotations[deployapi.DeploymentConfigAnnotation] = deploymentConfig.Name
36
+	}
37
+	item.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew)
38
+	return item
39
+}
40
+
41
+func TestDeploymentByDeploymentConfigIndexFunc(t *testing.T) {
42
+	config := mockDeploymentConfig("a", "b")
43
+	deployment := mockDeployment("a", "c", config)
44
+	actualKey, err := DeploymentByDeploymentConfigIndexFunc(deployment)
45
+	if err != nil {
46
+		t.Errorf("Unexpected error %v", err)
47
+	}
48
+	expectedKey := "a/b"
49
+	if actualKey != expectedKey {
50
+		t.Errorf("expected %v, actual %v", expectedKey, actualKey)
51
+	}
52
+	deploymentWithNoConfig := &kapi.ReplicationController{}
53
+	actualKey, err = DeploymentByDeploymentConfigIndexFunc(deploymentWithNoConfig)
54
+	if err != nil {
55
+		t.Errorf("Unexpected error %v", err)
56
+	}
57
+	expectedKey = "orphan"
58
+	if actualKey != expectedKey {
59
+		t.Errorf("expected %v, actual %v", expectedKey, actualKey)
60
+	}
61
+}
62
+
63
+func TestFilterBeforePredicate(t *testing.T) {
64
+	youngerThan := time.Hour
65
+	now := util.Now()
66
+	old := util.NewTime(now.Time.Add(-1 * youngerThan))
67
+	items := []*kapi.ReplicationController{}
68
+	items = append(items, withCreated(mockDeployment("a", "old", nil), old))
69
+	items = append(items, withCreated(mockDeployment("a", "new", nil), now))
70
+	filter := &andFilter{
71
+		filterPredicates: []FilterPredicate{NewFilterBeforePredicate(youngerThan)},
72
+	}
73
+	result := filter.Filter(items)
74
+	if len(result) != 1 {
75
+		t.Errorf("Unexpected number of results")
76
+	}
77
+	if expected, actual := "old", result[0].Name; expected != actual {
78
+		t.Errorf("expected %v, actual %v", expected, actual)
79
+	}
80
+}
81
+
82
+func TestEmptyDataSet(t *testing.T) {
83
+	deployments := []*kapi.ReplicationController{}
84
+	deploymentConfigs := []*deployapi.DeploymentConfig{}
85
+	dataSet := NewDataSet(deploymentConfigs, deployments)
86
+	_, exists, err := dataSet.GetDeploymentConfig(&kapi.ReplicationController{})
87
+	if exists || err != nil {
88
+		t.Errorf("Unexpected result %v, %v", exists, err)
89
+	}
90
+	deploymentConfigResults, err := dataSet.ListDeploymentConfigs()
91
+	if err != nil {
92
+		t.Errorf("Unexpected result %v", err)
93
+	}
94
+	if len(deploymentConfigResults) != 0 {
95
+		t.Errorf("Unexpected result %v", deploymentConfigResults)
96
+	}
97
+	deploymentResults, err := dataSet.ListDeployments()
98
+	if err != nil {
99
+		t.Errorf("Unexpected result %v", err)
100
+	}
101
+	if len(deploymentResults) != 0 {
102
+		t.Errorf("Unexpected result %v", deploymentResults)
103
+	}
104
+	deploymentResults, err = dataSet.ListDeploymentsByDeploymentConfig(&deployapi.DeploymentConfig{})
105
+	if err != nil {
106
+		t.Errorf("Unexpected result %v", err)
107
+	}
108
+	if len(deploymentResults) != 0 {
109
+		t.Errorf("Unexpected result %v", deploymentResults)
110
+	}
111
+}
112
+
113
+func TestPopulatedDataSet(t *testing.T) {
114
+	deploymentConfigs := []*deployapi.DeploymentConfig{
115
+		mockDeploymentConfig("a", "deployment-config-1"),
116
+		mockDeploymentConfig("b", "deployment-config-2"),
117
+	}
118
+	deployments := []*kapi.ReplicationController{
119
+		mockDeployment("a", "deployment-1", deploymentConfigs[0]),
120
+		mockDeployment("a", "deployment-2", deploymentConfigs[0]),
121
+		mockDeployment("b", "deployment-3", deploymentConfigs[1]),
122
+		mockDeployment("c", "deployment-4", nil),
123
+	}
124
+	dataSet := NewDataSet(deploymentConfigs, deployments)
125
+	for _, deployment := range deployments {
126
+		deploymentConfig, exists, err := dataSet.GetDeploymentConfig(deployment)
127
+		config, hasConfig := deployment.Annotations[deployapi.DeploymentConfigAnnotation]
128
+		if hasConfig {
129
+			if err != nil {
130
+				t.Errorf("Item %v, unexpected error: %v", deployment, err)
131
+			}
132
+			if !exists {
133
+				t.Errorf("Item %v, unexpected result: %v", deployment, exists)
134
+			}
135
+			if expected, actual := config, deploymentConfig.Name; expected != actual {
136
+				t.Errorf("expected %v, actual %v", expected, actual)
137
+			}
138
+			if expected, actual := deployment.Namespace, deploymentConfig.Namespace; expected != actual {
139
+				t.Errorf("expected %v, actual %v", expected, actual)
140
+			}
141
+		} else {
142
+			if err != nil {
143
+				t.Errorf("Item %v, unexpected error: %v", deployment, err)
144
+			}
145
+			if exists {
146
+				t.Errorf("Item %v, unexpected result: %v", deployment, exists)
147
+			}
148
+		}
149
+	}
150
+	expectedNames := util.NewStringSet("deployment-1", "deployment-2")
151
+	deploymentResults, err := dataSet.ListDeploymentsByDeploymentConfig(deploymentConfigs[0])
152
+	if err != nil {
153
+		t.Errorf("Unexpected result %v", err)
154
+	}
155
+	if len(deploymentResults) != len(expectedNames) {
156
+		t.Errorf("Unexpected result %v", deploymentResults)
157
+	}
158
+	for _, deployment := range deploymentResults {
159
+		if !expectedNames.Has(deployment.Name) {
160
+			t.Errorf("Unexpected name: %v", deployment.Name)
161
+		}
162
+	}
163
+}
0 164
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+package prune
1
+
2
+import (
3
+	"time"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+
7
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
8
+)
9
+
10
+// PruneFunc is a function that is invoked for each item during Prune
11
+type PruneFunc func(item *kapi.ReplicationController) error
12
+
13
+// PruneTask is an object that knows how to execute a single iteration of a Prune
14
+type PruneTasker interface {
15
+	PruneTask() error
16
+}
17
+
18
+// pruneTask is an object that knows how to prune a data set
19
+type pruneTask struct {
20
+	resolver Resolver
21
+	handler  PruneFunc
22
+}
23
+
24
+// NewPruneTasker returns a PruneTasker over specified data using specified flags
25
+// keepYoungerThan will filter out all objects from prune data set that are younger than the specified time duration
26
+// orphans if true will include inactive orphan deployments in candidate prune set
27
+// keepComplete is per DeploymentConfig how many of the most recent deployments should be preserved
28
+// keepFailed is per DeploymentConfig how many of the most recent failed deployments should be preserved
29
+func NewPruneTasker(deploymentConfigs []*deployapi.DeploymentConfig, deployments []*kapi.ReplicationController, keepYoungerThan time.Duration, orphans bool, keepComplete int, keepFailed int, handler PruneFunc) PruneTasker {
30
+	filter := &andFilter{
31
+		filterPredicates: []FilterPredicate{
32
+			FilterDeploymentsPredicate,
33
+			FilterZeroReplicaSize,
34
+			NewFilterBeforePredicate(keepYoungerThan),
35
+		},
36
+	}
37
+	deployments = filter.Filter(deployments)
38
+	dataSet := NewDataSet(deploymentConfigs, deployments)
39
+
40
+	resolvers := []Resolver{}
41
+	if orphans {
42
+		inactiveDeploymentStatus := []deployapi.DeploymentStatus{
43
+			deployapi.DeploymentStatusComplete,
44
+			deployapi.DeploymentStatusFailed,
45
+		}
46
+		resolvers = append(resolvers, NewOrphanDeploymentResolver(dataSet, inactiveDeploymentStatus))
47
+	}
48
+	resolvers = append(resolvers, NewPerDeploymentConfigResolver(dataSet, keepComplete, keepFailed))
49
+	return &pruneTask{
50
+		resolver: &mergeResolver{resolvers: resolvers},
51
+		handler:  handler,
52
+	}
53
+}
54
+
55
+// PruneTask will visit each item in the prunable set and invoke the associated handler
56
+func (t *pruneTask) PruneTask() error {
57
+	deployments, err := t.resolver.Resolve()
58
+	if err != nil {
59
+		return err
60
+	}
61
+	for _, deployment := range deployments {
62
+		err = t.handler(deployment)
63
+		if err != nil {
64
+			return err
65
+		}
66
+	}
67
+	return nil
68
+}
0 69
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+package prune
1
+
2
+import (
3
+	"sort"
4
+	"testing"
5
+	"time"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
+
10
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
11
+)
12
+
13
+type mockPruneRecorder struct {
14
+	set util.StringSet
15
+	err error
16
+}
17
+
18
+func (m *mockPruneRecorder) Handler(deployment *kapi.ReplicationController) error {
19
+	m.set.Insert(deployment.Name)
20
+	return m.err
21
+}
22
+
23
+func (m *mockPruneRecorder) Verify(t *testing.T, expected util.StringSet) {
24
+	if len(m.set) != len(expected) || !m.set.HasAll(expected.List()...) {
25
+		expectedValues := expected.List()
26
+		actualValues := m.set.List()
27
+		sort.Strings(expectedValues)
28
+		sort.Strings(actualValues)
29
+		t.Errorf("expected \n\t%v\n, actual \n\t%v\n", expectedValues, actualValues)
30
+	}
31
+}
32
+
33
+func TestPruneTask(t *testing.T) {
34
+	deploymentStatusOptions := []deployapi.DeploymentStatus{
35
+		deployapi.DeploymentStatusComplete,
36
+		deployapi.DeploymentStatusFailed,
37
+		deployapi.DeploymentStatusNew,
38
+		deployapi.DeploymentStatusPending,
39
+		deployapi.DeploymentStatusRunning,
40
+	}
41
+	deploymentStatusFilter := []deployapi.DeploymentStatus{
42
+		deployapi.DeploymentStatusComplete,
43
+		deployapi.DeploymentStatusFailed,
44
+	}
45
+	deploymentStatusFilterSet := util.StringSet{}
46
+	for _, deploymentStatus := range deploymentStatusFilter {
47
+		deploymentStatusFilterSet.Insert(string(deploymentStatus))
48
+	}
49
+
50
+	for _, orphans := range []bool{true, false} {
51
+		for _, deploymentStatusOption := range deploymentStatusOptions {
52
+			keepYoungerThan := time.Hour
53
+
54
+			now := util.Now()
55
+			old := util.NewTime(now.Time.Add(-1 * keepYoungerThan))
56
+
57
+			deploymentConfigs := []*deployapi.DeploymentConfig{}
58
+			deployments := []*kapi.ReplicationController{}
59
+
60
+			deploymentConfig := mockDeploymentConfig("a", "deployment-config")
61
+			deploymentConfigs = append(deploymentConfigs, deploymentConfig)
62
+
63
+			deployments = append(deployments, withCreated(withStatus(mockDeployment("a", "build-1", deploymentConfig), deploymentStatusOption), now))
64
+			deployments = append(deployments, withCreated(withStatus(mockDeployment("a", "build-2", deploymentConfig), deploymentStatusOption), old))
65
+			deployments = append(deployments, withSize(withCreated(withStatus(mockDeployment("a", "build-3-with-replicas", deploymentConfig), deploymentStatusOption), old), 4))
66
+			deployments = append(deployments, withCreated(withStatus(mockDeployment("a", "orphan-build-1", nil), deploymentStatusOption), now))
67
+			deployments = append(deployments, withCreated(withStatus(mockDeployment("a", "orphan-build-2", nil), deploymentStatusOption), old))
68
+			deployments = append(deployments, withSize(withCreated(withStatus(mockDeployment("a", "orphan-build-3-with-replicas", nil), deploymentStatusOption), old), 4))
69
+
70
+			keepComplete := 1
71
+			keepFailed := 1
72
+			expectedValues := util.StringSet{}
73
+			filter := &andFilter{
74
+				filterPredicates: []FilterPredicate{
75
+					FilterDeploymentsPredicate,
76
+					FilterZeroReplicaSize,
77
+					NewFilterBeforePredicate(keepYoungerThan),
78
+				},
79
+			}
80
+			dataSet := NewDataSet(deploymentConfigs, filter.Filter(deployments))
81
+			resolver := NewPerDeploymentConfigResolver(dataSet, keepComplete, keepFailed)
82
+			if orphans {
83
+				resolver = &mergeResolver{
84
+					resolvers: []Resolver{resolver, NewOrphanDeploymentResolver(dataSet, deploymentStatusFilter)},
85
+				}
86
+			}
87
+			expectedDeployments, err := resolver.Resolve()
88
+			for _, item := range expectedDeployments {
89
+				expectedValues.Insert(item.Name)
90
+			}
91
+
92
+			recorder := &mockPruneRecorder{set: util.StringSet{}}
93
+			task := NewPruneTasker(deploymentConfigs, deployments, keepYoungerThan, orphans, keepComplete, keepFailed, recorder.Handler)
94
+			err = task.PruneTask()
95
+			if err != nil {
96
+				t.Errorf("Unexpected error %v", err)
97
+			}
98
+			recorder.Verify(t, expectedValues)
99
+		}
100
+	}
101
+
102
+}
0 103
new file mode 100644
... ...
@@ -0,0 +1,125 @@
0
+package prune
1
+
2
+import (
3
+	"sort"
4
+
5
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
6
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
7
+
8
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
9
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
10
+)
11
+
12
+// Resolver knows how to resolve the set of candidate objects to prune
13
+type Resolver interface {
14
+	Resolve() ([]*kapi.ReplicationController, error)
15
+}
16
+
17
+// mergeResolver merges the set of results from multiple resolvers
18
+type mergeResolver struct {
19
+	resolvers []Resolver
20
+}
21
+
22
+func (m *mergeResolver) Resolve() ([]*kapi.ReplicationController, error) {
23
+	results := []*kapi.ReplicationController{}
24
+	for _, resolver := range m.resolvers {
25
+		items, err := resolver.Resolve()
26
+		if err != nil {
27
+			return nil, err
28
+		}
29
+		results = append(results, items...)
30
+	}
31
+	return results, nil
32
+}
33
+
34
+// NewOrphanDeploymentResolver returns a Resolver that matches objects with no associated DeploymentConfig and has a DeploymentStatus in filter
35
+func NewOrphanDeploymentResolver(dataSet DataSet, deploymentStatusFilter []deployapi.DeploymentStatus) Resolver {
36
+	filter := util.NewStringSet()
37
+	for _, deploymentStatus := range deploymentStatusFilter {
38
+		filter.Insert(string(deploymentStatus))
39
+	}
40
+	return &orphanDeploymentResolver{
41
+		dataSet:                dataSet,
42
+		deploymentStatusFilter: filter,
43
+	}
44
+}
45
+
46
+// orphanDeploymentResolver resolves orphan deployments that match the specified filter
47
+type orphanDeploymentResolver struct {
48
+	dataSet                DataSet
49
+	deploymentStatusFilter util.StringSet
50
+}
51
+
52
+// Resolve the matching set of objects
53
+func (o *orphanDeploymentResolver) Resolve() ([]*kapi.ReplicationController, error) {
54
+	deployments, err := o.dataSet.ListDeployments()
55
+	if err != nil {
56
+		return nil, err
57
+	}
58
+
59
+	results := []*kapi.ReplicationController{}
60
+	for _, deployment := range deployments {
61
+		deploymentStatus := deployutil.DeploymentStatusFor(deployment)
62
+		if !o.deploymentStatusFilter.Has(string(deploymentStatus)) {
63
+			continue
64
+		}
65
+		_, exists, _ := o.dataSet.GetDeploymentConfig(deployment)
66
+		if !exists {
67
+			results = append(results, deployment)
68
+		}
69
+	}
70
+	return results, nil
71
+}
72
+
73
+type perDeploymentConfigResolver struct {
74
+	dataSet      DataSet
75
+	keepComplete int
76
+	keepFailed   int
77
+}
78
+
79
+// NewPerDeploymentConfigResolver returns a Resolver that selects items to prune per config
80
+func NewPerDeploymentConfigResolver(dataSet DataSet, keepComplete int, keepFailed int) Resolver {
81
+	return &perDeploymentConfigResolver{
82
+		dataSet:      dataSet,
83
+		keepComplete: keepComplete,
84
+		keepFailed:   keepFailed,
85
+	}
86
+}
87
+
88
+func (o *perDeploymentConfigResolver) Resolve() ([]*kapi.ReplicationController, error) {
89
+	deploymentConfigs, err := o.dataSet.ListDeploymentConfigs()
90
+	if err != nil {
91
+		return nil, err
92
+	}
93
+
94
+	completeStates := util.NewStringSet(string(deployapi.DeploymentStatusComplete))
95
+	failedStates := util.NewStringSet(string(deployapi.DeploymentStatusFailed))
96
+
97
+	results := []*kapi.ReplicationController{}
98
+	for _, deploymentConfig := range deploymentConfigs {
99
+		deployments, err := o.dataSet.ListDeploymentsByDeploymentConfig(deploymentConfig)
100
+		if err != nil {
101
+			return nil, err
102
+		}
103
+
104
+		completeDeployments, failedDeployments := []*kapi.ReplicationController{}, []*kapi.ReplicationController{}
105
+		for _, deployment := range deployments {
106
+			status := deployutil.DeploymentStatusFor(deployment)
107
+			if completeStates.Has(string(status)) {
108
+				completeDeployments = append(completeDeployments, deployment)
109
+			} else if failedStates.Has(string(status)) {
110
+				failedDeployments = append(failedDeployments, deployment)
111
+			}
112
+		}
113
+		sort.Sort(sortableReplicationControllers(completeDeployments))
114
+		sort.Sort(sortableReplicationControllers(failedDeployments))
115
+
116
+		if o.keepComplete >= 0 && o.keepComplete < len(completeDeployments) {
117
+			results = append(results, completeDeployments[o.keepComplete:]...)
118
+		}
119
+		if o.keepFailed >= 0 && o.keepFailed < len(failedDeployments) {
120
+			results = append(results, failedDeployments[o.keepFailed:]...)
121
+		}
122
+	}
123
+	return results, nil
124
+}
0 125
new file mode 100644
... ...
@@ -0,0 +1,187 @@
0
+package prune
1
+
2
+import (
3
+	"fmt"
4
+	"sort"
5
+	"testing"
6
+	"time"
7
+
8
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
9
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
10
+
11
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+)
13
+
14
+type mockResolver struct {
15
+	items []*kapi.ReplicationController
16
+	err   error
17
+}
18
+
19
+func (m *mockResolver) Resolve() ([]*kapi.ReplicationController, error) {
20
+	return m.items, m.err
21
+}
22
+
23
+func TestMergeResolver(t *testing.T) {
24
+	resolverA := &mockResolver{
25
+		items: []*kapi.ReplicationController{
26
+			mockDeployment("a", "b", nil),
27
+		},
28
+	}
29
+	resolverB := &mockResolver{
30
+		items: []*kapi.ReplicationController{
31
+			mockDeployment("c", "d", nil),
32
+		},
33
+	}
34
+	resolver := &mergeResolver{resolvers: []Resolver{resolverA, resolverB}}
35
+	results, err := resolver.Resolve()
36
+	if err != nil {
37
+		t.Errorf("Unexpected error %v", err)
38
+	}
39
+	if len(results) != 2 {
40
+		t.Errorf("Unexpected results %v", results)
41
+	}
42
+	expectedNames := util.NewStringSet("b", "d")
43
+	for _, item := range results {
44
+		if !expectedNames.Has(item.Name) {
45
+			t.Errorf("Unexpected name %v", item.Name)
46
+		}
47
+	}
48
+}
49
+
50
+func TestOrphanDeploymentResolver(t *testing.T) {
51
+	activeDeploymentConfig := mockDeploymentConfig("a", "active-deployment-config")
52
+	inactiveDeploymentConfig := mockDeploymentConfig("a", "inactive-deployment-config")
53
+
54
+	deploymentConfigs := []*deployapi.DeploymentConfig{activeDeploymentConfig}
55
+	deployments := []*kapi.ReplicationController{}
56
+
57
+	expectedNames := util.StringSet{}
58
+	deploymentStatusOptions := []deployapi.DeploymentStatus{
59
+		deployapi.DeploymentStatusComplete,
60
+		deployapi.DeploymentStatusFailed,
61
+		deployapi.DeploymentStatusNew,
62
+		deployapi.DeploymentStatusPending,
63
+		deployapi.DeploymentStatusRunning,
64
+	}
65
+
66
+	deploymentStatusFilter := []deployapi.DeploymentStatus{
67
+		deployapi.DeploymentStatusComplete,
68
+		deployapi.DeploymentStatusFailed,
69
+	}
70
+	deploymentStatusFilterSet := util.StringSet{}
71
+	for _, deploymentStatus := range deploymentStatusFilter {
72
+		deploymentStatusFilterSet.Insert(string(deploymentStatus))
73
+	}
74
+
75
+	for _, deploymentStatusOption := range deploymentStatusOptions {
76
+		deployments = append(deployments, withStatus(mockDeployment("a", string(deploymentStatusOption)+"-active", activeDeploymentConfig), deploymentStatusOption))
77
+		deployments = append(deployments, withStatus(mockDeployment("a", string(deploymentStatusOption)+"-inactive", inactiveDeploymentConfig), deploymentStatusOption))
78
+		deployments = append(deployments, withStatus(mockDeployment("a", string(deploymentStatusOption)+"-orphan", nil), deploymentStatusOption))
79
+		if deploymentStatusFilterSet.Has(string(deploymentStatusOption)) {
80
+			expectedNames.Insert(string(deploymentStatusOption) + "-inactive")
81
+			expectedNames.Insert(string(deploymentStatusOption) + "-orphan")
82
+		}
83
+	}
84
+
85
+	dataSet := NewDataSet(deploymentConfigs, deployments)
86
+	resolver := NewOrphanDeploymentResolver(dataSet, deploymentStatusFilter)
87
+	results, err := resolver.Resolve()
88
+	if err != nil {
89
+		t.Errorf("Unexpected error %v", err)
90
+	}
91
+	foundNames := util.StringSet{}
92
+	for _, result := range results {
93
+		foundNames.Insert(result.Name)
94
+	}
95
+	if len(foundNames) != len(expectedNames) || !expectedNames.HasAll(foundNames.List()...) {
96
+		t.Errorf("expected %v, actual %v", expectedNames, foundNames)
97
+	}
98
+}
99
+
100
+func TestPerDeploymentConfigResolver(t *testing.T) {
101
+	deploymentStatusOptions := []deployapi.DeploymentStatus{
102
+		deployapi.DeploymentStatusComplete,
103
+		deployapi.DeploymentStatusFailed,
104
+		deployapi.DeploymentStatusNew,
105
+		deployapi.DeploymentStatusPending,
106
+		deployapi.DeploymentStatusRunning,
107
+	}
108
+	deploymentConfigs := []*deployapi.DeploymentConfig{
109
+		mockDeploymentConfig("a", "deployment-config-1"),
110
+		mockDeploymentConfig("b", "deployment-config-2"),
111
+	}
112
+	deploymentsPerStatus := 100
113
+	deployments := []*kapi.ReplicationController{}
114
+	for _, deploymentConfig := range deploymentConfigs {
115
+		for _, deploymentStatusOption := range deploymentStatusOptions {
116
+			for i := 0; i < deploymentsPerStatus; i++ {
117
+				deployment := withStatus(mockDeployment(deploymentConfig.Namespace, fmt.Sprintf("%v-%v-%v", deploymentConfig.Name, deploymentStatusOption, i), deploymentConfig), deploymentStatusOption)
118
+				deployments = append(deployments, deployment)
119
+			}
120
+		}
121
+	}
122
+
123
+	now := util.Now()
124
+	for i := range deployments {
125
+		creationTimestamp := util.NewTime(now.Time.Add(-1 * time.Duration(i) * time.Hour))
126
+		deployments[i].CreationTimestamp = creationTimestamp
127
+	}
128
+
129
+	// test number to keep at varying ranges
130
+	for keep := 0; keep < deploymentsPerStatus*2; keep++ {
131
+		dataSet := NewDataSet(deploymentConfigs, deployments)
132
+
133
+		expectedNames := util.StringSet{}
134
+		deploymentCompleteStatusFilterSet := util.NewStringSet(string(deployapi.DeploymentStatusComplete))
135
+		deploymentFailedStatusFilterSet := util.NewStringSet(string(deployapi.DeploymentStatusFailed))
136
+
137
+		for _, deploymentConfig := range deploymentConfigs {
138
+			deploymentItems, err := dataSet.ListDeploymentsByDeploymentConfig(deploymentConfig)
139
+			if err != nil {
140
+				t.Errorf("Unexpected err %v", err)
141
+			}
142
+			completedDeployments, failedDeployments := []*kapi.ReplicationController{}, []*kapi.ReplicationController{}
143
+			for _, deployment := range deploymentItems {
144
+				status := deployment.Annotations[deployapi.DeploymentStatusAnnotation]
145
+				if deploymentCompleteStatusFilterSet.Has(status) {
146
+					completedDeployments = append(completedDeployments, deployment)
147
+				} else if deploymentFailedStatusFilterSet.Has(status) {
148
+					failedDeployments = append(failedDeployments, deployment)
149
+				}
150
+			}
151
+			sort.Sort(sortableReplicationControllers(completedDeployments))
152
+			sort.Sort(sortableReplicationControllers(failedDeployments))
153
+			purgeCompleted := []*kapi.ReplicationController{}
154
+			purgeFailed := []*kapi.ReplicationController{}
155
+			if keep >= 0 && keep < len(completedDeployments) {
156
+				purgeCompleted = completedDeployments[keep:]
157
+			}
158
+			if keep >= 0 && keep < len(failedDeployments) {
159
+				purgeFailed = failedDeployments[keep:]
160
+			}
161
+			for _, deployment := range purgeCompleted {
162
+				expectedNames.Insert(deployment.Name)
163
+			}
164
+			for _, deployment := range purgeFailed {
165
+				expectedNames.Insert(deployment.Name)
166
+			}
167
+		}
168
+
169
+		resolver := NewPerDeploymentConfigResolver(dataSet, keep, keep)
170
+		results, err := resolver.Resolve()
171
+		if err != nil {
172
+			t.Errorf("Unexpected error %v", err)
173
+		}
174
+		foundNames := util.StringSet{}
175
+		for _, result := range results {
176
+			foundNames.Insert(result.Name)
177
+		}
178
+		if len(foundNames) != len(expectedNames) || !expectedNames.HasAll(foundNames.List()...) {
179
+			expectedValues := expectedNames.List()
180
+			actualValues := foundNames.List()
181
+			sort.Strings(expectedValues)
182
+			sort.Strings(actualValues)
183
+			t.Errorf("keep %v\n, expected \n\t%v\n, actual \n\t%v\n", keep, expectedValues, actualValues)
184
+		}
185
+	}
186
+}
0 187
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package prune
1
+
2
+import (
3
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
4
+)
5
+
6
+// sortableReplicationControllers supports sorting ReplicationController items by most recently created
7
+type sortableReplicationControllers []*kapi.ReplicationController
8
+
9
+func (s sortableReplicationControllers) Len() int {
10
+	return len(s)
11
+}
12
+
13
+func (s sortableReplicationControllers) Less(i, j int) bool {
14
+	return !s[i].CreationTimestamp.Before(s[j].CreationTimestamp)
15
+}
16
+
17
+func (s sortableReplicationControllers) Swap(i, j int) {
18
+	t := s[i]
19
+	s[i] = s[j]
20
+	s[j] = t
21
+}
0 22
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package prune
1
+
2
+import (
3
+	"sort"
4
+	"testing"
5
+	"time"
6
+
7
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
8
+	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
9
+)
10
+
11
+// TestSort verifies that builds are sorted by most recently created
12
+func TestSort(t *testing.T) {
13
+	present := util.Now()
14
+	past := util.NewTime(present.Time.Add(-1 * time.Minute))
15
+	controllers := []*kapi.ReplicationController{
16
+		{
17
+			ObjectMeta: kapi.ObjectMeta{
18
+				Name:              "past",
19
+				CreationTimestamp: past,
20
+			},
21
+		},
22
+		{
23
+			ObjectMeta: kapi.ObjectMeta{
24
+				Name:              "present",
25
+				CreationTimestamp: present,
26
+			},
27
+		},
28
+	}
29
+	sort.Sort(sortableReplicationControllers(controllers))
30
+	if controllers[0].Name != "present" {
31
+		t.Errorf("Unexpected sort order")
32
+	}
33
+	if controllers[1].Name != "past" {
34
+		t.Errorf("Unexpected sort order")
35
+	}
36
+}