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

Subscribe to Single Namespace Operator #1172

Merged
merged 1 commit into from
Feb 22, 2019
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
3 changes: 3 additions & 0 deletions frontend/__mocks__/k8sResourcesMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const testClusterServiceVersion: ClusterServiceVersionKind = {
'alm-owner-testapp': 'testapp.clusterserviceversions.operators.coreos.com.v1alpha1',
},
},
installModes: [],
install: {
strategy: 'Deployment',
spec: {
Expand Down Expand Up @@ -129,6 +130,7 @@ export const localClusterServiceVersion: ClusterServiceVersionKind = {
'alm-owner-local-testapp': 'local-testapp.clusterserviceversions.operators.coreos.com.v1alpha1',
},
},
installModes: [],
install: {
strategy: 'Deployment',
spec: {
Expand Down Expand Up @@ -268,6 +270,7 @@ export const testPackageManifest: PackageManifestKind = {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}],
defaultChannel: 'alpha',
Expand Down
6 changes: 6 additions & 0 deletions frontend/__mocks__/operatorHubItemsMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const amqPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"Kafka","metadata":{"name":"my-cluster"},"spec":{"kafka":{"replicas":3,"listeners":{"plain":{},"tls":{}},"config":{"offsets.topic.replication.factor":3,"transaction.state.log.replication.factor":3,"transaction.state.log.min.isr":2},"storage":{"type":"ephemeral"}},"zookeeper":{"replicas":3,"storage":{"type":"ephemeral"}},"entityOperator":{"topicOperator":{},"userOperator":{}}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaConnect","metadata":{"name":"my-connect-cluster"},"spec":{"replicas":1,"bootstrapServers":"my-cluster-kafka-bootstrap:9093","tls":{"trustedCertificates":[{"secretName":"my-cluster-cluster-ca-cert","certificate":"ca.crt"}]}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaConnectS2I","metadata":{"name":"my-connect-cluster"},"spec":{"replicas":1,"bootstrapServers":"my-cluster-kafka-bootstrap:9093","tls":{"trustedCertificates":[{"secretName":"my-cluster-cluster-ca-cert","certificate":"ca.crt"}]}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaTopic","metadata":{"name":"my-topic","labels":{"strimzi.io/cluster":"my-cluster"}},"spec":{"partitions":10,"replicas":3,"config":{"retention.ms":604800000,"segment.bytes":1073741824}}}, {"apiVersion":"kafka.strimzi.io/v1alpha1","kind":"KafkaUser","metadata":{"name":"my-user","labels":{"strimzi.io/cluster":"my-cluster"}},"spec":{"authentication":{"type":"tls"},"authorization":{"type":"simple","acls":[{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Read","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Describe","host":"*"},{"resource":{"type":"group","name":"my-group","patternType":"literal"},"operation":"Read","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Write","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Create","host":"*"},{"resource":{"type":"topic","name":"my-topic","patternType":"literal"},"operation":"Describe","host":"*"}]}}}]',
description: '**Red Hat AMQ Streams** is a massively scalable, distributed, and high performance data streaming platform based on the Apache Kafka project. \nAMQ Streams provides an event streaming backbone that allows microservices and other application components to exchange data with extremely high throughput and low latency.\n\n**The core capabilities include**\n* A pub/sub messaging model, similar to a traditional enterprise messaging system, in which application components publish and consume events to/from an ordered stream\n* The long term, fault-tolerant storage of events\n* The ability for a consumer to replay streams of events\n* The ability to partition topics for horizontal scalability\n\n# Before you start\n\n1. Create AMQ Streams Cluster Roles\n```\n$ oc apply -f http://amq.io/amqstreams/rbac.yaml\n```\n2. Create following bindings\n```\n$ oc adm policy add-cluster-role-to-user strimzi-cluster-operator -z strimzi-cluster-operator --namespace <namespace>\n$ oc adm policy add-cluster-role-to-user strimzi-kafka-broker -z strimzi-cluster-operator --namespace <namespace>\n```',
Expand Down Expand Up @@ -89,6 +90,7 @@ const etcdPackageManifest = {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdCluster","metadata":{"name":"example","namespace":"default"},"spec":{"size":3,"version":"3.2.13"}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdRestore","metadata":{"name":"example-etcd-cluster"},"spec":{"etcdCluster":{"name":"example-etcd-cluster"},"backupStorageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}},{"apiVersion":"etcd.database.coreos.com/v1beta2","kind":"EtcdBackup","metadata":{"name":"example-etcd-cluster-backup"},"spec":{"etcdEndpoints":["<etcd-cluster-endpoints>"],"storageType":"S3","s3":{"path":"<full-s3-path>","awsSecret":"<aws-secret>"}}}]',
'tectonic-visibility': 'ocs',
Expand Down Expand Up @@ -136,6 +138,7 @@ const federationv2PackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
description: 'Kubernetes Federation V2 namespace-scoped installation',
categories: '',
Expand Down Expand Up @@ -184,6 +187,7 @@ const prometheusPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
'alm-examples': '[{"apiVersion":"monitoring.coreos.com/v1","kind":"Prometheus","metadata":{"name":"example","labels":{"prometheus":"k8s"}},"spec":{"replicas":2,"version":"v2.3.2","serviceAccountName":"prometheus-k8s","securityContext": {}, "serviceMonitorSelector":{"matchExpressions":[{"key":"k8s-app","operator":"Exists"}]},"ruleSelector":{"matchLabels":{"role":"prometheus-rulefiles","prometheus":"k8s"}},"alerting":{"alertmanagers":[{"namespace":"monitoring","name":"alertmanager-main","port":"web"}]}}},{"apiVersion":"monitoring.coreos.com/v1","kind":"ServiceMonitor","metadata":{"name":"example","labels":{"k8s-app":"prometheus"}},"spec":{"selector":{"matchLabels":{"k8s-app":"prometheus"}},"endpoints":[{"port":"web","interval":"30s"}]}},{"apiVersion":"monitoring.coreos.com/v1","kind":"Alertmanager","metadata":{"name":"alertmanager-main"},"spec":{"replicas":3, "securityContext": {}}}]',
description: 'The Prometheus Operator for Kubernetes provides easy monitoring definitions for Kubernetes services and deployment and management of Prometheus instances.',
Expand Down Expand Up @@ -230,6 +234,7 @@ const svcatPackageManifest = {
provider: {
name: 'Red Hat',
},
installModes: [],
annotations: {
description: 'Service Catalog lets you provision cloud services directly from the comfort of native Kubernetes tooling.',
categories: 'catalog',
Expand Down Expand Up @@ -276,6 +281,7 @@ const dummyPackageManifest = {
provider: {
name: 'Dummy',
},
installModes: [],
annotations: {
description: 'Dummy is not a real operator',
categories: 'dummy',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ describe(SubscriptionChannelModal.name, () => {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}, {
name: 'nightly',
Expand All @@ -48,6 +49,7 @@ describe(SubscriptionChannelModal.name, () => {
provider: {
name: 'CoreOS, Inc',
},
installModes: [],
},
}];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* eslint-disable no-undef, no-unused-vars */

import * as React from 'react';
import * as _ from 'lodash-es';
import { shallow } from 'enzyme';

import { requireOperatorGroup, NoOperatorGroupMsg } from '../../../public/components/operator-lifecycle-manager/operator-group';
import { testOperatorGroup } from '../../../__mocks__/k8sResourcesMocks';
import { requireOperatorGroup, NoOperatorGroupMsg, supports, InstallModeSet, InstallModeType, installedFor } from '../../../public/components/operator-lifecycle-manager/operator-group';
import { OperatorGroupKind, SubscriptionKind } from '../../../public/components/operator-lifecycle-manager';
import { testOperatorGroup, testSubscription } from '../../../__mocks__/k8sResourcesMocks';

describe('requireOperatorGroup', () => {
const SomeComponent = () => <div>Requires OperatorGroup</div>;
Expand Down Expand Up @@ -33,3 +35,111 @@ describe('requireOperatorGroup', () => {
expect(wrapper.find(NoOperatorGroupMsg).exists()).toBe(false);
});
});

describe('installedFor', () => {
const pkgName = testSubscription.spec.name;
const ns = testSubscription.metadata.namespace;
let subscriptions: SubscriptionKind[];
let operatorGroups: OperatorGroupKind[];

beforeEach(() => {
subscriptions = [];
operatorGroups = [];
});

it('returns false if no `Subscriptions` exist for the given package', () => {
subscriptions = [testSubscription];
operatorGroups = [{...testOperatorGroup, status: {namespaces: [ns], lastUpdated: null}}];

expect(installedFor(subscriptions)(operatorGroups)('new-operator')(ns)).toBe(false);
});

it('returns false if no `OperatorGroups` target the given namespace', () => {
subscriptions = [testSubscription];
operatorGroups = [{...testOperatorGroup, status: {namespaces: ['prod-a', 'prod-b'], lastUpdated: null}}];

expect(installedFor(subscriptions)(operatorGroups)(pkgName)(ns)).toBe(false);
});

it('returns false if checking for `all-namespaces`', () => {
subscriptions = [testSubscription];
operatorGroups = [{...testOperatorGroup, status: {namespaces: [ns], lastUpdated: null}}];

expect(installedFor(subscriptions)(operatorGroups)(pkgName)('')).toBe(false);
});

it('returns true if `Subscription` exists in the "global" `OperatorGroup`', () => {
subscriptions = [testSubscription];
operatorGroups = [{...testOperatorGroup, status: {namespaces: [''], lastUpdated: null}}];

expect(installedFor(subscriptions)(operatorGroups)(pkgName)(ns)).toBe(true);
});

it('returns true if `Subscription` exists in an `OperatorGroup` that targets given namespace', () => {
subscriptions = [testSubscription];
operatorGroups = [{...testOperatorGroup, status: {namespaces: [ns], lastUpdated: null}}];

expect(installedFor(subscriptions)(operatorGroups)(pkgName)(ns)).toBe(true);
});
});

describe('supports', () => {
let set: InstallModeSet;
let ownNamespaceGroup: OperatorGroupKind;
let singleNamespaceGroup: OperatorGroupKind;
let multiNamespaceGroup: OperatorGroupKind;
let allNamespacesGroup: OperatorGroupKind;

beforeEach(() => {
ownNamespaceGroup = _.cloneDeep(testOperatorGroup);
ownNamespaceGroup.status = {namespaces: [ownNamespaceGroup.metadata.namespace], lastUpdated: null};
singleNamespaceGroup = _.cloneDeep(testOperatorGroup);
singleNamespaceGroup.status = {namespaces: ['test-ns'], lastUpdated: null};
multiNamespaceGroup = _.cloneDeep(testOperatorGroup);
multiNamespaceGroup.status = {namespaces: ['test-ns', 'default'], lastUpdated: null};
allNamespacesGroup = _.cloneDeep(testOperatorGroup);
allNamespacesGroup.status = {namespaces: [''], lastUpdated: null};
});

it('correctly returns for an Operator that can only run in its own namespace', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: true},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: false},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: false},
];

expect(supports(set)(ownNamespaceGroup)).toBe(true);
expect(supports(set)(singleNamespaceGroup)).toBe(true);
expect(supports(set)(multiNamespaceGroup)).toBe(false);
expect(supports(set)(allNamespacesGroup)).toBe(false);
});

it('correctly returns for an Operator which can run in several namespaces', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: true},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: true},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: false},
];

expect(supports(set)(ownNamespaceGroup)).toBe(true);
expect(supports(set)(singleNamespaceGroup)).toBe(true);
expect(supports(set)(multiNamespaceGroup)).toBe(true);
expect(supports(set)(allNamespacesGroup)).toBe(false);
});

it('correctly returns for an Operator which can only run in all namespaces', () => {
set = [
{type: InstallModeType.InstallModeTypeOwnNamespace, supported: true},
{type: InstallModeType.InstallModeTypeSingleNamespace, supported: false},
{type: InstallModeType.InstallModeTypeMultiNamespace, supported: false},
{type: InstallModeType.InstallModeTypeAllNamespaces, supported: true},
];

expect(supports(set)(ownNamespaceGroup)).toBe(false);
expect(supports(set)(singleNamespaceGroup)).toBe(false);
expect(supports(set)(multiNamespaceGroup)).toBe(false);
expect(supports(set)(allNamespacesGroup)).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe(PackageManifestRow.displayName, () => {
let wrapper: ShallowWrapper<PackageManifestRowProps>;

beforeEach(() => {
wrapper = shallow(<PackageManifestRow obj={testPackageManifest} catalogSourceNamespace={testCatalogSource.metadata.namespace} catalogSourceName={testCatalogSource.metadata.name} subscription={testSubscription} defaultNS="default" />);
wrapper = shallow(<PackageManifestRow obj={testPackageManifest} catalogSourceNamespace={testCatalogSource.metadata.namespace} catalogSourceName={testCatalogSource.metadata.name} subscription={testSubscription} defaultNS="default" canSubscribe={true} />);
});

it('renders column for package name and logo', () => {
Expand All @@ -58,11 +58,15 @@ describe(PackageManifestRow.displayName, () => {
expect(wrapper.find('.co-resource-list__item').childAt(2).find(Link).at(0).childAt(0).text()).toEqual('View');
});

it('renders button to create new subscription', () => {
wrapper = wrapper.setProps({subscription: null});

it('renders button to create new subscription if `canSubscribe` is true', () => {
expect(wrapper.find('.co-resource-list__item').childAt(2).find('button').text()).toEqual('Create Subscription');
});

it('does not render button to create new subscription if `canSubscribe` is false', () => {
wrapper = wrapper.setProps({canSubscribe: false});

expect(wrapper.find('.co-resource-list__item').childAt(2).find('button').exists()).toBe(false);
});
});

describe(PackageManifestList.displayName, () => {
Expand All @@ -74,7 +78,7 @@ describe(PackageManifestList.displayName, () => {
otherPackageManifest.status.catalogSource = 'another-catalog-source';
otherPackageManifest.status.catalogSourceDisplayName = 'Another Catalog Source';
otherPackageManifest.status.catalogSourcePublisher = 'Some Publisher';
packages = [testPackageManifest, otherPackageManifest];
packages = [otherPackageManifest, testPackageManifest];

wrapper = shallow(<PackageManifestList.WrappedComponent loaded={true} data={packages} operatorGroup={null} subscription={null} />);
});
Expand All @@ -83,7 +87,6 @@ describe(PackageManifestList.displayName, () => {
expect(wrapper.find('.co-catalogsource-list__section').length).toEqual(2);
packages.forEach(({status}, i) => {
expect(wrapper.find('.co-catalogsource-list__section').at(i).find('h3').text()).toEqual(status.catalogSourceDisplayName);
expect(wrapper.find('.co-catalogsource-list__section').at(i).find('h3').text()).toEqual(status.catalogSourceDisplayName);
});
});

Expand Down
3 changes: 2 additions & 1 deletion frontend/integration-tests/protractor.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export const config: Config = {
crud: ['tests/base.scenario.ts', 'tests/crud.scenario.ts', 'tests/secrets.scenario.ts', 'tests/filter.scenario.ts', 'tests/modal-annotations.scenario.ts', 'tests/environment.scenario.ts'],
monitoring: ['tests/base.scenario.ts', 'tests/monitoring.scenario.ts'],
newApp: ['tests/base.scenario.ts', 'tests/overview/overview.scenario.ts', 'tests/source-to-image.scenario.ts', 'tests/deploy-image.scenario.ts'],
olm: ['tests/base.scenario.ts', 'tests/olm/descriptors.scenario.ts', 'tests/olm/catalog.scenario.ts', 'tests/olm/etcd.scenario.ts'],
olm: ['tests/base.scenario.ts', 'tests/olm/descriptors.scenario.ts', 'tests/olm/catalog.scenario.ts', 'tests/olm/prometheus.scenario.ts', 'tests/olm/etcd.scenario.ts'],
olmUpgrade: ['tests/base.scenario.ts', 'tests/olm/update-channel-approval.scenario.ts'],
performance: ['tests/base.scenario.ts', 'tests/performance.scenario.ts'],
serviceCatalog: ['tests/base.scenario.ts', 'tests/service-catalog/service-catalog.scenario.ts', 'tests/service-catalog/service-broker.scenario.ts', 'tests/service-catalog/service-class.scenario.ts', 'tests/service-catalog/service-binding.scenario.ts', 'tests/developer-catalog.scenario.ts'],
Expand All @@ -108,6 +108,7 @@ export const config: Config = {
'tests/olm/descriptors.scenario.ts',
'tests/olm/catalog.scenario.ts',
'tests/operator-hub/operator-hub.scenario.ts',
'tests/olm/prometheus.scenario.ts',
'tests/olm/etcd.scenario.ts'],
all: ['tests/base.scenario.ts',
'tests/crud.scenario.ts',
Expand Down
24 changes: 22 additions & 2 deletions frontend/integration-tests/tests/olm/catalog.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,29 @@ import * as sidenavView from '../../views/sidenav.view';

describe('Installing a service from a Catalog Source', () => {
const openCloudServices = new Set(['etcd', 'Prometheus Operator', 'AMQ Streams', 'Service Catalog', 'FederationV2']);
const operatorGroupName = 'test-operatorgroup';

beforeAll(async() => {
const catalogSource = {
apiVersion: 'operators.coreos.com/v1alpha1',
kind: 'CatalogSource',
metadata: {name: 'console-e2e'},
spec: {
sourceType: 'grpc',
image: 'quay.io/operatorframework/operator-manifests@sha256:ac3140d9f2d2a3cf5446d82048ddf64ad5cd13b31070d1c4b5c689b7272062dc',
displayName: 'Console E2E Operators',
publisher: 'Red Hat, Inc',
},
};
execSync(`echo '${JSON.stringify(catalogSource)}' | kubectl create -n ${testName} -f -`);
// FIXME(alecmerdler): Wait until `PackageManifests` are being served from registry pod
browser.sleep(30000);

const operatorGroup = {
apiVersion: 'operators.coreos.com/v1alpha2',
kind: 'OperatorGroup',
metadata: {name: 'test-operatorgroup'},
spec: {selector: {matchLabels: {'test-name': testName}}},
metadata: {name: operatorGroupName},
spec: {targetNamespaces: [testName]},
};
execSync(`echo '${JSON.stringify(operatorGroup)}' | kubectl create -n ${testName} -f -`);

Expand All @@ -28,6 +44,10 @@ describe('Installing a service from a Catalog Source', () => {
checkErrors();
});

afterAll(() => {
execSync(`kubectl delete operatorgroup -n ${testName} ${operatorGroupName}`);
});

it('displays `Catalog` tab in navigation sidebar', async() => {
await browser.wait(until.presenceOf(sidenavView.navSectionFor('Catalog')));

Expand Down
Loading