Skip to content

Commit 54bb08a

Browse files
committed
Add migrate command for legacy HPAs
There are current broken HPAs floating around that either use the legacy oapi DeploymentConfig defintion (`v1.DeploymentConfig`) or the incorrect API group, due to the webconsole (all scalables, but with the group as `extensions/v1beta1`). This introduces a migrate command that corrects the ScaleTargetRef of those HPAs to have correct API groups that are usable by generic scale clients.
1 parent f2b6cd1 commit 54bb08a

File tree

3 files changed

+290
-0
lines changed

3 files changed

+290
-0
lines changed

pkg/oc/admin/admin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
migrateauthorization "github.com/openshift/origin/pkg/oc/admin/migrate/authorization"
2222
migrateetcd "github.com/openshift/origin/pkg/oc/admin/migrate/etcd"
2323
migrateimages "github.com/openshift/origin/pkg/oc/admin/migrate/images"
24+
migratehpa "github.com/openshift/origin/pkg/oc/admin/migrate/legacyhpa"
2425
migratestorage "github.com/openshift/origin/pkg/oc/admin/migrate/storage"
2526
"github.com/openshift/origin/pkg/oc/admin/network"
2627
"github.com/openshift/origin/pkg/oc/admin/node"
@@ -97,6 +98,7 @@ func NewCommandAdmin(name, fullName string, in io.Reader, out io.Writer, errout
9798
migratestorage.NewCmdMigrateAPIStorage("storage", fullName+" "+migrate.MigrateRecommendedName+" storage", f, in, out, errout),
9899
migrateauthorization.NewCmdMigrateAuthorization("authorization", fullName+" "+migrate.MigrateRecommendedName+" authorization", f, in, out, errout),
99100
migrateetcd.NewCmdMigrateTTLs("etcd-ttl", fullName+" "+migrate.MigrateRecommendedName+" etcd-ttl", f, in, out, errout),
101+
migratehpa.NewCmdMigrateLegacyHPA("legacy-hpa", fullName+" "+migrate.MigrateRecommendedName+" legacy-hpa", f, in, out, errout),
100102
),
101103
top.NewCommandTop(top.TopRecommendedName, fullName+" "+top.TopRecommendedName, f, out, errout),
102104
image.NewCmdVerifyImageSignature(name, fullName+" "+image.VerifyRecommendedName, f, out, errout),

pkg/oc/admin/migrate/legacyhpa/hpa.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package legacyhpa
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"strings"
7+
8+
"github.com/spf13/cobra"
9+
10+
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/runtime/schema"
12+
autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling"
13+
autoscalingclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion"
14+
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
15+
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
16+
"k8s.io/kubernetes/pkg/kubectl/resource"
17+
18+
"github.com/openshift/origin/pkg/oc/admin/migrate"
19+
"github.com/openshift/origin/pkg/oc/cli/util/clientcmd"
20+
)
21+
22+
var (
23+
defaultMigrations = map[versionKind]versionKind{
24+
// legacy oapi group
25+
{"v1", "DeploymentConfig"}: {"apps.openshift.io/v1", "DeploymentConfig"},
26+
27+
// webconsole shenaniganry
28+
{"extensions/v1beta1", "DeploymentConfig"}: {"apps.openshift.io/v1", "DeploymentConfig"},
29+
{"extensions/v1beta1", "Deployment"}: {"apps/v1", "Deployment"},
30+
{"extensions/v1beta1", "ReplicaSet"}: {"apps/v1", "ReplicaSet"},
31+
{"extensions/v1beta1", "ReplicationController"}: {"v1", "ReplicationController"},
32+
}
33+
34+
internalMigrateLegacyHPALong = templates.LongDesc(fmt.Sprintf(`
35+
Migrate Horizontal Pod Autoscalers to refer to new API groups
36+
37+
This command locates and updates every Horizontal Pod Autoscaler which refers to a particular
38+
group-version-kind to refer to some other, equivalent group-version-kind.
39+
40+
The following transformations will occur:
41+
42+
%s`, prettyPrintMigrations(defaultMigrations)))
43+
44+
internalMigrateLegacyHPAExample = templates.Examples(`
45+
# Perform a dry-run of updating all objects
46+
%[1]s
47+
48+
# To actually perform the update, the confirm flag must be appended
49+
%[1]s --confirm
50+
51+
# Migrate a specific group-version-kind to the latest preferred version
52+
%[1]s --initial=extensions/v1beta1.ReplicaSet --confirm
53+
54+
# Migrate a specific group-version-kind to a specific group-version-kind
55+
%[1]s --initial=v1.DeploymentConfig --final=apps.openshift.io/v1.DeploymentConfig --confirm`)
56+
)
57+
58+
func prettyPrintMigrations(versionKinds map[versionKind]versionKind) string {
59+
lines := make([]string, 0, len(versionKinds))
60+
for initial, final := range versionKinds {
61+
line := fmt.Sprintf(" - %s.%s --> %s.%s", initial.APIVersion, initial.Kind, final.APIVersion, final.Kind)
62+
lines = append(lines, line)
63+
}
64+
65+
return strings.Join(lines, "\n")
66+
}
67+
68+
type versionKind struct {
69+
APIVersion string
70+
Kind string
71+
}
72+
73+
func (vk versionKind) GroupVersionKind() (schema.GroupVersionKind, error) {
74+
groupVer, err := schema.ParseGroupVersion(vk.APIVersion)
75+
if err != nil {
76+
return schema.GroupVersionKind{}, err
77+
}
78+
return groupVer.WithKind(vk.Kind), nil
79+
}
80+
81+
type MigrateLegacyHPAOptions struct {
82+
// maps initial gvks to final gvks in the same format
83+
// as HPAs use (CrossVersionObjectReferences) for ease of access.
84+
finalVersionKinds map[versionKind]versionKind
85+
86+
hpaClient autoscalingclient.AutoscalingInterface
87+
88+
migrate.ResourceOptions
89+
}
90+
91+
func (o *MigrateLegacyHPAOptions) Bind(c *cobra.Command) {
92+
o.ResourceOptions.Bind(c)
93+
}
94+
95+
// NewCmdMigrateLegacyAPI implements a MigrateLegacyHPA command
96+
func NewCmdMigrateLegacyHPA(name, fullName string, f *clientcmd.Factory, in io.Reader, out, errout io.Writer) *cobra.Command {
97+
options := &MigrateLegacyHPAOptions{
98+
ResourceOptions: migrate.ResourceOptions{
99+
In: in,
100+
Out: out,
101+
ErrOut: errout,
102+
103+
AllNamespaces: true,
104+
Include: []string{"horizontalpodautoscalers.autoscaling"},
105+
},
106+
}
107+
cmd := &cobra.Command{
108+
Use: name,
109+
Short: "Update HPAs to point to the latest group-version-kinds",
110+
Long: internalMigrateLegacyHPALong,
111+
Example: fmt.Sprintf(internalMigrateLegacyHPAExample, fullName),
112+
Run: func(cmd *cobra.Command, args []string) {
113+
kcmdutil.CheckErr(options.Complete(name, f, cmd, args))
114+
kcmdutil.CheckErr(options.Validate())
115+
kcmdutil.CheckErr(options.Run())
116+
},
117+
}
118+
options.Bind(cmd)
119+
120+
return cmd
121+
}
122+
123+
func (o *MigrateLegacyHPAOptions) Complete(name string, f *clientcmd.Factory, c *cobra.Command, args []string) error {
124+
if len(args) != 0 {
125+
return fmt.Errorf("%s takes no positional arguments", name)
126+
}
127+
128+
o.ResourceOptions.SaveFn = o.save
129+
if err := o.ResourceOptions.Complete(f, c); err != nil {
130+
return err
131+
}
132+
133+
o.finalVersionKinds = make(map[versionKind]versionKind)
134+
135+
// copy all manual transformations in
136+
for initial, final := range defaultMigrations {
137+
o.finalVersionKinds[initial] = final
138+
}
139+
140+
kubeClientSet, err := f.ClientSet()
141+
if err != nil {
142+
return err
143+
}
144+
o.hpaClient = kubeClientSet.Autoscaling()
145+
146+
return nil
147+
}
148+
149+
func (o MigrateLegacyHPAOptions) Validate() error {
150+
return o.ResourceOptions.Validate()
151+
}
152+
153+
func (o MigrateLegacyHPAOptions) Run() error {
154+
return o.ResourceOptions.Visitor().Visit(func(info *resource.Info) (migrate.Reporter, error) {
155+
return o.checkAndTransform(info.Object)
156+
})
157+
}
158+
159+
func (o *MigrateLegacyHPAOptions) checkAndTransform(hpaRaw runtime.Object) (migrate.Reporter, error) {
160+
var hpa *autoscaling.HorizontalPodAutoscaler
161+
var wasHPA bool
162+
if hpa, wasHPA = hpaRaw.(*autoscaling.HorizontalPodAutoscaler); !wasHPA {
163+
return nil, fmt.Errorf("unrecognized object %#v", hpaRaw)
164+
}
165+
166+
currentVersionKind := versionKind{
167+
APIVersion: hpa.Spec.ScaleTargetRef.APIVersion,
168+
Kind: hpa.Spec.ScaleTargetRef.Kind,
169+
}
170+
171+
newVersionKind, err := o.latestVersionKind(currentVersionKind)
172+
if err != nil {
173+
return nil, err
174+
}
175+
176+
if currentVersionKind != newVersionKind {
177+
hpa.Spec.ScaleTargetRef.APIVersion = newVersionKind.APIVersion
178+
hpa.Spec.ScaleTargetRef.Kind = newVersionKind.Kind
179+
return migrate.ReporterBool(true), nil
180+
}
181+
182+
return migrate.ReporterBool(false), nil
183+
}
184+
185+
func (o *MigrateLegacyHPAOptions) latestVersionKind(current versionKind) (versionKind, error) {
186+
if newVersionKind, isKnown := o.finalVersionKinds[current]; isKnown {
187+
return newVersionKind, nil
188+
}
189+
190+
return current, nil
191+
}
192+
193+
// save invokes the API to alter an object. The reporter passed to this method is the same returned by
194+
// the migration visitor method. It should return an error if the input type cannot be saved
195+
// It returns migrate.ErrRecalculate if migration should be re-run on the provided object.
196+
func (o *MigrateLegacyHPAOptions) save(info *resource.Info, reporter migrate.Reporter) error {
197+
var hpa *autoscaling.HorizontalPodAutoscaler
198+
var wasHPA bool
199+
if hpa, wasHPA = info.Object.(*autoscaling.HorizontalPodAutoscaler); !wasHPA {
200+
return fmt.Errorf("unrecognized object %#v", info.Object)
201+
}
202+
203+
updatedHPA, err := o.hpaClient.HorizontalPodAutoscalers(hpa.Namespace).Update(hpa)
204+
if err != nil {
205+
return migrate.DefaultRetriable(info, err)
206+
}
207+
info.Refresh(updatedHPA, true)
208+
return nil
209+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package legacyhpa
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
autoscaling "k8s.io/kubernetes/pkg/apis/autoscaling"
9+
)
10+
11+
func TestDefaultMigrations(t *testing.T) {
12+
testCases := []struct {
13+
name string
14+
input versionKind
15+
output versionKind
16+
}{
17+
{
18+
name: "legacy-dc",
19+
input: versionKind{"v1", "DeploymentConfig"},
20+
output: versionKind{"apps.openshift.io/v1", "DeploymentConfig"},
21+
},
22+
{
23+
name: "console-dc",
24+
input: versionKind{"extensions/v1beta1", "DeploymentConfig"},
25+
output: versionKind{"apps.openshift.io/v1", "DeploymentConfig"},
26+
},
27+
{
28+
name: "console-rc",
29+
input: versionKind{"extensions/v1beta1", "ReplicationController"},
30+
output: versionKind{"v1", "ReplicationController"},
31+
},
32+
{
33+
name: "console-deploy",
34+
input: versionKind{"extensions/v1beta1", "Deployment"},
35+
output: versionKind{"apps/v1", "Deployment"},
36+
},
37+
{
38+
name: "console-rs",
39+
input: versionKind{"extensions/v1beta1", "ReplicaSet"},
40+
output: versionKind{"apps/v1", "ReplicaSet"},
41+
},
42+
{
43+
name: "ok-dc",
44+
input: versionKind{"apps.openshift.io/v1", "DeploymentConfig"},
45+
output: versionKind{"apps.openshift.io/v1", "DeploymentConfig"},
46+
},
47+
{
48+
name: "other",
49+
input: versionKind{"cheese/v1alpha1", "Cheddar"},
50+
output: versionKind{"cheese/v1alpha1", "Cheddar"},
51+
},
52+
}
53+
54+
opts := MigrateLegacyHPAOptions{
55+
finalVersionKinds: defaultMigrations,
56+
}
57+
58+
for _, tc := range testCases {
59+
oldHPA := &autoscaling.HorizontalPodAutoscaler{
60+
Spec: autoscaling.HorizontalPodAutoscalerSpec{
61+
ScaleTargetRef: autoscaling.CrossVersionObjectReference{
62+
APIVersion: tc.input.APIVersion,
63+
Kind: tc.input.Kind,
64+
Name: tc.name,
65+
},
66+
},
67+
}
68+
69+
reporter, err := opts.checkAndTransform(oldHPA)
70+
if !assert.NoError(t, err, "%s: expected not to produce an error", tc.name) {
71+
continue
72+
}
73+
74+
assert.Equal(t, tc.input != tc.output, reporter.Changed(), "%s: expected to properly indicate whether or not it the HPA was changed", tc.name)
75+
76+
newVersionKind := versionKind{oldHPA.Spec.ScaleTargetRef.APIVersion, oldHPA.Spec.ScaleTargetRef.Kind}
77+
assert.Equal(t, tc.output, newVersionKind, "%s: expected the HPA to be updated to the correct group-version-kind", tc.name)
78+
}
79+
}

0 commit comments

Comments
 (0)