Skip to content

Commit

Permalink
rebase on an early commit of #250 and implement e2e tests
Browse files Browse the repository at this point in the history
Signed-off-by: Bryce Palmer <bpalmer@redhat.com>
  • Loading branch information
everettraven committed Jun 2, 2023
1 parent 18046bc commit a49efb5
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 169 deletions.
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ kind-cluster-cleanup: kind ## Delete the kind cluster

kind-load-test-artifacts: kind ## Load the e2e testdata container images into a kind cluster
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/registry-v1/prometheus-operator.v0.47.0 -t localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/bundles/plain-v0/plain.v0.1.0 -t localhost/testdata/bundles/plain-v0/plain:v0.1.0
$(CONTAINER_RUNTIME) build $(TESTDATA_DIR)/catalogs -f $(TESTDATA_DIR)/catalogs/test-catalog.Dockerfile -t localhost/testdata/catalogs/test-catalog:e2e
$(KIND) load docker-image localhost/testdata/bundles/registry-v1/prometheus-operator:v0.47.0 --name $(KIND_CLUSTER_NAME)
$(KIND) load docker-image localhost/testdata/bundles/plain-v0/plain:v0.1.0 --name $(KIND_CLUSTER_NAME)
$(KIND) load docker-image localhost/testdata/catalogs/test-catalog:e2e --name $(KIND_CLUSTER_NAME)

##@ Build
Expand Down Expand Up @@ -195,7 +197,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.10.0
.PHONY: kind
kind: $(KIND) ## Download kind locally if necessary.
$(KIND): $(LOCALBIN)
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kind@v0.15.0
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kind@v0.19.0

.PHONY: ginkgo
ginkgo: $(GINKGO) ## Download ginkgo locally if necessary.
Expand Down
116 changes: 116 additions & 0 deletions internal/controllers/operator_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,108 @@ var _ = Describe("Operator Controller Test", func() {
err := cl.Delete(ctx, operator)
Expect(err).To(Not(HaveOccurred()))
})
When("the operator specifies a package with a plain+v0 bundle", func() {
var pkgName string
var pkgVer string
var pkgChan string
BeforeEach(func() {
By("initializing cluster state")
pkgName = "plain"
pkgVer = "0.1.0"
pkgChan = "beta"
operator = &operatorsv1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
Spec: operatorsv1alpha1.OperatorSpec{
PackageName: pkgName,
Version: pkgVer,
Channel: pkgChan,
},
}
err := cl.Create(ctx, operator)
Expect(err).NotTo(HaveOccurred())
})
It("sets resolution success status", func() {
By("running reconcile")
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
Expect(res).To(Equal(ctrl.Result{}))
Expect(err).NotTo(HaveOccurred())

By("fetching updated operator after reconcile")
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())

By("Checking the status fields")
Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/plain@sha256:plain"))
Expect(operator.Status.InstalledBundleResource).To(Equal(""))

By("checking the expected conditions")
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
Expect(cond).NotTo(BeNil())
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess))
Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/plain@sha256:plain\""))
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled)
Expect(cond).NotTo(BeNil())
Expect(cond.Status).To(Equal(metav1.ConditionUnknown))
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationStatusUnknown))
Expect(cond.Message).To(Equal("bundledeployment status is unknown"))

By("fetching the bundled deployment")
bd := &rukpakv1alpha1.BundleDeployment{}
Expect(cl.Get(ctx, types.NamespacedName{Name: opKey.Name}, bd)).NotTo(HaveOccurred())
Expect(bd.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain"))
Expect(bd.Spec.Template.Spec.ProvisionerClassName).To(Equal("core-rukpak-io-plain"))
Expect(bd.Spec.Template.Spec.Source.Type).To(Equal(rukpakv1alpha1.SourceTypeImage))
Expect(bd.Spec.Template.Spec.Source.Image).NotTo(BeNil())
Expect(bd.Spec.Template.Spec.Source.Image.Ref).To(Equal("quay.io/operatorhub/plain@sha256:plain"))
})
})
When("the operator specifies a package with a bade bundle mediatype", func() {
var pkgName string
var pkgVer string
var pkgChan string
BeforeEach(func() {
By("initializing cluster state")
pkgName = "badmedia"
pkgVer = "0.1.0"
pkgChan = "beta"
operator = &operatorsv1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{Name: opKey.Name},
Spec: operatorsv1alpha1.OperatorSpec{
PackageName: pkgName,
Version: pkgVer,
Channel: pkgChan,
},
}
err := cl.Create(ctx, operator)
Expect(err).NotTo(HaveOccurred())
})
It("sets resolution success status", func() {
By("running reconcile")
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
Expect(res).To(Equal(ctrl.Result{}))
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("unknown bundle mediatype: badmedia+v1"))

By("fetching updated operator after reconcile")
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())

By("Checking the status fields")
Expect(operator.Status.ResolvedBundleResource).To(Equal("quay.io/operatorhub/badmedia@sha256:badmedia"))
Expect(operator.Status.InstalledBundleResource).To(Equal(""))

By("checking the expected conditions")
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeResolved)
Expect(cond).NotTo(BeNil())
Expect(cond.Status).To(Equal(metav1.ConditionTrue))
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonSuccess))
Expect(cond.Message).To(Equal("resolved to \"quay.io/operatorhub/badmedia@sha256:badmedia\""))
cond = apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeInstalled)
Expect(cond).NotTo(BeNil())
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonInstallationFailed))
Expect(cond.Message).To(Equal("unknown bundle mediatype: badmedia+v1"))
})
})
})
When("an invalid semver is provided that bypasses the regex validation", func() {
var (
Expand Down Expand Up @@ -1061,4 +1163,18 @@ var testEntitySource = input.NewCacheQuerier(map[deppy.Identifier]input.Entity{
"olm.package": `{"packageName":"badimage","version":"0.1.0"}`,
"olm.gvk": `[]`,
}),
"operatorhub/plain/0.1.0": *input.NewEntity("operatorhub/plain/0.1.0", map[string]string{
"olm.bundle.path": `"quay.io/operatorhub/plain@sha256:plain"`,
"olm.channel": `{"channelName":"beta","priority":0}`,
"olm.package": `{"packageName":"plain","version":"0.1.0"}`,
"olm.gvk": `[]`,
"olm.bundle.mediatype": `"plain+v0"`,
}),
"operatorhub/badmedia/0.1.0": *input.NewEntity("operatorhub/badmedia/0.1.0", map[string]string{
"olm.bundle.path": `"quay.io/operatorhub/badmedia@sha256:badmedia"`,
"olm.channel": `{"channelName":"beta","priority":0}`,
"olm.package": `{"packageName":"badmedia","version":"0.1.0"}`,
"olm.gvk": `[]`,
"olm.bundle.mediatype": `"badmedia+v1"`,
}),
})
3 changes: 3 additions & 0 deletions internal/resolution/entitysources/catalogdsource.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/operator-framework/deppy/pkg/deppy"
"github.com/operator-framework/deppy/pkg/deppy/input"
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
"github.com/operator-framework/operator-registry/alpha/property"
"sigs.k8s.io/controller-runtime/pkg/client"

Expand Down Expand Up @@ -86,6 +87,8 @@ func getEntities(ctx context.Context, client client.Client) (input.EntityList, e
// this is already a json marshalled object, so it doesn't need to be marshalled
// like the other ones
props[property.TypePackage] = string(prop.Value)
case entity.PropertyBundleMediaType:
props[entity.PropertyBundleMediaType] = string(prop.Value)
}
}

Expand Down
178 changes: 128 additions & 50 deletions test/e2e/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

const (
defaultTimeout = 30 * time.Second
defaultTimeout = 120 * time.Second
defaultPoll = 1 * time.Second
testCatalogRef = "localhost/testdata/catalogs/test-catalog:e2e"
)
Expand All @@ -27,25 +27,17 @@ var _ = Describe("Operator Install", func() {
ctx context.Context
pkgName string
operatorName string
catalogName string
operator *operatorv1alpha1.Operator
operatorCatalog *catalogd.Catalog
)
When("An operator is installed from an operator catalog", func() {
BeforeEach(func() {
ctx = context.Background()
pkgName = "prometheus"
operatorName = fmt.Sprintf("operator-%s", rand.String(8))
operator = &operatorv1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: operatorName,
},
Spec: operatorv1alpha1.OperatorSpec{
PackageName: pkgName,
},
}
catalogName = fmt.Sprintf("catalog-%s", rand.String(8))
operatorCatalog = &catalogd.Catalog{
ObjectMeta: metav1.ObjectMeta{
Name: "test-catalog",
Name: catalogName,
},
Spec: catalogd.CatalogSpec{
Source: catalogd.CatalogSource{
Expand All @@ -59,54 +51,140 @@ var _ = Describe("Operator Install", func() {
err := c.Create(ctx, operatorCatalog)
Expect(err).ToNot(HaveOccurred())
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: "test-catalog"}, operatorCatalog)
err = c.Get(ctx, types.NamespacedName{Name: catalogName}, operatorCatalog)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(operatorCatalog.Status.Conditions)).To(Equal(1))
g.Expect(operatorCatalog.Status.Conditions[0].Message).To(ContainSubstring("successfully unpacked the catalog image"))
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
})
It("resolves the specified package with correct bundle path", func() {
By("creating the Operator resource")
err := c.Create(ctx, operator)
Expect(err).ToNot(HaveOccurred())

By("eventually reporting a successful resolution and bundle path")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(operator.Status.Conditions)).To(Equal(2))
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
cond := apimeta.FindStatusCondition(operatorCatalog.Status.Conditions, catalogd.TypeUnpacked)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("resolved to"))
g.Expect(operator.Status.ResolvedBundleResource).ToNot(BeEmpty())
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
g.Expect(cond.Message).To(ContainSubstring("successfully unpacked the catalog image"))

By("eventually installing the package successfully")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeInstalled)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("installed from"))
g.Expect(operator.Status.InstalledBundleResource).ToNot(BeEmpty())
bd := rukpakv1alpha1.BundleDeployment{}
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &bd)
// For some reason the above condition check is returning true and the
// Operators end up being created before the packages exist. Adding this check
// to ensure that there are some packages that exist before actually returning from this
// check.
pkgList := &catalogd.PackageList{}
err = c.List(ctx, pkgList)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(bd.Status.Conditions)).To(Equal(2))
g.Expect(bd.Status.Conditions[0].Reason).To(Equal("UnpackSuccessful"))
g.Expect(bd.Status.Conditions[1].Reason).To(Equal("InstallationSucceeded"))
g.Expect(pkgList.Items).To(HaveLen(2))
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())

})

AfterEach(func() {
err := c.Delete(ctx, operatorCatalog)
Expect(err).ToNot(HaveOccurred())
err = c.Delete(ctx, operator)
Expect(err).ToNot(HaveOccurred())
})

When("the operator bundle format is registry+v1", func() {
BeforeEach(func() {
pkgName = "prometheus"
operatorName = fmt.Sprintf("operator-%s", rand.String(8))
operator = &operatorv1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: operatorName,
},
Spec: operatorv1alpha1.OperatorSpec{
PackageName: pkgName,
},
}
})
It("resolves the specified package with correct bundle path", func() {
By("creating the Operator resource")
err := c.Create(ctx, operator)
Expect(err).ToNot(HaveOccurred())

By("eventually reporting a successful resolution and bundle path")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(operator.Status.Conditions)).To(Equal(2))
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("resolved to"))
g.Expect(operator.Status.ResolvedBundleResource).ToNot(BeEmpty())
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())

By("eventually installing the package successfully")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeInstalled)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("installed from"))
g.Expect(operator.Status.InstalledBundleResource).ToNot(BeEmpty())
bd := rukpakv1alpha1.BundleDeployment{}
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &bd)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(bd.Status.Conditions)).To(Equal(2))
g.Expect(bd.Status.Conditions[0].Reason).To(Equal("UnpackSuccessful"))
g.Expect(bd.Status.Conditions[1].Reason).To(Equal("InstallationSucceeded"))
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())

})
AfterEach(func() {
err := c.Delete(ctx, operator)
Expect(err).ToNot(HaveOccurred())
})
})

When("the operator bundle format is plain+v0", func() {
BeforeEach(func() {
pkgName = "plain"
operatorName = fmt.Sprintf("operator-%s", rand.String(8))
operator = &operatorv1alpha1.Operator{
ObjectMeta: metav1.ObjectMeta{
Name: operatorName,
},
Spec: operatorv1alpha1.OperatorSpec{
PackageName: pkgName,
},
}
})
It("resolves the specified package with correct bundle path", func() {
By("creating the Operator resource")
err := c.Create(ctx, operator)
Expect(err).ToNot(HaveOccurred())

By("eventually reporting a successful resolution and bundle path")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(operator.Status.Conditions)).To(Equal(2))
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("resolved to"))
g.Expect(operator.Status.ResolvedBundleResource).ToNot(BeEmpty())
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())

By("eventually installing the package successfully")
Eventually(func(g Gomega) {
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
g.Expect(err).ToNot(HaveOccurred())
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeInstalled)
g.Expect(cond).ToNot(BeNil())
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
g.Expect(cond.Message).To(ContainSubstring("installed from"))
g.Expect(operator.Status.InstalledBundleResource).ToNot(BeEmpty())
bd := rukpakv1alpha1.BundleDeployment{}
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &bd)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(len(bd.Status.Conditions)).To(Equal(2))
g.Expect(bd.Status.Conditions[0].Reason).To(Equal("UnpackSuccessful"))
g.Expect(bd.Status.Conditions[1].Reason).To(Equal("InstallationSucceeded"))
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())

})
AfterEach(func() {
err := c.Delete(ctx, operator)
Expect(err).ToNot(HaveOccurred())
})
})

})
})
15 changes: 1 addition & 14 deletions testdata/catalogs/test-catalog.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,2 @@
# The base image is expected to contain
# /bin/opm (with a serve subcommand) and /bin/grpc_health_probe
FROM quay.io/operator-framework/opm:latest

# Configure the entrypoint and command
ENTRYPOINT ["/bin/opm"]
CMD ["serve", "/configs", "--cache-dir=/tmp/cache"]

# Copy declarative config root into image at /configs and pre-populate serve cache
FROM scratch
ADD test-catalog /configs
RUN ["/bin/opm", "serve", "/configs", "--cache-dir=/tmp/cache", "--cache-only"]

# Set DC-specific label for the location of the DC root directory
# in the image
LABEL operators.operatorframework.io.index.configs.v1=/configs
Loading

0 comments on commit a49efb5

Please sign in to comment.