Skip to content

Commit

Permalink
fix(network) ensure network configuration is fully supported in a clu…
Browse files Browse the repository at this point in the history
…stered backend

Signed-off-by: David Edler <[email protected]>
  • Loading branch information
edlerd committed Jan 16, 2025
1 parent 2092179 commit 5b8158b
Show file tree
Hide file tree
Showing 23 changed files with 1,055 additions and 223 deletions.
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@ const App: FC = () => {
/>
}
/>
<Route
path="/ui/project/:project/member/:member/network/:name"
element={
<ProtectedRoute
outlet={<ProjectLoader outlet={<NetworkDetail />} />}
/>
}
/>
<Route
path="/ui/project/:project/network/:name/:activeTab"
element={
Expand Down
181 changes: 158 additions & 23 deletions src/api/networks.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { handleEtagResponse, handleResponse } from "util/helpers";
import type { LxdNetwork, LxdNetworkState } from "types/network";
import type {
LxdNetwork,
LXDNetworkOnClusterMember,
LxdNetworkState,
} from "types/network";
import type { LxdApiResponse } from "types/apiResponse";
import type { LxdClusterMember } from "types/cluster";
import { areNetworksEqual } from "util/networks";
import type { ClusterSpecificValues } from "components/ClusterSpecificSelect";
import type { LxdClusterMember } from "types/cluster";

export const fetchNetworks = (project: string): Promise<LxdNetwork[]> => {
export const fetchNetworks = (
project: string,
target?: string,
): Promise<LxdNetwork[]> => {
const targetParam = target ? `&target=${target}` : "";
return new Promise((resolve, reject) => {
fetch(`/1.0/networks?project=${project}&recursion=1`)
fetch(`/1.0/networks?project=${project}&recursion=1${targetParam}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdNetwork[]>) => {
const filteredNetworks = data.metadata.filter(
Expand All @@ -20,24 +29,97 @@ export const fetchNetworks = (project: string): Promise<LxdNetwork[]> => {
});
};

export const fetchNetworksFromClusterMembers = (
project: string,
clusterMembers: LxdClusterMember[],
): Promise<LXDNetworkOnClusterMember[]> => {
return new Promise((resolve, reject) => {
Promise.allSettled(
clusterMembers.map((member) => {
return fetchNetworks(project, member.server_name);
}),
)
.then((results) => {
const error = results.find((res) => res.status === "rejected")
?.reason as Error | undefined;

if (error) {
reject(error);
return;
}

const result: LXDNetworkOnClusterMember[] = [];

for (let i = 0; i < clusterMembers.length; i++) {
const memberName = clusterMembers[i].server_name;
const networks = results[i] as PromiseFulfilledResult<LxdNetwork[]>;
networks.value.forEach((network) =>
result.push({ ...network, memberName }),
);
}

resolve(result);
})
.catch(reject);
});
};

export const fetchNetwork = (
name: string,
project: string,
target?: string,
): Promise<LxdNetwork> => {
const targetParam = target ? `&target=${target}` : "";
return new Promise((resolve, reject) => {
fetch(`/1.0/networks/${name}?project=${project}`)
fetch(`/1.0/networks/${name}?project=${project}${targetParam}`)
.then(handleEtagResponse)
.then((data) => resolve(data as LxdNetwork))
.catch(reject);
});
};

export const fetchNetworkFromClusterMembers = (
name: string,
project: string,
clusterMembers: LxdClusterMember[],
): Promise<LXDNetworkOnClusterMember[]> => {
return new Promise((resolve, reject) => {
Promise.allSettled(
clusterMembers.map((member) => {
return fetchNetwork(name, project, member.server_name);
}),
)
.then((results) => {
const error = results.find((res) => res.status === "rejected")
?.reason as Error | undefined;

if (error) {
reject(error);
return;
}

const result: LXDNetworkOnClusterMember[] = [];

for (let i = 0; i < clusterMembers.length; i++) {
const name = clusterMembers[i].server_name;
const promise = results[i] as PromiseFulfilledResult<LxdNetwork>;
result.push({ ...promise.value, memberName: name });
}

resolve(result);
})
.catch(reject);
});
};

export const fetchNetworkState = (
name: string,
project: string,
target?: string,
): Promise<LxdNetworkState> => {
const targetParam = target ? `&target=${target}` : "";
return new Promise((resolve, reject) => {
fetch(`/1.0/networks/${name}/state?project=${project}`)
fetch(`/1.0/networks/${name}/state?project=${project}${targetParam}`)
.then(handleResponse)
.then((data: LxdApiResponse<LxdNetworkState>) => resolve(data.metadata))
.catch(reject);
Expand All @@ -48,20 +130,21 @@ export const createClusterNetwork = (
network: Partial<LxdNetwork>,
project: string,
clusterMembers: LxdClusterMember[],
parentsPerClusterMember?: ClusterSpecificValues,
): Promise<void> => {
return new Promise((resolve, reject) => {
const memberNetwork = {
name: network.name,
description: network.description,
type: network.type,
config: {
parent: network.config?.parent,
},
};

Promise.allSettled(
clusterMembers.map(async (member) => {
await createNetwork(memberNetwork, project, member.server_name);
clusterMembers.map((member) => {
const memberNetwork = {
name: network.name,
type: network.type,
config: {
parent: parentsPerClusterMember?.[member.server_name],
},
};
createNetwork(memberNetwork, project, member.server_name).catch(
(e: Error) => Promise.reject(e),
);
}),
)
.then((results) => {
Expand All @@ -72,6 +155,7 @@ export const createClusterNetwork = (
reject(error);
return;
}

// The network parent is cluster member specific, so we omit it on the cluster wide network configuration.
delete network.config?.parent;
createNetwork(network, project).then(resolve).catch(reject);
Expand Down Expand Up @@ -110,15 +194,20 @@ export const createNetwork = (
export const updateNetwork = (
network: Partial<LxdNetwork> & Required<Pick<LxdNetwork, "config">>,
project: string,
target?: string,
): Promise<void> => {
return new Promise((resolve, reject) => {
fetch(`/1.0/networks/${network.name ?? ""}?project=${project}`, {
method: "PUT",
body: JSON.stringify(network),
headers: {
"If-Match": network.etag ?? "invalid-etag",
const targetParam = target ? `&target=${target}` : "";
fetch(
`/1.0/networks/${network.name ?? ""}?project=${project}${targetParam}`,
{
method: "PUT",
body: JSON.stringify(network),
headers: {
"If-Match": network.etag ?? "",
},
},
})
)
.then(handleResponse)
.then(resolve)
.catch(async (e: Error) => {
Expand All @@ -135,6 +224,52 @@ export const updateNetwork = (
});
};

export const updateClusterNetwork = (
network: Partial<LxdNetwork> & Required<Pick<LxdNetwork, "config">>,
project: string,
parentsPerClusterMember?: ClusterSpecificValues,
): Promise<void> => {
if (!parentsPerClusterMember) {
return updateNetwork(network, project);
}

return new Promise((resolve, reject) => {
Promise.allSettled(
Object.keys(parentsPerClusterMember).map((memberName) => {
const memberNetwork = {
name: network.name,
type: network.type,
config: {
parent: parentsPerClusterMember[memberName],
},
etag: "",
};
return updateNetwork(memberNetwork, project, memberName).catch(
(e: Error) =>
Promise.reject(
new Error(
`Error on network update on cluster member ${memberName}. ${e.message}`,
),
),
);
}),
)
.then((results) => {
const error = results.find((res) => res.status === "rejected")
?.reason as Error | undefined;

if (error) {
reject(error);
return;
}
updateNetwork({ ...network, etag: "" }, project)
.then(resolve)
.catch(reject);
})
.catch(reject);
});
};

export const renameNetwork = (
oldName: string,
newName: string,
Expand Down
Loading

0 comments on commit 5b8158b

Please sign in to comment.