diff --git a/packages/backend/src/managers/applicationManager.ts b/packages/backend/src/managers/applicationManager.ts index b576c721f..248b5accb 100644 --- a/packages/backend/src/managers/applicationManager.ts +++ b/packages/backend/src/managers/applicationManager.ts @@ -264,6 +264,7 @@ export class ApplicationManager { Target: `/${modelName}`, Source: modelPath, Type: 'bind', + Mode: 'Z', }, ], }; @@ -279,35 +280,21 @@ export class ApplicationManager { envs = [`MODEL_ENDPOINT=${endPoint}`]; } } - const createdContainer = await containerEngine.createContainer(podInfo.engineId, { + const podifiedName = this.getRandomName(`${image.appName}-podified`); + await containerEngine.createContainer(podInfo.engineId, { Image: image.id, + name: podifiedName, Detach: true, HostConfig: hostConfig, Env: envs, start: false, + pod: podInfo.Id, + }); + containers.push({ + name: podifiedName, + modelService: image.modelService, + ports: image.ports, }); - - // now, for each container, put it in the pod - if (createdContainer) { - const podifiedName = this.getRandomName(`${image.appName}-podified`); - await containerEngine.replicatePodmanContainer( - { - id: createdContainer.id, - engineId: podInfo.engineId, - }, - { engineId: podInfo.engineId }, - { pod: podInfo.Id, name: podifiedName }, - ); - containers.push({ - name: podifiedName, - modelService: image.modelService, - ports: image.ports, - }); - // remove the external container - await containerEngine.deleteContainer(podInfo.engineId, createdContainer.id); - } else { - throw new Error(`failed at creating container for image ${image.id}`); - } }), ); return containers; diff --git a/packages/backend/src/models/QemuUploader.ts b/packages/backend/src/models/QemuUploader.ts new file mode 100644 index 000000000..bc9c00fdb --- /dev/null +++ b/packages/backend/src/models/QemuUploader.ts @@ -0,0 +1,59 @@ +/********************************************************************** + * Copyright (C) 2024 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import * as podmanDesktopApi from '@podman-desktop/api'; +import { getFirstRunningMachine, getPodmanCli } from '../utils/podman'; +import type { UploadWorker } from './uploader'; +import path from 'node:path'; + +export class QemuUploader implements UploadWorker { + async canUpload(): Promise { + const machine = await getFirstRunningMachine(); + return machine.VMType === 'qemu'; + } + + async upload(localPath: string): Promise { + if (!localPath) { + throw new Error('invalid local path'); + } + + const machine = await getFirstRunningMachine(); + const remotePath = `/var/home/core/${path.basename(localPath)}`; + // check if model already loaded on the podman machine + let existsRemote = true; + try { + await podmanDesktopApi.process.exec(getPodmanCli(), ['machine', 'ssh', machine.Name, 'stat', remotePath]); + } catch (e) { + existsRemote = false; + } + + // if not exists remotely it copies it from the local path + if (!existsRemote) { + await podmanDesktopApi.process.exec(getPodmanCli(), [ + 'machine', + 'ssh', + machine.Name, + 'cp', + localPath, + remotePath, + ]); + } + + return remotePath; + } +} diff --git a/packages/backend/src/models/WSLUploader.ts b/packages/backend/src/models/WSLUploader.ts index e70ec3124..3bfac5ce8 100644 --- a/packages/backend/src/models/WSLUploader.ts +++ b/packages/backend/src/models/WSLUploader.ts @@ -22,7 +22,7 @@ import { getFirstRunningMachine, getPodmanCli } from '../utils/podman'; import type { UploadWorker } from './uploader'; export class WSLUploader implements UploadWorker { - canUpload(): boolean { + async canUpload(): Promise { return podmanDesktopApi.env.isWindows; } diff --git a/packages/backend/src/models/uploader.ts b/packages/backend/src/models/uploader.ts index 9b38e5a29..0904824af 100644 --- a/packages/backend/src/models/uploader.ts +++ b/packages/backend/src/models/uploader.ts @@ -20,9 +20,10 @@ import { EventEmitter, type Event } from '@podman-desktop/api'; import { WSLUploader } from './WSLUploader'; import { getDurationSecondsSince } from '../utils/utils'; import type { CompletionProgressiveEvent, ProgressiveEvent } from '../utils/progressiveEvent'; +import { QemuUploader } from './QemuUploader'; export interface UploadWorker { - canUpload: () => boolean; + canUpload: () => Promise; upload: (path: string) => Promise; } @@ -35,11 +36,17 @@ export class Uploader { private localModelPath: string, private abortSignal?: AbortSignal, ) { - this.#workers = [new WSLUploader()]; + this.#workers = [new WSLUploader(), new QemuUploader()]; } async perform(): Promise { - const workers = this.#workers.filter(w => w.canUpload()); + const workers = []; + for (const worker of this.#workers) { + const canUpload = await worker.canUpload(); + if (canUpload) { + workers.push(worker); + } + } let modelPath = this.localModelPath; try { if (workers && workers.length > 1) { diff --git a/packages/backend/src/utils/podman.ts b/packages/backend/src/utils/podman.ts index 10b989393..909de2ed7 100644 --- a/packages/backend/src/utils/podman.ts +++ b/packages/backend/src/utils/podman.ts @@ -27,6 +27,7 @@ export type MachineJSON = { Starting: boolean; Default: boolean; UserModeNetworking?: boolean; + VMType?: string; }; export function getPodmanCli(): string {