In admin updates, update the Operator version and then update the Cluster object

This commit is contained in:
Amber Brown 2024-04-23 13:41:44 +10:00 коммит произвёл Daniel Holmes
Родитель 699e9fb423
Коммит 309e84bbe1
8 изменённых файлов: 340 добавлений и 48 удалений

Просмотреть файл

@ -106,9 +106,10 @@ const (
// Maintenance tasks that perform work on the cluster
//
MaintenanceTaskEverything MaintenanceTask = "Everything"
MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate"
MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal"
MaintenanceTaskEverything MaintenanceTask = "Everything"
MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate"
MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal"
MaintenanceTaskSyncClusterObject MaintenanceTask = "SyncClusterObject"
//
// Maintenance tasks for updating customer maintenance signals

Просмотреть файл

@ -202,9 +202,10 @@ const (
// Maintenance tasks that perform work on the cluster
//
MaintenanceTaskEverything MaintenanceTask = "Everything"
MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate"
MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal"
MaintenanceTaskEverything MaintenanceTask = "Everything"
MaintenanceTaskOperator MaintenanceTask = "OperatorUpdate"
MaintenanceTaskRenewCerts MaintenanceTask = "CertificatesRenewal"
MaintenanceTaskSyncClusterObject MaintenanceTask = "SyncClusterObject"
//
// Maintenance tasks for updating customer maintenance signals

Просмотреть файл

@ -77,6 +77,14 @@ func TestAdminUpdateSteps(t *testing.T) {
"[Action ensureAROOperator]",
"[Condition aroDeploymentReady, timeout 20m0s]",
"[Condition ensureAROOperatorRunningDesiredVersion, timeout 5m0s]",
"[Action syncClusterObject]",
}
syncClusterObjectSteps := []string{
"[Action startVMs]",
"[Condition apiServersReady, timeout 30m0s]",
"[Action initializeOperatorDeployer]",
"[Action syncClusterObject]",
}
hiveSteps := []string{
@ -188,6 +196,16 @@ func TestAdminUpdateSteps(t *testing.T) {
},
shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps, certificateRenewalSteps),
},
{
name: "SyncClusterObject steps",
fixture: func() (*api.OpenShiftClusterDocument, bool) {
doc := baseClusterDoc()
doc.OpenShiftCluster.Properties.ProvisioningState = api.ProvisioningStateAdminUpdating
doc.OpenShiftCluster.Properties.MaintenanceTask = api.MaintenanceTaskSyncClusterObject
return doc, true
},
shouldRunSteps: utilgenerics.ConcatMultipleSlices(zerothSteps, syncClusterObjectSteps),
},
{
name: "adminUpdate() does not adopt Hive-created clusters",
fixture: func() (*api.OpenShiftClusterDocument, bool) {

Просмотреть файл

@ -5,6 +5,7 @@ package cluster
import (
"context"
"fmt"
cloudcredentialv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -32,9 +33,25 @@ func (m *manager) ensureAROOperator(ctx context.Context) error {
return nil
}
err := m.aroOperatorDeployer.CreateOrUpdate(ctx)
err := m.aroOperatorDeployer.Update(ctx)
if err != nil {
m.log.Errorf("cannot ensureAROOperator.CreateOrUpdate: %s", err.Error())
m.log.Error(fmt.Errorf("cannot ensureAROOperator.Update: %w", err))
}
return err
}
func (m *manager) installAROOperator(ctx context.Context) error {
err := m.aroOperatorDeployer.Install(ctx)
if err != nil {
m.log.Error(fmt.Errorf("cannot installAROOperator.Install: %w", err))
}
return err
}
func (m *manager) syncClusterObject(ctx context.Context) error {
err := m.aroOperatorDeployer.SyncClusterObject(ctx)
if err != nil {
m.log.Error(fmt.Errorf("cannot ensureAROOperator.SyncClusterObject: %w", err))
}
return err
}

Просмотреть файл

@ -31,7 +31,7 @@ func TestEnsureAROOperator(t *testing.T) {
wantErr string
}{
{
name: "create/update success",
name: "update success",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
@ -55,12 +55,12 @@ func TestEnsureAROOperator(t *testing.T) {
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
CreateOrUpdate(gomock.Any()).
Update(gomock.Any()).
Return(nil)
},
},
{
name: "create/update failure",
name: "update failure",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
@ -84,7 +84,7 @@ func TestEnsureAROOperator(t *testing.T) {
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
CreateOrUpdate(gomock.Any()).
Update(gomock.Any()).
Return(errors.New("Mock return: CreateFailed"))
},
@ -127,6 +127,181 @@ func TestEnsureAROOperator(t *testing.T) {
}
}
func TestInstallAROOperator(t *testing.T) {
ctx := context.Background()
const (
key = "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName1"
)
for _, tt := range []struct {
name string
doc *api.OpenShiftClusterDocument
mocks func(*mock_deploy.MockOperator)
wantErr string
}{
{
name: "install success",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
ID: key,
Properties: api.OpenShiftClusterProperties{
IngressProfiles: []api.IngressProfile{
{
Visibility: api.VisibilityPublic,
Name: "default",
},
},
ProvisioningState: api.ProvisioningStateAdminUpdating,
ClusterProfile: api.ClusterProfile{
Version: "4.8.18",
},
NetworkProfile: api.NetworkProfile{
APIServerPrivateEndpointIP: "1.2.3.4",
},
},
},
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
Install(gomock.Any()).
Return(nil)
},
},
{
name: "install failure",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
ID: key,
Properties: api.OpenShiftClusterProperties{
IngressProfiles: []api.IngressProfile{
{
Visibility: api.VisibilityPublic,
Name: "default",
},
},
ProvisioningState: api.ProvisioningStateAdminUpdating,
ClusterProfile: api.ClusterProfile{
Version: "4.8.18",
},
NetworkProfile: api.NetworkProfile{
APIServerPrivateEndpointIP: "1.2.3.4",
},
},
},
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
Install(gomock.Any()).
Return(errors.New("Mock return: CreateFailed"))
},
wantErr: "Mock return: CreateFailed",
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dep := mock_deploy.NewMockOperator(controller)
if tt.mocks != nil {
tt.mocks(dep)
}
m := &manager{
log: logrus.NewEntry(logrus.StandardLogger()),
doc: tt.doc,
aroOperatorDeployer: dep,
}
err := m.installAROOperator(ctx)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}
func TestSyncClusterObject(t *testing.T) {
ctx := context.Background()
const (
key = "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/resourceGroup/providers/Microsoft.RedHatOpenShift/openShiftClusters/resourceName1"
)
for _, tt := range []struct {
name string
doc *api.OpenShiftClusterDocument
mocks func(*mock_deploy.MockOperator)
wantErr string
}{
{
name: "sync object",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
ID: key,
Properties: api.OpenShiftClusterProperties{
IngressProfiles: []api.IngressProfile{
{
Visibility: api.VisibilityPublic,
Name: "default",
},
},
},
},
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
SyncClusterObject(gomock.Any()).
Return(nil)
},
},
{
name: "sync object failure",
doc: &api.OpenShiftClusterDocument{
Key: strings.ToLower(key),
OpenShiftCluster: &api.OpenShiftCluster{
ID: key,
Properties: api.OpenShiftClusterProperties{
IngressProfiles: []api.IngressProfile{
{
Visibility: api.VisibilityPublic,
Name: "default",
},
},
},
},
},
mocks: func(dep *mock_deploy.MockOperator) {
dep.EXPECT().
SyncClusterObject(gomock.Any()).
Return(errors.New("Mock return: DeployFailed"))
},
wantErr: "Mock return: DeployFailed",
},
} {
t.Run(tt.name, func(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()
dep := mock_deploy.NewMockOperator(controller)
if tt.mocks != nil {
tt.mocks(dep)
}
m := &manager{
log: logrus.NewEntry(logrus.StandardLogger()),
doc: tt.doc,
aroOperatorDeployer: dep,
}
err := m.syncClusterObject(ctx)
utilerror.AssertErrorMessage(t, err, tt.wantErr)
})
}
}
func TestAroDeploymentReady(t *testing.T) {
ctx := context.Background()

Просмотреть файл

@ -48,6 +48,7 @@ func (m *manager) adminUpdate() []steps.Step {
isEverything := task == api.MaintenanceTaskEverything || task == ""
isOperator := task == api.MaintenanceTaskOperator
isRenewCerts := task == api.MaintenanceTaskRenewCerts
isSyncClusterObject := task == api.MaintenanceTaskSyncClusterObject
stepsToRun := m.getZerothSteps()
if isEverything {
@ -66,6 +67,8 @@ func (m *manager) adminUpdate() []steps.Step {
}
} else if isRenewCerts {
stepsToRun = append(stepsToRun, m.getCertificateRenewalSteps()...)
} else if isSyncClusterObject {
stepsToRun = append(stepsToRun, m.getSyncClusterObjectSteps()...)
}
// We don't run this on an operator-only deploy as PUCM scripts then cannot
@ -158,6 +161,24 @@ func (m *manager) getOperatorUpdateSteps() []steps.Step {
// The following are dependent on initializeOperatorDeployer.
steps.Condition(m.aroDeploymentReady, 20*time.Minute, true),
steps.Condition(m.ensureAROOperatorRunningDesiredVersion, 5*time.Minute, true),
// Once the ARO Operator is updated, synchronize the Cluster object. This is
// done after the ARO Operator is potentially updated so that any flag
// changes that happen in the same request only apply on the new Operator.
// Otherwise, it is possible for a flag change to occur on the old Operator
// version, then require reconciling to a new version a second time (e.g.
// DNSMasq changes) with the associated node cyclings for the resource
// updates.
steps.Action(m.syncClusterObject),
}
return utilgenerics.ConcatMultipleSlices(m.getEnsureAPIServerReadySteps(), steps)
}
func (m *manager) getSyncClusterObjectSteps() []steps.Step {
steps := []steps.Step{
steps.Action(m.initializeOperatorDeployer),
steps.Action(m.syncClusterObject),
}
return utilgenerics.ConcatMultipleSlices(m.getEnsureAPIServerReadySteps(), steps)
}
@ -341,7 +362,7 @@ func (m *manager) bootstrap() []steps.Step {
steps.Action(m.initializeKubernetesClients),
steps.Action(m.initializeOperatorDeployer), // depends on kube clients
steps.Condition(m.apiServersReady, 30*time.Minute, true),
steps.Action(m.ensureAROOperator),
steps.Action(m.installAROOperator),
steps.Action(m.incrInstallPhase),
)

Просмотреть файл

@ -51,13 +51,15 @@ import (
var embeddedFiles embed.FS
type Operator interface {
CreateOrUpdate(context.Context) error
Install(context.Context) error
Update(context.Context) error
CreateOrUpdateCredentialsRequest(context.Context) error
IsReady(context.Context) (bool, error)
Restart(context.Context, []string) error
IsRunningDesiredVersion(context.Context) (bool, error)
RenewMDSDCertificate(context.Context) error
EnsureUpgradeAnnotation(context.Context) error
SyncClusterObject(context.Context) error
}
type operator struct {
@ -188,7 +190,6 @@ func (o *operator) createObjects(ctx context.Context) ([]kruntime.Object, error)
func (o *operator) resources(ctx context.Context) ([]kruntime.Object, error) {
// first static resources from Assets
results, err := o.createObjects(ctx)
if err != nil {
return nil, err
@ -211,6 +212,24 @@ func (o *operator) resources(ctx context.Context) ([]kruntime.Object, error) {
return nil, err
}
// create a secret here for genevalogging, later we will copy it to
// the genevalogging namespace.
return append(results,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: pkgoperator.SecretName,
Namespace: pkgoperator.Namespace,
},
Data: map[string][]byte{
genevalogging.GenevaCertName: gcsCertBytes,
genevalogging.GenevaKeyName: gcsKeyBytes,
corev1.DockerConfigJsonKey: []byte(ps),
},
},
), nil
}
func (o *operator) clusterObject() (*arov1alpha1.Cluster, error) {
vnetID, _, err := apisubnet.Split(o.oc.Properties.MasterProfile.SubnetID)
if err != nil {
return nil, err
@ -281,32 +300,44 @@ func (o *operator) resources(ctx context.Context) ([]kruntime.Object, error) {
// covers the case of an admin-disable, we need to update dnsmasq on each node
cluster.Spec.GatewayDomains = make([]string, 0)
}
// create a secret here for genevalogging, later we will copy it to
// the genevalogging namespace.
return append(results,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: pkgoperator.SecretName,
Namespace: pkgoperator.Namespace,
},
Data: map[string][]byte{
genevalogging.GenevaCertName: gcsCertBytes,
genevalogging.GenevaKeyName: gcsKeyBytes,
corev1.DockerConfigJsonKey: []byte(ps),
},
},
cluster,
), nil
return cluster, nil
}
func (o *operator) CreateOrUpdate(ctx context.Context) error {
func (o *operator) SyncClusterObject(ctx context.Context) error {
resource, err := o.clusterObject()
if err != nil {
return err
}
return o.dh.Ensure(ctx, resource)
}
func (o *operator) Install(ctx context.Context) error {
resources, err := o.resources(ctx)
if err != nil {
return err
}
err = dynamichelper.Prepare(resources)
// If we're installing the Operator for the first time, include the Cluster
// object, otherwise it is updated separately
cluster, err := o.clusterObject()
if err != nil {
return err
}
resources = append(resources, cluster)
return o.applyDeployment(ctx, resources)
}
func (o *operator) Update(ctx context.Context) error {
resources, err := o.resources(ctx)
if err != nil {
return err
}
return o.applyDeployment(ctx, resources)
}
func (o *operator) applyDeployment(ctx context.Context, resources []kruntime.Object) error {
err := dynamichelper.Prepare(resources)
if err != nil {
return err
}

Просмотреть файл

@ -34,20 +34,6 @@ func (m *MockOperator) EXPECT() *MockOperatorMockRecorder {
return m.recorder
}
// CreateOrUpdate mocks base method.
func (m *MockOperator) CreateOrUpdate(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateOrUpdate", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CreateOrUpdate indicates an expected call of CreateOrUpdate.
func (mr *MockOperatorMockRecorder) CreateOrUpdate(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockOperator)(nil).CreateOrUpdate), arg0)
}
// CreateOrUpdateCredentialsRequest mocks base method.
func (m *MockOperator) CreateOrUpdateCredentialsRequest(arg0 context.Context) error {
m.ctrl.T.Helper()
@ -76,6 +62,20 @@ func (mr *MockOperatorMockRecorder) EnsureUpgradeAnnotation(arg0 interface{}) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureUpgradeAnnotation", reflect.TypeOf((*MockOperator)(nil).EnsureUpgradeAnnotation), arg0)
}
// Install mocks base method.
func (m *MockOperator) Install(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Install", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Install indicates an expected call of Install.
func (mr *MockOperatorMockRecorder) Install(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Install", reflect.TypeOf((*MockOperator)(nil).Install), arg0)
}
// IsReady mocks base method.
func (m *MockOperator) IsReady(arg0 context.Context) (bool, error) {
m.ctrl.T.Helper()
@ -133,3 +133,31 @@ func (mr *MockOperatorMockRecorder) Restart(arg0, arg1 interface{}) *gomock.Call
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Restart", reflect.TypeOf((*MockOperator)(nil).Restart), arg0, arg1)
}
// SyncClusterObject mocks base method.
func (m *MockOperator) SyncClusterObject(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SyncClusterObject", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SyncClusterObject indicates an expected call of SyncClusterObject.
func (mr *MockOperatorMockRecorder) SyncClusterObject(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncClusterObject", reflect.TypeOf((*MockOperator)(nil).SyncClusterObject), arg0)
}
// Update mocks base method.
func (m *MockOperator) Update(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// Update indicates an expected call of Update.
func (mr *MockOperatorMockRecorder) Update(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOperator)(nil).Update), arg0)
}