Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit test install strategy ALM-227 #132

Merged

Conversation

ecordell
Copy link
Member

  • refactored install strategy bit to separate concerns and make things more testable
  • added a client interface that the install strategy uses
  • added tests for install behavior for when things exist in the cluster and when they don't

@ecordell ecordell changed the title Unit test install strategy Unit test install strategy ALM-227 Oct 30, 2017
@ecordell ecordell removed the request for review from jzelinskie October 30, 2017 15:31
@@ -130,6 +130,7 @@ const (
CSVReasonRequirementsNotMet ConditionReason = "RequirementsNotMet"
CSVReasonRequirementsMet ConditionReason = "AllRequirementsMet"
CSVReasonComponentFailed ConditionReason = "InstallComponentFailed"
CSVReasonInvalidStrategy ConditionReason = "InstallStrategyInvalid"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InstallStraegyInvalid reads a little weird to me. Can we reverse the order on it, i.e. InvalidInstallStrategy?

type InstallStrategyDeploymentInterface interface {
CreateRole(role *v1beta1rbac.Role) (*v1beta1rbac.Role, error)
CreateRoleBinding(roleBinding *v1beta1rbac.RoleBinding) (*v1beta1rbac.RoleBinding, error)
GetOrCreateServiceAccount(serviceAccount *v1.ServiceAccount) (*v1.ServiceAccount, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected behavior of this function isn't very clear from the name. I think simply making it CreateServiceAccount where it's a no-op if it already exists (or EnsureServiceCountExists?) is fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually does return the ServiceAccount it finds if it's already there - it's not a no-op if it already exists.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't look like anything other than the name, which is set in alm/install/deployment and by necessity the same.

Should this really be an upsert, i.e. CreateOrUpdateServiceAccount? I think what's throwing me off is maybe the service account will match what you gave the function, maybe it won't (if it already exists).

CreateRoleBinding(roleBinding *v1beta1rbac.RoleBinding) (*v1beta1rbac.RoleBinding, error)
GetOrCreateServiceAccount(serviceAccount *v1.ServiceAccount) (*v1.ServiceAccount, error)
CreateDeployment(deployment *v1beta1extensions.Deployment) (*v1beta1extensions.Deployment, error)
CheckServiceAccount(serviceAccountName string) (bool, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check what? It might be simpler to just have this be the simple GetServiceAccountByName, otherwise maybe CheckServiceAccountExists or something else to clarify what this means.

GetOrCreateServiceAccount(serviceAccount *v1.ServiceAccount) (*v1.ServiceAccount, error)
CreateDeployment(deployment *v1beta1extensions.Deployment) (*v1beta1extensions.Deployment, error)
CheckServiceAccount(serviceAccountName string) (bool, error)
CheckOwnedDeployments(owner metav1.ObjectMeta, deploymentSpecs []v1beta1extensions.DeploymentSpec) (bool, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. This function weirdly mixes client and consumer concerns. Doing GetOwnedDeployments and having the compare logic in install/deployment.go makes it much easier to understand what's going on.


type InstallStrategyDeploymentClient struct {
opClient opClient.Interface
Namespace string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unclear on how this namespace is used. Are all actions scoped to this namespace? As a consumer of this client, it's very opaque. Either consistently use Namespace and rename this like InstallStrategyDeploymentClientForNamespace or whatever, or (my preference) add namespace string as an argument to each function in the interface that needs it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, all actions are restricted to the namespace. I can make that clear with the name

func (c *InstallStrategyDeploymentClient) CheckOwnedDeployments(owner metav1.ObjectMeta, deploymentSpecs []v1beta1extensions.DeploymentSpec) (bool, error) {
existingDeployments, err := c.opClient.ListDeploymentsWithLabels(c.Namespace, map[string]string{
"alm-owner-name": owner.Name,
"alm-owner-namespace": owner.Namespace,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal pref, I would prefer to have CheckOwnedDeployments(ownerName, ownerNamespace...) or whatever to clarify what it's matching on.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might change this query to user ownerreferences in the future, so I think it makes sense to keep the whole owner

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have some fear still from experiences where parts of the codebase break unexpectedly when you change something about the object because it relied on some obscure field on the object you didn't even know about.

I'll trust your judgement on where we need flexibility more than guaranteeing future stability. 👍


func NewStrategyDeploymentInstaller(opClient opClient.Interface, ownerMeta metav1.ObjectMeta, ownerType metav1.TypeMeta) StrategyInstaller {
return &StrategyDeploymentInstaller{
strategyClient: client.NewInstallStrategyDeploymentClient(opClient, ownerMeta.Namespace),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little awkward, but better separation of concerns if the consumer instantiates and passes along the alm/client instead of opClient; means we can remove operator-client from the entire alm/install package and cleanly pass the client from the ALM operator's instantiation (create the alm/client somewhere after here: https://github.com/ecordell/alm/blob/eb2937fb06723562be5d077b860b70a8368ce367/queueinformer/queueinformer_operator.go#L22)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved it up a level but it's hard to move it much higher than the strategy resolver because each install strategy will need a different client:

func (r *StrategyResolver) InstallerForStrategy(strategyName string, opClient opClient.Interface, ownerMeta metav1.ObjectMeta) StrategyInstaller {
	switch strategyName {
	case InstallStrategyNameDeployment:
		strategyClient := client.NewInstallStrategyDeploymentClient(opClient, ownerMeta.Namespace)
		return NewStrategyDeploymentInstaller(strategyClient, ownerMeta)
	}
       //...
}

When we get to the point of having more than one install strategy type we can revisit refactoring

type StrategyDeploymentInstaller struct {
strategyClient client.InstallStrategyDeploymentInterface
ownerMeta metav1.ObjectMeta
ownerType metav1.TypeMeta
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't from commits in this pr but it looks like ownerType is never used.

ericavonb
ericavonb previously approved these changes Oct 30, 2017
@ecordell ecordell force-pushed the unit-test-install-strategy branch 3 times, most recently from a7ae91a to 86117dd Compare October 31, 2017 12:16
ericavonb
ericavonb previously approved these changes Oct 31, 2017
}
if err == nil {
return foundAccount, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be clearer to do,

if err == nil {
	return foundAccount, nil
}
if !apierrors.IsNotFound(err) {
	return nil, errors.Wrap(err, "checking for existing serviceacccount failed")
}

if err != nil && !apierrors.IsAlreadyExists(err) {
return createdAccount, errors.Wrap(err, "creating serviceacccount failed")
}
return createdAccount, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to re-fetch the serviceaccount if you get a IsAlreadyExists error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, we only need the name to create the rolebinding

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But won't createdAccount be nil? You could return serviceAccount but I still find it unclear about what you're getting back. In the first Get, the return value foundAccount != serviceAccount necessarily. And if the Create is successful, the createdAccount will have additional data on it that serviceAccount does not. I'd rather have consistent behavior, which might mean updating the service account if found.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be nil, but that's the correct value for it for this return. I've changed it explicitly to nil so that it's clear.


// Check deployments
if found, err := i.checkForOwnedDeployments(i.ownerMeta, strategy.DeploymentSpecs); !found {
log.Infof("deployments not found")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.Errorf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to debugf, which fits a bit better I think

@ecordell ecordell force-pushed the unit-test-install-strategy branch 2 times, most recently from 4ecd4c7 to 1bbd0ec Compare October 31, 2017 21:50
if err != nil && !apierrors.IsAlreadyExists(err) {
return createdAccount, errors.Wrap(err, "creating serviceacccount failed")
}
return createdAccount, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But won't createdAccount be nil? You could return serviceAccount but I still find it unclear about what you're getting back. In the first Get, the return value foundAccount != serviceAccount necessarily. And if the Create is successful, the createdAccount will have additional data on it that serviceAccount does not. I'd rather have consistent behavior, which might mean updating the service account if found.

@ecordell ecordell merged commit 3365990 into operator-framework:master Nov 1, 2017
@jzelinskie jzelinskie deleted the unit-test-install-strategy branch November 6, 2017 16:33
njhale pushed a commit to njhale/operator-lifecycle-manager that referenced this pull request Sep 10, 2018
…tall-strategy

Unit test install strategy ALM-227
ecordell added a commit to ecordell/operator-lifecycle-manager that referenced this pull request Mar 8, 2019
…tall-strategy

Unit test install strategy ALM-227
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants