Skip to content

Commit

Permalink
Split websocket connections by kind
Browse files Browse the repository at this point in the history
Instead of connecting to a single websocket endpoint to receive
events for all potentially interesting resources on the cluster,
switch to a model where the page connects to a separate websocket
endpoint per kind it wants to watch for updates.

This means that any given page only has to process events for resources
it's actually displaying. It also has the added benefit of allowing us
to provide real-time updates for resources displayed by extensions
which was not supported under the previous model.

The Kubernetes API server supports a `watch` query parameter on 'list'
requests to make a websocket connection instead of the default HTTP
GET request. This is not supported on 'get' requests for a single resource.
To support watching updates for a single resource we use the
`fieldSelector` query param to limit the list request to the single
resource we're interested in via its `metadata.name` field.
  • Loading branch information
AlanGreene authored and tekton-robot committed Aug 9, 2021
1 parent fb63a47 commit 85f8527
Show file tree
Hide file tree
Showing 42 changed files with 715 additions and 336 deletions.
34 changes: 28 additions & 6 deletions src/api/clusterInterceptors.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import {
useResource
} from './utils';

export function getClusterInterceptors({ filters = [] } = {}) {
const uri = getTektonAPI(
function getClusterInterceptorsAPI({ filters, isWebSocket, name }) {
return getTektonAPI(
'clusterinterceptors',
{ group: triggersAPIGroup, version: 'v1alpha1' },
getQueryParams(filters)
{ group: triggersAPIGroup, isWebSocket, version: 'v1alpha1' },
getQueryParams({ filters, name })
);
}

export function getClusterInterceptors({ filters = [] } = {}) {
const uri = getClusterInterceptorsAPI({ filters });
return get(uri).then(checkData);
}

Expand All @@ -40,9 +44,27 @@ export function getClusterInterceptor({ name }) {
}

export function useClusterInterceptors(params) {
return useCollection('ClusterInterceptor', getClusterInterceptors, params);
const webSocketURL = getClusterInterceptorsAPI({
...params,
isWebSocket: true
});
return useCollection({
api: getClusterInterceptors,
kind: 'ClusterInterceptor',
params,
webSocketURL
});
}

export function useClusterInterceptor(params) {
return useResource('ClusterInterceptor', getClusterInterceptor, params);
const webSocketURL = getClusterInterceptorsAPI({
...params,
isWebSocket: true
});
return useResource({
api: getClusterInterceptor,
kind: 'ClusterInterceptor',
params,
webSocketURL
});
}
16 changes: 10 additions & 6 deletions src/api/clusterInterceptors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ it('useClusterInterceptors', () => {
jest.spyOn(utils, 'useCollection').mockImplementation(() => query);
expect(API.useClusterInterceptors(params)).toEqual(query);
expect(utils.useCollection).toHaveBeenCalledWith(
'ClusterInterceptor',
API.getClusterInterceptors,
params
expect.objectContaining({
api: API.getClusterInterceptors,
kind: 'ClusterInterceptor',
params
})
);
});

Expand All @@ -54,8 +56,10 @@ it('useClusterInterceptor', () => {
jest.spyOn(utils, 'useResource').mockImplementation(() => query);
expect(API.useClusterInterceptor(params)).toEqual(query);
expect(utils.useResource).toHaveBeenCalledWith(
'ClusterInterceptor',
API.getClusterInterceptor,
params
expect.objectContaining({
api: API.getClusterInterceptor,
kind: 'ClusterInterceptor',
params
})
);
});
27 changes: 24 additions & 3 deletions src/api/clusterTasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ import {
useResource
} from './utils';

function getClusterTasksAPI({ filters, isWebSocket, name }) {
return getTektonAPI(
'clustertasks',
{ isWebSocket },
getQueryParams({ filters, name })
);
}

export function getClusterTasks({ filters = [] } = {}) {
const uri = getTektonAPI('clustertasks', undefined, getQueryParams(filters));
const uri = getClusterTasksAPI({ filters });
return get(uri).then(checkData);
}

Expand All @@ -36,9 +44,22 @@ export function deleteClusterTask({ name }) {
}

export function useClusterTasks(params) {
return useCollection('ClusterTask', getClusterTasks, params);
const webSocketURL = getClusterTasksAPI({ ...params, isWebSocket: true });
return useCollection({
api: getClusterTasks,
kind: 'ClusterTask',
params,
webSocketURL
});
}

export function useClusterTask(params, queryConfig) {
return useResource('ClusterTask', getClusterTask, params, queryConfig);
const webSocketURL = getClusterTasksAPI({ ...params, isWebSocket: true });
return useResource({
api: getClusterTask,
kind: 'ClusterTask',
params,
queryConfig,
webSocketURL
});
}
27 changes: 16 additions & 11 deletions src/api/clusterTasks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ it('useClusterTasks', () => {
jest.spyOn(utils, 'useCollection').mockImplementation(() => query);
expect(API.useClusterTasks(params)).toEqual(query);
expect(utils.useCollection).toHaveBeenCalledWith(
'ClusterTask',
API.getClusterTasks,
params
expect.objectContaining({
api: API.getClusterTasks,
kind: 'ClusterTask',
params
})
);
});

Expand All @@ -64,18 +66,21 @@ it('useClusterTask', () => {
jest.spyOn(utils, 'useResource').mockImplementation(() => query);
expect(API.useClusterTask(params)).toEqual(query);
expect(utils.useResource).toHaveBeenCalledWith(
'ClusterTask',
API.getClusterTask,
params,
undefined
expect.objectContaining({
api: API.getClusterTask,
kind: 'ClusterTask',
params
})
);

const queryConfig = { fake: 'queryConfig' };
API.useClusterTask(params, queryConfig);
expect(utils.useResource).toHaveBeenCalledWith(
'ClusterTask',
API.getClusterTask,
params,
queryConfig
expect.objectContaining({
api: API.getClusterTask,
kind: 'ClusterTask',
params,
queryConfig
})
);
});
38 changes: 28 additions & 10 deletions src/api/clusterTriggerBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import {
useResource
} from './utils';

export function getClusterTriggerBindings({ filters = [] } = {}) {
const uri = getTektonAPI(
function getClusterTriggerBindingsAPI({ filters, isWebSocket, name }) {
return getTektonAPI(
'clustertriggerbindings',
{ group: triggersAPIGroup, version: 'v1alpha1' },
getQueryParams(filters)
{ group: triggersAPIGroup, isWebSocket, version: 'v1alpha1' },
getQueryParams({ filters, name })
);
}

export function getClusterTriggerBindings({ filters = [] } = {}) {
const uri = getClusterTriggerBindingsAPI({ filters });
return get(uri).then(checkData);
}

Expand All @@ -40,13 +44,27 @@ export function getClusterTriggerBinding({ name }) {
}

export function useClusterTriggerBindings(params) {
return useCollection(
'ClusterTriggerBinding',
getClusterTriggerBindings,
params
);
const webSocketURL = getClusterTriggerBindingsAPI({
...params,
isWebSocket: true
});
return useCollection({
api: getClusterTriggerBindings,
kind: 'ClusterTriggerBinding',
params,
webSocketURL
});
}

export function useClusterTriggerBinding(params) {
return useResource('ClusterTriggerBinding', getClusterTriggerBinding, params);
const webSocketURL = getClusterTriggerBindingsAPI({
...params,
isWebSocket: true
});
return useResource({
api: getClusterTriggerBinding,
kind: 'ClusterTriggerBinding',
params,
webSocketURL
});
}
16 changes: 10 additions & 6 deletions src/api/clusterTriggerBindings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ it('useClusterTriggerBindings', () => {
jest.spyOn(utils, 'useCollection').mockImplementation(() => query);
expect(API.useClusterTriggerBindings(params)).toEqual(query);
expect(utils.useCollection).toHaveBeenCalledWith(
'ClusterTriggerBinding',
API.getClusterTriggerBindings,
params
expect.objectContaining({
api: API.getClusterTriggerBindings,
kind: 'ClusterTriggerBinding',
params
})
);
});

Expand All @@ -54,8 +56,10 @@ it('useClusterTriggerBinding', () => {
jest.spyOn(utils, 'useResource').mockImplementation(() => query);
expect(API.useClusterTriggerBinding(params)).toEqual(query);
expect(utils.useResource).toHaveBeenCalledWith(
'ClusterTriggerBinding',
API.getClusterTriggerBinding,
params
expect.objectContaining({
api: API.getClusterTriggerBinding,
kind: 'ClusterTriggerBinding',
params
})
);
});
6 changes: 6 additions & 0 deletions src/api/comms.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import ReconnectingWebSocket from 'reconnecting-websocket';

const csrfHeader = {
'Tekton-Client': 'tektoncd/dashboard'
};
Expand All @@ -19,6 +21,10 @@ const defaultOptions = {
credentials: 'same-origin'
};

export function createWebSocket(url) {
return new ReconnectingWebSocket(url);
}

export function getAPIRoot() {
const { host, pathname, protocol } = window.location;
let baseURL = `${protocol}//${host}${pathname}`;
Expand Down
28 changes: 22 additions & 6 deletions src/api/conditions.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import {
useResource
} from './utils';

export function getConditions({ filters = [], namespace } = {}) {
const uri = getTektonAPI(
function getConditionsAPI({ filters, isWebSocket, name, namespace }) {
return getTektonAPI(
'conditions',
{ namespace, version: 'v1alpha1' },
getQueryParams(filters)
{ isWebSocket, namespace, version: 'v1alpha1' },
getQueryParams({ filters, name })
);
}

export function getConditions({ filters = [], namespace } = {}) {
const uri = getConditionsAPI({ filters, namespace });
return get(uri).then(checkData);
}

Expand All @@ -39,9 +43,21 @@ export function getCondition({ name, namespace }) {
}

export function useConditions(params) {
return useCollection('Condition', getConditions, params);
const webSocketURL = getConditionsAPI({ ...params, isWebSocket: true });
return useCollection({
api: getConditions,
kind: 'Condition',
params,
webSocketURL
});
}

export function useCondition(params) {
return useResource('Condition', getCondition, params);
const webSocketURL = getConditionsAPI({ ...params, isWebSocket: true });
return useResource({
api: getCondition,
kind: 'Condition',
params,
webSocketURL
});
}
16 changes: 10 additions & 6 deletions src/api/conditions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ it('useConditions', () => {
jest.spyOn(utils, 'useCollection').mockImplementation(() => query);
expect(API.useConditions(params)).toEqual(query);
expect(utils.useCollection).toHaveBeenCalledWith(
'Condition',
API.getConditions,
params
expect.objectContaining({
api: API.getConditions,
kind: 'Condition',
params
})
);
});

Expand All @@ -54,8 +56,10 @@ it('useCondition', () => {
jest.spyOn(utils, 'useResource').mockImplementation(() => query);
expect(API.useCondition(params)).toEqual(query);
expect(utils.useResource).toHaveBeenCalledWith(
'Condition',
API.getCondition,
params
expect.objectContaining({
kind: 'Condition',
api: API.getCondition,
params
})
);
});
28 changes: 22 additions & 6 deletions src/api/eventListeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ import {
useResource
} from './utils';

export function getEventListeners({ filters = [], namespace } = {}) {
const uri = getTektonAPI(
function getEventListenersAPI({ filters, isWebSocket, name, namespace }) {
return getTektonAPI(
'eventlisteners',
{ group: triggersAPIGroup, namespace, version: 'v1alpha1' },
getQueryParams(filters)
{ group: triggersAPIGroup, isWebSocket, namespace, version: 'v1alpha1' },
getQueryParams({ filters, name })
);
}

export function getEventListeners({ filters = [], namespace } = {}) {
const uri = getEventListenersAPI({ filters, namespace });
return get(uri).then(checkData);
}

Expand All @@ -41,9 +45,21 @@ export function getEventListener({ name, namespace }) {
}

export function useEventListeners(params) {
return useCollection('EventListener', getEventListeners, params);
const webSocketURL = getEventListenersAPI({ ...params, isWebSocket: true });
return useCollection({
api: getEventListeners,
kind: 'EventListener',
params,
webSocketURL
});
}

export function useEventListener(params) {
return useResource('EventListener', getEventListener, params);
const webSocketURL = getEventListenersAPI({ ...params, isWebSocket: true });
return useResource({
api: getEventListener,
kind: 'EventListener',
params,
webSocketURL
});
}
Loading

0 comments on commit 85f8527

Please sign in to comment.