Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions OWNERS_ALIASES
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,54 @@ aliases:
- mpatlasov
- rhrmo
- dfajmon
storage-approvers:
storage-approvers:
- jsafrane
- tsmetana
- gnufied
- dobsonj
- RomanBednar
- mpatlasov
- rhrmo
- dfajmon
- dfajmon
openshift-edge-reviewers:
- Neilhamza
- agullon
- copejon
- dhensel-rh
- eggfoobar
- eslutsky
- fonta-rh
- fracappa
- ggiguash
- jaypoulz
- jeff-roche
- jerpeter1
- jogeo
- kasturinarra
- lucaconsalvi
- mmakwana30
- pacevedom
- pmtk
- qJkee
- vimauro
openshift-edge-approvers:
- Neilhamza
- agullon
- brandisher
- copejon
- dhensel-rh
- eggfoobar
- eslutsky
- fonta-rh
- fracappa
- ggiguash
- jaypoulz
- jeff-roche
- jerpeter1
- jogeo
- kasturinarra
- lucaconsalvi
- mmakwana30
- pacevedom
- pmtk
- qJkee
8 changes: 8 additions & 0 deletions pkg/cli/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/openshift/oc/pkg/cli/admin/release"
"github.com/openshift/oc/pkg/cli/admin/restartkubelet"
"github.com/openshift/oc/pkg/cli/admin/top"
"github.com/openshift/oc/pkg/cli/admin/transition"
"github.com/openshift/oc/pkg/cli/admin/upgrade"
"github.com/openshift/oc/pkg/cli/admin/verifyimagesignature"
"github.com/openshift/oc/pkg/cli/admin/waitfornodereboot"
Expand All @@ -50,6 +51,9 @@ var adminLong = ktemplates.LongDesc(`
// inspectAlertsFeatureGate is an environment variable used to gate the inclusion of the inspect-alerts subcommand.
const inspectAlertsFeatureGate = "OC_ENABLE_CMD_INSPECT_ALERTS"

// transitionTopologyFeatureGate is an environment variable used to gate the inclusion of the transition topology subcommand.
const transitionTopologyFeatureGate = "OC_ENABLE_CMD_TRANSITION_TOPOLOGY"

func NewCommandAdmin(f kcmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
// Main command
cmds := &cobra.Command{
Expand All @@ -72,6 +76,10 @@ func NewCommandAdmin(f kcmdutil.Factory, streams genericiooptions.IOStreams) *co
clusterManagement = append(clusterManagement, inspectalerts.New(f, streams))
}

if kcmdutil.FeatureGate(transitionTopologyFeatureGate).IsEnabled() {
clusterManagement = append(clusterManagement, transition.NewCmdTransition(f, streams))
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
groups := ktemplates.CommandGroups{
{
Message: "Cluster Management:",
Expand Down
7 changes: 7 additions & 0 deletions pkg/cli/admin/transition/OWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# See the OWNERS docs: https://git.k8s.io/community/contributors/guide/owners.md
# This file just uses aliases defined in OWNERS_ALIASES.

approvers:
- openshift-edge-approvers
reviewers:
- openshift-edge-reviewers
163 changes: 163 additions & 0 deletions pkg/cli/admin/transition/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package transition

import (
"context"
"fmt"
"io"
"strings"

"github.com/spf13/cobra"

operatorv1 "github.com/openshift/api/operator/v1"
configv1client "github.com/openshift/client-go/config/clientset/versioned"
operatorv1client "github.com/openshift/client-go/operator/clientset/versioned"
v1helpers "github.com/openshift/library-go/pkg/operator/v1helpers"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
kcmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/util/templates"
)

var (
statusLong = templates.LongDesc(`
Monitor topology transition progress.

Displays the current control plane and infrastructure topology status,
and shows the cluster-config-operator transition conditions to monitor
transition progress.
`)

statusExample = templates.Examples(`
# Monitor transition progress
oc adm transition status
`)
)

// statusOptions holds options for the status subcommand
type statusOptions struct {
configClient configv1client.Interface
operatorClient operatorv1client.Interface

genericclioptions.IOStreams
}

// newCmdStatus creates the status subcommand
func newCmdStatus(f kcmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &statusOptions{
IOStreams: streams,
}

cmd := &cobra.Command{
Use: "status",
Short: "Monitor topology transition progress",
Long: statusLong,
Example: statusExample,
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(o.complete(f, cmd, args))
kcmdutil.CheckErr(o.run(cmd.Context()))
},
}

return cmd
}

// complete sets up all required fields from the factory
func (o *statusOptions) complete(f kcmdutil.Factory, cmd *cobra.Command, args []string) error {
restConfig, err := f.ToRESTConfig()
if err != nil {
return fmt.Errorf("failed to get REST config: %w", err)
}

o.configClient, err = configv1client.NewForConfig(restConfig)
if err != nil {
return fmt.Errorf("failed to create config client: %w", err)
}

o.operatorClient, err = operatorv1client.NewForConfig(restConfig)
if err != nil {
return fmt.Errorf("failed to create operator client: %w", err)
}

return nil
}

// run executes the status subcommand
func (o *statusOptions) run(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, apiRequestTimeout)
defer cancel()

if err := o.printTopologyStatus(ctx); err != nil {
return err
}

return o.printTopologyTransitionStatus(ctx)
}

// printTopologyStatus will output the Control Plane and Infrastructure topologies from spec and status
func (o *statusOptions) printTopologyStatus(ctx context.Context) error {
infra, err := o.configClient.ConfigV1().Infrastructures().Get(ctx, infrastructureResourceName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get Infrastructure resource: %w", err)
}

notSet := "(not set)"
cpSpecTopology := string(infra.Spec.ControlPlaneTopology)
if cpSpecTopology == "" {
cpSpecTopology = notSet
}

cpStatusTopology := string(infra.Status.ControlPlaneTopology)
if cpStatusTopology == "" {
cpStatusTopology = notSet
}

infraStatusTopology := string(infra.Status.InfrastructureTopology)
if infraStatusTopology == "" {
infraStatusTopology = notSet
}

var output strings.Builder
statusOutput := `
Control Plane Topology:
Spec (desired): %s
Status (current): %s

Infrastructure Topology:
Status (current): %s
`
fmt.Fprintf(&output, statusOutput, cpSpecTopology, cpStatusTopology, infraStatusTopology)

if _, err := io.WriteString(o.Out, output.String()); err != nil {
return err
}

return nil
}

func (o *statusOptions) printTopologyTransitionStatus(ctx context.Context) error {
operatorConfig, err := o.operatorClient.OperatorV1().Configs().Get(ctx, clusterConfigOperatorResourceName, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get configs.operator.openshift.io/cluster: %w", err)
}

// Pull out the relevant conditions from CCO
progressingCond := v1helpers.FindOperatorCondition(operatorConfig.Status.Conditions, topologyTransitionControllerProgressingCondition)
upgradeableCond := v1helpers.FindOperatorCondition(operatorConfig.Status.Conditions, topologyTransitionControllerUpgradeableCondition)

var output strings.Builder
fmt.Fprintln(&output, "\nTransition Status")
fmt.Fprintln(&output, formatTopologyConditionStatus("Progressing", progressingCond))
fmt.Fprintln(&output, formatTopologyConditionStatus("Upgradeable", upgradeableCond))

_, err = io.WriteString(o.Out, output.String())
return err
}

// formatTopologyConditionStatus formats the provided topology transition condition status under a 'label' heading
func formatTopologyConditionStatus(label string, cond *operatorv1.OperatorCondition) string {
if cond == nil {
return fmt.Sprintf(" %s: Condition not available\n", label)
}

return fmt.Sprintf(" %s\n Status: %s\n Reason: %s\n Condition: %s\n", label, cond.Status, cond.Reason, cond.Message)
}
Loading