Skip to content

Commit

Permalink
Merge pull request #14 from omnia-network/lorenzo/check-deployments-s…
Browse files Browse the repository at this point in the history
…tate

Deployment state management during deployment fetching
  • Loading branch information
lorenzoronzani authored Jun 23, 2024
2 parents a97eaee + 39a12d5 commit 7eb2e0d
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 87 deletions.
5 changes: 0 additions & 5 deletions deps/candid/rdmx6-jaaaa-aaaaa-aaadq-cai.did
Original file line number Diff line number Diff line change
Expand Up @@ -542,11 +542,6 @@ service : (opt InternetIdentityInit) -> {
fetch_entries: () -> (vec BufferedArchiveEntry);
acknowledge_entries: (sequence_number: nat64) -> ();

// Calls used for event stats housekeeping.
// Only callable by the canister itself.
prune_events_if_necessary: () -> ();
inject_prune_event: (timestamp: Timestamp) -> ();

// V2 API
// WARNING: The following methods are experimental and may change in the future.

Expand Down
6 changes: 3 additions & 3 deletions deps/pulled.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
"canisters": {
"rdmx6-jaaaa-aaaaa-aaadq-cai": {
"name": "internet_identity",
"wasm_hash": "764cff569a98a3c4d54cba6750fda63f554fc53e7d42a6365d9bdec3280d63c3",
"wasm_hash_download": "764cff569a98a3c4d54cba6750fda63f554fc53e7d42a6365d9bdec3280d63c3",
"wasm_hash": "01797eac7db02126e7cb507e2f3134e2d7b5154289808fdc2c8c03d1fc2dc60e",
"wasm_hash_download": "01797eac7db02126e7cb507e2f3134e2d7b5154289808fdc2c8c03d1fc2dc60e",
"init_guide": "Use '(null)' for sensible defaults. See the candid interface for more details.",
"init_arg": null,
"candid_args": "(opt InternetIdentityInit)",
"gzip": true
}
}
}
}
84 changes: 32 additions & 52 deletions frontend/app/dashboard/new-deployment/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import {
useIcContext,
} from "@/contexts/IcContext";
import type { DeploymentParams, DeploymentState } from "@/declarations/backend.did";
import { extractDeploymentCreated } from "@/helpers/deployment";
import { extractOk } from "@/helpers/result";
import { sendManifestToProvider } from "@/services/deployment";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
Expand All @@ -21,6 +19,7 @@ import { displayE8sAsIcp, icpToE8s } from "@/helpers/ui";
import { Spinner } from "@/components/spinner";
import { NewDeploymentForm } from "@/components/new-deployment-form";
import { transferE8sToBackend } from "@/services/backend";
import { completeDeployment, confirmDeployment, updateDeploymentState } from "@/services/deployment";

const FETCH_DEPLOYMENT_PRICE_INTERVAL_MS = 30_000; // 30 seconds

Expand Down Expand Up @@ -98,6 +97,26 @@ export default function NewDeployment() {
}
}, [backendActor, deploymentE8sPrice, ledgerCanister, refreshLedgerData, toastError]);

const setDeploymentAsActive = useCallback(async (deploymentId: string) => {
try {
closeWs();

const stepActive = {
Active: null,
};
setDeploymentSteps((prev) => [...prev, stepActive]);

await completeDeployment(backendActor!, deploymentId);

setIsDeploying(false);
router.push("/dashboard");
} catch (e) {
console.error("Failed to complete deployment:", e);
setDeploymentError("Failed to complete deployment, see console for details");
setIsDeploying(false);
}
}, [backendActor, closeWs, router]);

const onWsOpen: OnWsOpenCallback = useCallback(async () => {
console.log("ws open");

Expand Down Expand Up @@ -153,29 +172,21 @@ export default function NewDeployment() {
return;
}

let leaseCreated = false;

try {
if ("LeaseCreated" in deploymentUpdate.update) {
const { manifest_sorted_json, dseq } = extractDeploymentCreated(
deploymentSteps.find((el) =>
el.hasOwnProperty("DeploymentCreated")
)!
);
const { provider_url } = deploymentUpdate.update.LeaseCreated;
const deploymentCreatedState = deploymentSteps.find((el) =>
el.hasOwnProperty("DeploymentCreated")
)!;

const manifestUrl = new URL(
`/deployment/${dseq}/manifest`,
provider_url
);

await sendManifestToProvider(
manifestUrl.toString(),
manifest_sorted_json,
await confirmDeployment(
deploymentUpdate.update,
deploymentCreatedState,
tlsCertificateData!
);

leaseCreated = true;
await setDeploymentAsActive(deploymentUpdate.id);

await fetchDeployments(backendActor!);
}
} catch (e) {
console.error(e);
Expand All @@ -189,12 +200,7 @@ export default function NewDeployment() {

setDeploymentSteps((prev) => [...prev, stepFailed]);

extractOk(
await backendActor!.update_deployment_state(
deploymentUpdate.id,
stepFailed
)
);
await updateDeploymentState(backendActor!, deploymentUpdate.id, stepFailed);
} catch (e) {
console.error("Failed to update deployment:", e);
}
Expand All @@ -204,41 +210,15 @@ export default function NewDeployment() {
);
setIsDeploying(false);
}

try {
if (leaseCreated) {
closeWs();

const stepActive = {
Active: null,
};
setDeploymentSteps((prev) => [...prev, stepActive]);
extractOk(
await backendActor!.update_deployment_state(
deploymentUpdate.id,
stepActive
)
);

await fetchDeployments(backendActor!);

setIsDeploying(false);
router.push("/dashboard");
}
} catch (e) {
console.error("Failed to complete deployment:", e);
setDeploymentError("Failed to complete deployment, see console for details");
setIsDeploying(false);
}
},
[
tlsCertificateData,
setDeploymentSteps,
deploymentSteps,
backendActor,
fetchDeployments,
router,
closeWs,
setDeploymentAsActive,
]
);

Expand Down
19 changes: 5 additions & 14 deletions frontend/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,8 @@ import Tier from "@/components/Tier";
import { LoadingButton } from "@/components/loading-button";
import { Spinner } from "@/components/spinner";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card";
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible";
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -144,7 +133,9 @@ export default function Dashboard() {
<Card key={el.id}>
<CardHeader>
<CardTitle className="text-sm font-medium">{el.deployment.params.name}</CardTitle>
<CardDescription><pre className="font-xs">{el.id}</pre></CardDescription>
<CardDescription>
<pre className="font-xs">{el.id}</pre>
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-1">
<div>
Expand Down
78 changes: 65 additions & 13 deletions frontend/contexts/DeploymentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createX509, X509CertificateData } from "@/lib/certificate";
import { type BackendActor } from "@/services/backend";
import { createContext, useCallback, useContext, useState } from "react";
import { getCurrentUser } from "@/services/user";
import { completeDeployment, confirmDeployment, updateDeploymentState } from "@/services/deployment";

export type Deployments = OkType<GetDeploymentsResult>;

Expand All @@ -25,19 +26,6 @@ export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children
const [tlsCertificateData, setCertificateData] = useState<X509CertificateData | null>(null);
const [deployments, setDeployments] = useState<Deployments>([]);

const fetchDeployments = useCallback(async (actor: BackendActor) => {
const res = await actor.get_deployments();

const _deployments = extractOk(res);
setDeployments(
_deployments.sort((el1, el2) =>
getDeploymentCreatedDate(el2.deployment).getTime() - getDeploymentCreatedDate(el1.deployment).getTime()
)
);

console.log("deployments", _deployments);
}, []);

const loadOrCreateCertificate = useCallback(async (actor: BackendActor): Promise<X509CertificateData | null> => {
let certData = (await getCurrentUser(actor)).mtls_certificate[0] || null;

Expand All @@ -58,6 +46,70 @@ export const DeploymentProvider: React.FC<DeploymentProviderProps> = ({ children
return certData!;
}, []);

const completeDeployments = useCallback(async (actor: BackendActor, deployments: Deployments): Promise<number> => {
let updatedCount = 0;

for (const deployment of deployments) {
const lastState = deployment.deployment.state_history[deployment.deployment.state_history.length - 1][1];
const deploymentCreatedState = deployment.deployment.state_history.find(([_, state]) => "DeploymentCreated" in state)![1];
if ("LeaseCreated" in lastState) {
try {
const cert = await loadOrCreateCertificate(actor);

await confirmDeployment(
lastState,
deploymentCreatedState,
cert!
);
await completeDeployment(
actor,
deployment.id,
);

updatedCount++;
} catch (e) {
console.error("Failed to update deployment:", e);

try {
const stepFailed = {
FailedOnClient: {
reason: JSON.stringify(e),
},
};
await updateDeploymentState(actor, deployment.id, stepFailed);

updatedCount++;
} catch (e) {
console.error("Failed to update deployment:", e);
throw e;
}
}

}
}

return updatedCount;
}, [loadOrCreateCertificate]);

const fetchDeployments = useCallback(async (actor: BackendActor) => {
const res = await actor.get_deployments();

const _deployments = extractOk(res);

const updatedCount = await completeDeployments(actor, _deployments);
if (updatedCount > 0) {
return await fetchDeployments(actor);
}

setDeployments(
_deployments.sort((el1, el2) =>
getDeploymentCreatedDate(el2.deployment).getTime() - getDeploymentCreatedDate(el1.deployment).getTime()
)
);

console.log("deployments", _deployments);
}, [completeDeployments]);

return (
<DeploymentContext.Provider
value={{
Expand Down
49 changes: 49 additions & 0 deletions frontend/services/deployment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { X509CertificateData } from "@/lib/certificate";
import { wait } from "@/helpers/timer";
import { DeploymentState, MTlsCertificateData } from "@/declarations/backend.did";
import { extractDeploymentCreated } from "@/helpers/deployment";
import { BackendActor } from "@/services/backend";
import { extractOk } from "@/helpers/result";

const PROVIDER_PROXY_URL = "https://akash-provider-proxy.omnia-network.com/";

Expand Down Expand Up @@ -74,3 +78,48 @@ export const queryLeaseStatus = async (queryLeaseUrl: string, certData: X509Cert

return await res.json();
};

export const confirmDeployment = async (deploymentState: DeploymentState, deploymentCreatedState: DeploymentState, cert: MTlsCertificateData) => {
try {
if ("LeaseCreated" in deploymentState) {
const { manifest_sorted_json, dseq } = extractDeploymentCreated(deploymentCreatedState);

const { provider_url } = deploymentState.LeaseCreated;

const manifestUrl = new URL(
`/deployment/${dseq}/manifest`,
provider_url
);

await sendManifestToProvider(
manifestUrl.toString(),
manifest_sorted_json,
cert!
);

} else {
throw new Error("Deployment state is not in LeaseCreated state");
}
} catch (e) {
console.error("Failed to send manifest to provider", e);
throw e;
}
}

export const updateDeploymentState = async (backendActor: BackendActor, deploymentId: string, step: DeploymentState) => {
try {
extractOk(await backendActor.update_deployment_state(deploymentId, step));
} catch (e) {
console.error("Failed to update deployment state", e);
throw e;
}

}

export const completeDeployment = async (backendActor: BackendActor, deploymentId: string) => {
const stepActive = {
Active: null,
};

await updateDeploymentState(backendActor!, deploymentId, stepActive);
};

0 comments on commit 7eb2e0d

Please sign in to comment.