Skip to content

Commit 894d312

Browse files
committed
OCM-11581 | feat: edit/cluster support for SDN->OVN migrations
1 parent cfd8354 commit 894d312

File tree

4 files changed

+260
-7
lines changed

4 files changed

+260
-7
lines changed

cmd/edit/cluster/cmd.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ var args struct {
6969
allowedRegistriesForImport string
7070
platformAllowlist string
7171
additionalTrustedCa string
72+
73+
// SDN -> OVN Migration
74+
networkType string
75+
ovnInternalSubnets string
7276
}
7377

7478
var clusterRegistryConfigArgs *clusterregistryconfig.ClusterRegistryConfigArgs
@@ -186,6 +190,23 @@ func init() {
186190
"",
187191
"Account ID used for billing subscriptions purchased through the AWS console for ROSA",
188192
)
193+
194+
flags.StringVar(
195+
&args.networkType,
196+
"network-type",
197+
"",
198+
"Migrate a cluster's network type from OpenShiftSDN to OVN-Kubernetes",
199+
)
200+
201+
flags.StringVar(
202+
&args.ovnInternalSubnets,
203+
ocm.OvnInternalSubnetsFlagName,
204+
"",
205+
"OVN-Kubernetes internal subnet configuration for migrating 'network-type' from OpenShiftSDN -> "+
206+
"OVN-Kubernetes. Must be supplied as a string=value pair with any of 'join', 'transit', 'masquerade' "+
207+
"followed by a CIDR. \nExample: '--ovn-internal-subnets=\"join=192.168.255.0/24,transit=192.168.255.0/24,"+
208+
"masquerade=192.168.255.0/24\"'",
209+
)
189210
}
190211

191212
func run(cmd *cobra.Command, _ []string) {
@@ -223,6 +244,18 @@ func run(cmd *cobra.Command, _ []string) {
223244
os.Exit(1)
224245
}
225246

247+
ovnInternalSubnets, err := validateOvnInternalSubnetConfiguration()
248+
if err != nil {
249+
r.Reporter.Errorf(fmt.Sprintf("%s", err))
250+
os.Exit(1)
251+
}
252+
253+
networkType, err := validateNetworkType()
254+
if err != nil {
255+
r.Reporter.Errorf(fmt.Sprintf("%s", err))
256+
os.Exit(1)
257+
}
258+
226259
if interactive.Enabled() {
227260
r.Reporter.Infof("Interactive mode enabled.\n" +
228261
"Any optional fields can be ignored and will not be updated.")
@@ -702,6 +735,93 @@ func run(cmd *cobra.Command, _ []string) {
702735
}
703736
}
704737

738+
// SDN -> OVN Migration
739+
var clusterNetworkType string
740+
if !cmd.Flags().Changed(ocm.NetworkTypeFlagName) {
741+
var ok bool
742+
if cluster.Network() == nil {
743+
ok = false
744+
} else {
745+
networkType, ok = cluster.Network().GetType()
746+
clusterNetworkType = networkType // Store the cluster's current network type for interactive usage
747+
}
748+
if !ok {
749+
r.Reporter.Errorf("Unable to get cluster's network type")
750+
os.Exit(1)
751+
}
752+
}
753+
754+
var migrateNetworkType bool
755+
// Only prompt user with migrating the cluster's network type when it is not OVN-Kubernetes
756+
if interactive.Enabled() && clusterNetworkType != "" && clusterNetworkType != ocm.NetworkTypeOvn &&
757+
clusterNetworkType != ocm.NetworkTypeOvnAlias {
758+
759+
migrateNetworkType, err = interactive.GetBool(interactive.Input{
760+
Question: "Migrate cluster network type from OpenShiftSDN -> OVN-Kubernetes",
761+
Help: "Clusters are required to migrate from network type 'OpenShiftSDN' to 'OVN-Kubernetes', this allows " +
762+
"you to do this along with your cluster changes",
763+
Default: false,
764+
})
765+
766+
if err != nil {
767+
r.Reporter.Errorf("%s", err)
768+
os.Exit(1)
769+
}
770+
771+
if migrateNetworkType {
772+
migrateNetworkType, err = confirmMigration()
773+
774+
if err != nil {
775+
r.Reporter.Errorf("%s", err)
776+
os.Exit(1)
777+
}
778+
}
779+
780+
if migrateNetworkType && interactive.Enabled() {
781+
networkType, err = interactive.GetString(interactive.Input{
782+
Question: "Network type for cluster",
783+
Help: cmd.Flags().Lookup(ocm.NetworkTypeFlagName).Usage,
784+
Default: ocm.NetworkTypeOvn,
785+
})
786+
if err != nil {
787+
r.Reporter.Errorf("Expected a valid value: %v", err)
788+
os.Exit(1)
789+
}
790+
791+
ovnSubnets, err := interactive.GetString(interactive.Input{
792+
Question: "OVN-Kubernetes internal subnet configuration for cluster",
793+
Help: cmd.Flags().Lookup(ocm.OvnInternalSubnetsFlagName).Usage,
794+
Default: ovnInternalSubnets,
795+
Options: []string{ocm.SubnetConfigTransit, ocm.SubnetConfigJoin, ocm.SubnetConfigMasquerade},
796+
Required: false,
797+
})
798+
if err != nil {
799+
r.Reporter.Errorf("Expected a valid value: %v", err)
800+
}
801+
ovnInternalSubnets, err = ocm.ParseAndValidateOvnInternalSubnets(ovnSubnets)
802+
if err != nil {
803+
r.Reporter.Errorf("Failed to parse '%s': %s", ocm.OvnInternalSubnetsFlagName, err)
804+
}
805+
}
806+
}
807+
808+
if cmd.Flags().Changed(ocm.NetworkTypeFlagName) && networkType == ocm.NetworkTypeOvn {
809+
810+
migrateNetworkType, err = confirmMigration()
811+
812+
if err != nil {
813+
r.Reporter.Errorf("%s", err)
814+
os.Exit(1)
815+
}
816+
}
817+
818+
if networkType == ocm.NetworkTypeOvn && migrateNetworkType {
819+
clusterConfig.NetworkType = networkType
820+
if len(ovnInternalSubnets) > 0 {
821+
clusterConfig.OvnInternalSubnetConfiguration = ovnInternalSubnets
822+
}
823+
}
824+
705825
var billingAccount string
706826
if cmd.Flags().Changed("billing-account") {
707827
billingAccount = args.billingAccount
@@ -841,6 +961,32 @@ func validateExpiration() (expiration time.Time, err error) {
841961
return
842962
}
843963

964+
// SDN -> OVN migration subnet configuration validator
965+
func validateOvnInternalSubnetConfiguration() (ovnInternalSubnets map[string]string, err error) {
966+
if len(args.ovnInternalSubnets) > 0 {
967+
if args.networkType == "" {
968+
err = fmt.Errorf("Expected a value for %s when supplying the flag %s", ocm.NetworkTypeFlagName,
969+
ocm.OvnInternalSubnetsFlagName)
970+
return
971+
}
972+
ovnInternalSubnets, err = ocm.ParseAndValidateOvnInternalSubnets(args.ovnInternalSubnets)
973+
}
974+
return
975+
}
976+
977+
// SDN -> OVN migration network type validator (one option)
978+
func validateNetworkType() (networkConfig string, err error) {
979+
if len(args.networkType) > 0 {
980+
if args.networkType != ocm.NetworkTypeOvn && args.networkType != ocm.NetworkTypeOvnAlias {
981+
err = fmt.Errorf("Incorrect network type '%s', please use '%s' or remove the flag",
982+
args.networkType, ocm.NetworkTypeOvn)
983+
} else {
984+
networkConfig = ocm.NetworkTypeOvn // allows use of alias (OVN-Kubernetes)- but sets it to correct value for API
985+
}
986+
}
987+
return
988+
}
989+
844990
// parseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
845991
func parseRFC3339(s string) (time.Time, error) {
846992
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
@@ -979,3 +1125,14 @@ func BuildClusterConfigWithRegistry(clusterConfig ocm.Spec, allowedRegistries []
9791125
clusterConfig.AllowedRegistriesForImport = allowedRegistriesForImport
9801126
return clusterConfig, nil
9811127
}
1128+
1129+
func confirmMigration() (bool, error) {
1130+
return interactive.GetBool(interactive.Input{
1131+
Question: "Changing the network plugin will reboot cluster nodes, can not be interrupted or rolled " +
1132+
"back, and can not be combined with other operations such as cluster upgrades. \n\nConfirm that " +
1133+
"you want to proceed with migrating from 'OpenShiftSDN' to 'OVN-Kubernetes",
1134+
Help: "Confirm that you are wanting to migrate your cluster's network type, it may be safer to do " +
1135+
"this migration with no other changes",
1136+
Default: false,
1137+
})
1138+
}

cmd/rosa/structure_test/command_args/rosa/edit/cluster/command_args.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@
2121
- name: registry-config-platform-allowlist
2222
- name: registry-config-additional-trusted-ca
2323
- name: billing-account
24+
- name: network-type
25+
- name: ovn-internal-subnets

pkg/ocm/clusters.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,15 @@ type Spec struct {
9393
AvailabilityZones []string
9494

9595
// Network config
96-
NetworkType string
97-
MachineCIDR net.IPNet
98-
ServiceCIDR net.IPNet
99-
PodCIDR net.IPNet
100-
HostPrefix int
101-
Private *bool
102-
PrivateLink *bool
96+
NetworkType string
97+
SubnetConfiguration string
98+
OvnInternalSubnetConfiguration map[string]string
99+
MachineCIDR net.IPNet
100+
ServiceCIDR net.IPNet
101+
PodCIDR net.IPNet
102+
HostPrefix int
103+
Private *bool
104+
PrivateLink *bool
103105

104106
// Properties
105107
CustomProperties map[string]string
@@ -619,6 +621,40 @@ func (c *Client) UpdateCluster(clusterKey string, creator *aws.Creator, config S
619621
clusterBuilder = clusterBuilder.DisableUserWorkloadMonitoring(*config.DisableWorkloadMonitoring)
620622
}
621623

624+
// SDN -> OVN Migration
625+
if config.NetworkType == NetworkTypes[1] {
626+
// Create a request body for the specific cluster migration.
627+
requestBuilder := v1.ClusterMigrationBuilder{}
628+
requestBuilder.Type(v1.ClusterMigrationTypeSdnToOvn) // Type is required
629+
630+
if len(config.OvnInternalSubnetConfiguration) > 0 {
631+
// Create a builder for the specific migration type's configuration if necessary
632+
sdnToOvnBuilder := &v1.SdnToOvnClusterMigrationBuilder{}
633+
if _, ok := config.OvnInternalSubnetConfiguration[JoinIpv4]; ok {
634+
sdnToOvnBuilder.JoinIpv4(config.OvnInternalSubnetConfiguration[JoinIpv4])
635+
}
636+
if _, ok := config.OvnInternalSubnetConfiguration[TransitIpv4]; ok {
637+
sdnToOvnBuilder.TransitIpv4(config.OvnInternalSubnetConfiguration[TransitIpv4])
638+
}
639+
if _, ok := config.OvnInternalSubnetConfiguration[MasqueradeIpv4]; ok {
640+
sdnToOvnBuilder.MasqueradeIpv4(config.OvnInternalSubnetConfiguration[MasqueradeIpv4])
641+
}
642+
requestBuilder.SdnToOvn(sdnToOvnBuilder)
643+
}
644+
645+
requestBody, err := requestBuilder.Build()
646+
if err != nil {
647+
return errors.UserWrapf(err, "Unable to create cluster migration request")
648+
}
649+
650+
// Send the request to add a cluster migration.
651+
response, err := c.ocm.ClustersMgmt().V1().Clusters().
652+
Cluster(cluster.ID()).Migrations().Add().Body(requestBody).Send()
653+
if err != nil {
654+
return handleErr(response.Error(), err)
655+
}
656+
}
657+
622658
if config.HTTPProxy != nil || config.HTTPSProxy != nil || config.NoProxy != nil {
623659
clusterProxyBuilder := cmv1.NewProxy()
624660
if config.HTTPProxy != nil {

pkg/ocm/migrations.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,28 @@
11
package ocm
22

33
import (
4+
"net"
5+
"strings"
6+
47
cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
58
errors "github.com/zgalor/weberr"
69
)
710

11+
const (
12+
TransitIpv4 = "transit_ipv4"
13+
JoinIpv4 = "join_ipv4"
14+
MasqueradeIpv4 = "masquerade_ipv4"
15+
16+
SubnetConfigTransit = "transit"
17+
SubnetConfigMasquerade = "masquerade"
18+
SubnetConfigJoin = "join"
19+
20+
OvnInternalSubnetsFlagName = "ovn-internal-subnets"
21+
NetworkTypeFlagName = "network-type"
22+
NetworkTypeOvn = "OVNKubernetes"
23+
NetworkTypeOvnAlias = "OVN-Kubernetes"
24+
)
25+
826
func (c *Client) FetchClusterMigrations(clusterId string) (*cmv1.ClusterMigrationsListResponse,
927
error) {
1028
collection := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterId)
@@ -17,3 +35,43 @@ func (c *Client) FetchClusterMigrations(clusterId string) (*cmv1.ClusterMigratio
1735

1836
return clusterMigrations, nil
1937
}
38+
39+
// SDN -> OVN migration key=value parser
40+
func ParseAndValidateOvnInternalSubnets(internalSubnets string) (map[string]string, error) {
41+
subnetMap := make(map[string]string)
42+
if internalSubnets == "" || internalSubnets == `""` {
43+
return nil, errors.UserErrorf("Expected value for '%s'",
44+
OvnInternalSubnetsFlagName)
45+
} else {
46+
possibleConfigs := strings.Split(internalSubnets, ",")
47+
for i, config := range possibleConfigs {
48+
// Skip last one if empty, keep the ones leading up to it
49+
if config == "" && i == len(possibleConfigs)-1 {
50+
continue
51+
}
52+
if !strings.Contains(config, "=") {
53+
return nil, errors.UserErrorf("Expected key=value format for labels")
54+
}
55+
tokens := strings.Split(config, "=")
56+
// Validate key is correct
57+
if tokens[0] != SubnetConfigTransit && tokens[0] != SubnetConfigMasquerade && tokens[0] != SubnetConfigJoin {
58+
return nil, errors.UserErrorf("Expected 'join', 'transit', or "+
59+
"'masquerade' as keys for '%s'", OvnInternalSubnetsFlagName)
60+
}
61+
// Validate value
62+
_, _, err := net.ParseCIDR(tokens[1])
63+
if err != nil {
64+
return nil, errors.UserErrorf("Expected valid CIDR as value "+
65+
"for '%s'", tokens[0])
66+
}
67+
key := strings.TrimSpace(tokens[0])
68+
value := strings.TrimSpace(tokens[1])
69+
if _, exists := subnetMap[key]; exists {
70+
return nil, errors.UserErrorf("Duplicated subnet configuration key "+
71+
"'%s' used", key)
72+
}
73+
subnetMap[key] = value
74+
}
75+
}
76+
return subnetMap, nil
77+
}

0 commit comments

Comments
 (0)