Skip to content

Commit

Permalink
refactor: moving pod events method to PodManager
Browse files Browse the repository at this point in the history
Signed-off-by: axel7083 <[email protected]>
  • Loading branch information
axel7083 committed May 29, 2024
1 parent c1d4afc commit 1b5a398
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 73 deletions.
4 changes: 2 additions & 2 deletions packages/backend/src/managers/applicationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,10 +578,10 @@ export class ApplicationManager extends Publisher<ApplicationState[]> implements
this.notify();
});

this.podmanConnection.onPodStart((pod: PodInfo) => {
this.podManager.onStartPodEvent((pod: PodInfo) => {
this.adoptPod(pod);
});
this.podmanConnection.onPodRemove((podId: string) => {
this.podManager.onRemovePodEvent(({ podId }) => {
this.forgetPodById(podId);
});

Expand Down
66 changes: 0 additions & 66 deletions packages/backend/src/managers/podmanConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
type RegisterContainerConnectionEvent,
provider,
type UpdateContainerConnectionEvent,
containerEngine,
type PodInfo,
type Disposable,
} from '@podman-desktop/api';
Expand All @@ -38,16 +37,12 @@ export class PodmanConnection implements Disposable {
#toExecuteAtStartup: startupHandle[] = [];
#toExecuteAtMachineStop: machineStopHandle[] = [];
#toExecuteAtMachineStart: machineStartHandle[] = [];
#toExecuteAtPodStart: podStartHandle[] = [];
#toExecuteAtPodStop: podStopHandle[] = [];
#toExecuteAtPodRemove: podRemoveHandle[] = [];

#onEventDisposable: Disposable | undefined;

init(): void {
this.listenRegistration();
this.listenMachine();
this.watchPods();
}

dispose(): void {
Expand Down Expand Up @@ -114,65 +109,4 @@ export class PodmanConnection implements Disposable {
onMachineStop(f: machineStopHandle) {
this.#toExecuteAtMachineStop.push(f);
}

watchPods() {
if (this.#onEventDisposable !== undefined) throw new Error('already watching pods.');

this.#onEventDisposable = containerEngine.onEvent(event => {
if (event.Type !== 'pod') {
return;
}
switch (event.status) {
case 'remove':
for (const f of this.#toExecuteAtPodRemove) {
f(event.id);
}
break;
case 'start':
containerEngine
.listPods()
.then((pods: PodInfo[]) => {
const pod = pods.find((p: PodInfo) => p.Id === event.id);
if (!pod) {
return;
}
for (const f of this.#toExecuteAtPodStart) {
f(pod);
}
})
.catch((err: unknown) => {
console.error(err);
});
break;
case 'stop':
containerEngine
.listPods()
.then((pods: PodInfo[]) => {
const pod = pods.find((p: PodInfo) => p.Id === event.id);
if (!pod) {
return;
}
for (const f of this.#toExecuteAtPodStop) {
f(pod);
}
})
.catch((err: unknown) => {
console.error(err);
});
break;
}
});
}

onPodStart(f: podStartHandle) {
this.#toExecuteAtPodStart.push(f);
}

onPodStop(f: podStopHandle) {
this.#toExecuteAtPodStop.push(f);
}

onPodRemove(f: podRemoveHandle) {
this.#toExecuteAtPodRemove.push(f);
}
}
127 changes: 125 additions & 2 deletions packages/backend/src/managers/recipes/PodManager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

import { beforeEach, describe, vi, expect, test } from 'vitest';
import { PodManager } from './PodManager';
import type { ContainerInspectInfo, PodCreateOptions, PodInfo } from '@podman-desktop/api';
import { containerEngine } from '@podman-desktop/api';
import type { ContainerInspectInfo, ContainerJSONEvent, PodCreateOptions, PodInfo } from '@podman-desktop/api';
import { EventEmitter , containerEngine } from '@podman-desktop/api';

vi.mock('@podman-desktop/api', () => ({
containerEngine: {
Expand All @@ -29,7 +29,9 @@ vi.mock('@podman-desktop/api', () => ({
startPod: vi.fn(),
createPod: vi.fn(),
inspectContainer: vi.fn(),
onEvent: vi.fn(),
},
EventEmitter: vi.fn(),
}));

beforeEach(() => {
Expand All @@ -45,6 +47,18 @@ beforeEach(() => {
},
} as unknown as ContainerInspectInfo;
});

// mocking the EventEmitter mechanism
const listeners: ((value: unknown) => void)[] = [];

vi.mocked(EventEmitter).mockReturnValue({
event: vi.fn().mockImplementation(callback => {
listeners.push(callback);
}),
fire: vi.fn().mockImplementation((content: unknown) => {
listeners.forEach(listener => listener(content));
}),
} as unknown as EventEmitter<unknown>);
});

test('getAllPods should use container engine list pods method', async () => {
Expand Down Expand Up @@ -234,3 +248,112 @@ test('createPod should call containerEngine.createPod', async () => {
await new PodManager().createPod(options);
expect(containerEngine.createPod).toHaveBeenCalledWith(options);
});

test('dispose should dispose onEvent disposable', () => {
const disposableMock = vi.fn();
vi.mocked(containerEngine.onEvent).mockImplementation(() => {
return { dispose: disposableMock };
});

const podManager = new PodManager();
podManager.init();

podManager.dispose();

expect(containerEngine.onEvent).toHaveBeenCalled();
expect(disposableMock).toHaveBeenCalled();
});

const getInitializedPodManager = (): {
onEventListener: (e: ContainerJSONEvent) => unknown;
podManager: PodManager;
} => {
let func: ((e: ContainerJSONEvent) => unknown) | undefined = undefined;
vi.mocked(containerEngine.onEvent).mockImplementation(fn => {
func = fn;
return { dispose: vi.fn() };
});

const podManager = new PodManager();
podManager.init();

if (!func) throw new Error('listener should be defined');

return { onEventListener: func, podManager };
};

describe('events', () => {
test('onStartPodEvent listener should be called on start pod event', async () => {
vi.mocked(containerEngine.listPods).mockResolvedValue([
{
Id: 'pod-id-1',
Labels: {
'dummy-key': 'dummy-value',
hello: 'world',
},
},
] as unknown as PodInfo[]);

const { onEventListener, podManager } = getInitializedPodManager();

const startListenerMock = vi.fn();
podManager.onStartPodEvent(startListenerMock);

onEventListener({ id: 'pod-id-1', Type: 'pod', type: '', status: 'start' });

await vi.waitFor(() => {
expect(startListenerMock).toHaveBeenCalledWith({
Id: 'pod-id-1',
Labels: {
'dummy-key': 'dummy-value',
hello: 'world',
},
});
});
});

test('onStopPodEvent listener should be called on start pod event', async () => {
vi.mocked(containerEngine.listPods).mockResolvedValue([
{
Id: 'pod-id-1',
Labels: {
'dummy-key': 'dummy-value',
hello: 'world',
},
},
] as unknown as PodInfo[]);

const { onEventListener, podManager } = getInitializedPodManager();

const stopListenerMock = vi.fn();
podManager.onStopPodEvent(stopListenerMock);

onEventListener({ id: 'pod-id-1', Type: 'pod', type: '', status: 'stop' });

await vi.waitFor(() => {
expect(stopListenerMock).toHaveBeenCalledWith({
Id: 'pod-id-1',
Labels: {
'dummy-key': 'dummy-value',
hello: 'world',
},
});
});
});

test('onRemovePodEvent listener should be called on start pod event', async () => {
const { onEventListener, podManager } = getInitializedPodManager();

const removeListenerMock = vi.fn();
podManager.onRemovePodEvent(removeListenerMock);

onEventListener({ id: 'pod-id-1', Type: 'pod', type: '', status: 'remove' });

await vi.waitFor(() => {
expect(removeListenerMock).toHaveBeenCalledWith({
podId: 'pod-id-1',
});
});
expect(containerEngine.listPods).not.toHaveBeenCalled();
});
});
66 changes: 63 additions & 3 deletions packages/backend/src/managers/recipes/PodManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,60 @@
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import type { Disposable, PodCreateOptions, PodInfo } from '@podman-desktop/api';
import { containerEngine } from '@podman-desktop/api';
import type { Disposable, PodCreateOptions, PodInfo, Event } from '@podman-desktop/api';
import { containerEngine, EventEmitter } from '@podman-desktop/api';
import type { PodHealth } from '@shared/src/models/IApplicationState';
import { getPodHealth } from '../../utils/podsUtils';

export interface PodEvent {
podId: string;
}

export class PodManager implements Disposable {
dispose(): void {}
#eventDisposable: Disposable | undefined;

// start pod events
private readonly _onStartPodEvent = new EventEmitter<PodInfo>();
readonly onStartPodEvent: Event<PodInfo> = this._onStartPodEvent.event;

// stop pod events
private readonly _onStopPodEvent = new EventEmitter<PodInfo>();
readonly onStopPodEvent: Event<PodInfo> = this._onStopPodEvent.event;

// remove pod events
private readonly _onRemovePodEvent = new EventEmitter<PodEvent>();
readonly onRemovePodEvent: Event<PodEvent> = this._onRemovePodEvent.event;

constructor() {}

dispose(): void {
this.#eventDisposable?.dispose();
}

init(): void {
this.#eventDisposable = containerEngine.onEvent(async event => {
// filter on pod event type
if (event.Type !== 'pod') {
return;
}

if (event.status === 'remove') {
return this._onRemovePodEvent.fire({
podId: event.id,
});
}

const pod: PodInfo = await this.getPodById(event.id);
switch (event.status) {
case 'start':
this._onStartPodEvent.fire(pod);
break;
case 'stop':
this._onStopPodEvent.fire(pod);
break;
}
});
}

/**
* Utility method to get all the pods
Expand Down Expand Up @@ -75,6 +122,19 @@ export class PodManager implements Disposable {
return getPodHealth(containerStates);
}

/**
* This handy method is private as we do not want expose method not providing
* the engineId, but this is required because PodEvent do not provide the engineId
* @param id
* @private
*/
private async getPodById(id: string): Promise<PodInfo> {
const pods = await this.getAllPods();
const result = pods.find(pod => pod.Id === id);
if (!result) throw new Error(`pod with Id ${id} cannot be found.`);
return result;
}

async getPod(engineId: string, Id: string): Promise<PodInfo> {
const pods = await this.getAllPods();
const result = pods.find(pod => pod.engineId === engineId && pod.Id === Id);
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export class Studio {
this.#extensionContext.subscriptions.push(builderManager);

const podManager = new PodManager();
podManager.init();
this.#extensionContext.subscriptions.push(podManager);

this.modelsManager = new ModelsManager(
Expand Down

0 comments on commit 1b5a398

Please sign in to comment.