Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.apache.hadoop.ozone.recon.api.types.KeysResponse;
import org.apache.hadoop.ozone.recon.api.types.MissingContainerMetadata;
import org.apache.hadoop.ozone.recon.api.types.MissingContainersResponse;
import org.apache.hadoop.ozone.recon.api.types.QuasiClosedContainersResponse;
import org.apache.hadoop.ozone.recon.api.types.UnhealthyContainerMetadata;
import org.apache.hadoop.ozone.recon.api.types.UnhealthyContainersResponse;
import org.apache.hadoop.ozone.recon.api.types.UnhealthyContainersSummary;
Expand Down Expand Up @@ -447,6 +448,11 @@ private Response getUnhealthyContainersFromSchema(
for (UnhealthyContainersSummary s : summary) {
response.setSummaryCount(s.getContainerState(), s.getCount());
}

// Also include the quasi-closed count in the summary for the frontend Highlights tab
long quasiClosedCount = containerManager.getContainerStateCount(HddsProtos.LifeCycleState.QUASI_CLOSED);
response.setQuasiClosedCount(quasiClosedCount);

return Response.ok(response).build();
}

Expand Down Expand Up @@ -812,4 +818,54 @@ public Response getOmContainersDeletedInSCM(
response.put("containerDiscrepancyInfo", containerDiscrepancyInfoList);
return Response.ok(response).build();
}

/**
* Return all containers in QUASI_CLOSED state.
*
* @param limit max no. of containers to get.
* @param prevKey the containerID after which results are returned.
* @return {@link Response}
*/
@GET
@Path("/quasiClosed")
public Response getQuasiClosedContainers(
@DefaultValue(DEFAULT_FETCH_COUNT) @QueryParam(RECON_QUERY_LIMIT) int limit,
@DefaultValue(PREV_CONTAINER_ID_DEFAULT_VALUE) @QueryParam(RECON_QUERY_PREVKEY) long prevKey) {

List<ContainerInfo> containers = containerManager.getContainers(
ContainerID.valueOf(prevKey + 1), limit, HddsProtos.LifeCycleState.QUASI_CLOSED);

List<UnhealthyContainerMetadata> metaList = containers.stream()
.map(ci -> {
long containerID = ci.getContainerID();
int requiredNodes = 0;
try {
requiredNodes = ci.getReplicationConfig().getRequiredNodes();
} catch (Exception e) {
LOG.warn("Could not get required nodes for container {}", containerID, e);
}
List<ContainerHistory> replicas = containerManager.getLatestContainerHistory(containerID, requiredNodes);

UnhealthyContainerMetadata metadata = new UnhealthyContainerMetadata(
containerID,
"QUASI_CLOSED",
ci.getStateEnterTime() != null ? ci.getStateEnterTime().toEpochMilli() : 0L,
requiredNodes,
replicas.size(),
replicas.size() - requiredNodes,
"",
ci.getNumberOfKeys(),
ci.getPipelineID() != null ? ci.getPipelineID().getId() : null,
replicas
);
return metadata;
})
.collect(Collectors.toList());

long firstKey = metaList.isEmpty() ? prevKey : metaList.get(0).getContainerID();
long lastKey = metaList.isEmpty() ? prevKey : metaList.get(metaList.size() - 1).getContainerID();
long total = containerManager.getContainerStateCount(HddsProtos.LifeCycleState.QUASI_CLOSED);

return Response.ok(new QuasiClosedContainersResponse(total, firstKey, lastKey, metaList)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

package org.apache.hadoop.ozone.recon.api.types;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

/**
* Class that represents the API Response structure for Quasi-Closed Containers.
*/
public class QuasiClosedContainersResponse {

@JsonProperty("quasiClosedCount")
private long quasiClosedCount = 0;

@JsonProperty("firstKey")
private long firstKey = 0;

@JsonProperty("lastKey")
private long lastKey = 0;

@JsonProperty("containers")
private List<UnhealthyContainerMetadata> containers;

public QuasiClosedContainersResponse() {
}

public QuasiClosedContainersResponse(long quasiClosedCount, long firstKey, long lastKey,
List<UnhealthyContainerMetadata> containers) {
this.quasiClosedCount = quasiClosedCount;
this.firstKey = firstKey;
this.lastKey = lastKey;
this.containers = containers;
}

public long getQuasiClosedCount() {
return quasiClosedCount;
}

public void setQuasiClosedCount(long quasiClosedCount) {
this.quasiClosedCount = quasiClosedCount;
}

public long getFirstKey() {
return firstKey;
}

public void setFirstKey(long firstKey) {
this.firstKey = firstKey;
}

public long getLastKey() {
return lastKey;
}

public void setLastKey(long lastKey) {
this.lastKey = lastKey;
}

public List<UnhealthyContainerMetadata> getContainers() {
return containers;
}

public void setContainers(List<UnhealthyContainerMetadata> containers) {
this.containers = containers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,22 @@ public UnhealthyContainerMetadata(UnhealthyContainers rec,
this.keys = keyCount;
}

public UnhealthyContainerMetadata(long containerID, String containerState,
long unhealthySince, long expectedReplicaCount, long actualReplicaCount,
long replicaDeltaCount, String reason, long keys, UUID pipelineID,
List<ContainerHistory> replicas) {
this.containerID = containerID;
this.containerState = containerState;
this.unhealthySince = unhealthySince;
this.expectedReplicaCount = expectedReplicaCount;
this.actualReplicaCount = actualReplicaCount;
this.replicaDeltaCount = replicaDeltaCount;
this.reason = reason;
this.keys = keys;
this.pipelineID = pipelineID;
this.replicas = replicas;
}

// Default constructor, used by jackson lib for object deserialization.
public UnhealthyContainerMetadata() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public class UnhealthyContainersResponse {
@JsonProperty("replicaMismatchCount")
private long replicaMismatchCount = 0;

/**
* Total count of quasi-closed containers.
*/
@JsonProperty("quasiClosedCount")
private long quasiClosedCount = 0;

/**
* The smallest container ID in the current response batch.
* Used for pagination to determine the lower bound for the next page.
Expand Down Expand Up @@ -125,6 +131,14 @@ public long getReplicaMismatchCount() {
return replicaMismatchCount;
}

public long getQuasiClosedCount() {
return quasiClosedCount;
}

public void setQuasiClosedCount(long quasiClosedCount) {
this.quasiClosedCount = quasiClosedCount;
}

public long getLastKey() {
return lastKey;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,23 @@ const ContainerTable: React.FC<ContainerTableProps> = ({
hasPrevPage,
pageSize,
onPageSizeChange,
sinceColumnTitle = 'Unhealthy Since',
}) => {


function filterSelectedColumns() {
const columnKeys = selectedColumns.map((column) => column.value);
return COLUMNS.filter(
const filteredColumns = COLUMNS.filter(
(column) => columnKeys.indexOf(column.key as string) >= 0
);

// Override the title for the unhealthySince column if needed
return filteredColumns.map(col => {
if (col.key === 'unhealthySince') {
return { ...col, title: sinceColumnTitle };
}
return col;
});
}

async function loadRowData(containerID: number) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const TAB_STATE_MAP: Record<string, string> = {
'3': 'OVER_REPLICATED',
'4': 'MIS_REPLICATED',
'5': 'REPLICA_MISMATCH',
'6': 'QUASI_CLOSED',
};

const SearchableColumnOpts = [{
Expand Down Expand Up @@ -85,6 +86,7 @@ const Containers: React.FC<{}> = () => {
overReplicatedCount: 0,
misReplicatedCount: 0,
replicaMismatchCount: 0,
quasiClosedCount: 0,
});
const [pageSize, setPageSize] = useState<number>(DEFAULT_PAGE_SIZE);
const [tabStates, setTabStates] = useState<Record<string, TabPaginationState>>({
Expand All @@ -93,6 +95,7 @@ const Containers: React.FC<{}> = () => {
'3': { ...DEFAULT_TAB_STATE },
'4': { ...DEFAULT_TAB_STATE },
'5': { ...DEFAULT_TAB_STATE },
'6': { ...DEFAULT_TAB_STATE },
});
const [expandedRow, setExpandedRow] = useState<ExpandedRow>({});
const [selectedColumns, setSelectedColumns] = useState<Option[]>(defaultColumns);
Expand Down Expand Up @@ -140,8 +143,12 @@ const Containers: React.FC<{}> = () => {
}));

try {
const endpoint = tabKey === '6'
? `/api/v1/containers/quasiClosed`
: `/api/v1/containers/unhealthy/${containerStateName}`;

const response = await fetchData<ContainersPaginationResponse>(
`/api/v1/containers/unhealthy/${containerStateName}`,
endpoint,
'GET',
{ limit: fetchSize, minContainerId }
);
Expand Down Expand Up @@ -176,11 +183,12 @@ const Containers: React.FC<{}> = () => {
// Summary counts are returned by every tab endpoint.
setState(prev => ({
...prev,
missingCount: response.missingCount ?? 0,
underReplicatedCount: response.underReplicatedCount ?? 0,
overReplicatedCount: response.overReplicatedCount ?? 0,
misReplicatedCount: response.misReplicatedCount ?? 0,
replicaMismatchCount: response.replicaMismatchCount ?? 0,
missingCount: response.missingCount ?? prev.missingCount,
underReplicatedCount: response.underReplicatedCount ?? prev.underReplicatedCount,
overReplicatedCount: response.overReplicatedCount ?? prev.overReplicatedCount,
misReplicatedCount: response.misReplicatedCount ?? prev.misReplicatedCount,
replicaMismatchCount: response.replicaMismatchCount ?? prev.replicaMismatchCount,
quasiClosedCount: response.quasiClosedCount ?? prev.quasiClosedCount,
lastUpdated: Number(moment()),
}));
} catch (error) {
Expand Down Expand Up @@ -247,6 +255,7 @@ const Containers: React.FC<{}> = () => {
'3': { ...DEFAULT_TAB_STATE },
'4': { ...DEFAULT_TAB_STATE },
'5': { ...DEFAULT_TAB_STATE },
'6': { ...DEFAULT_TAB_STATE },
};
setTabStates(reset);
fetchTabData(selectedTab, 0, newSize);
Expand All @@ -260,6 +269,7 @@ const Containers: React.FC<{}> = () => {
'3': { ...DEFAULT_TAB_STATE },
'4': { ...DEFAULT_TAB_STATE },
'5': { ...DEFAULT_TAB_STATE },
'6': { ...DEFAULT_TAB_STATE },
});
fetchTabData(selectedTab, 0, pageSize);
clusterState.refetch();
Expand All @@ -276,6 +286,7 @@ const Containers: React.FC<{}> = () => {
overReplicatedCount,
misReplicatedCount,
replicaMismatchCount,
quasiClosedCount,
} = state;

const currentTabState = tabStates[selectedTab];
Expand Down Expand Up @@ -310,6 +321,10 @@ const Containers: React.FC<{}> = () => {
Mismatched Replicas <br/>
<span className='highlight-content-value'>{replicaMismatchCount ?? 'N/A'}</span>
</div>
<div className='highlight-content'>
Quasi Closed <br/>
<span className='highlight-content-value'>{quasiClosedCount ?? 'N/A'}</span>
</div>
</div>
);

Expand Down Expand Up @@ -363,10 +378,10 @@ const Containers: React.FC<{}> = () => {
</div>
<Tabs defaultActiveKey='1'
onChange={(activeKey: string) => handleTabChange(activeKey)}>
{(['1','2','3','4','5'] as const).map((key) => (
{(['1','2','3','4','5','6'] as const).map((key) => (
<Tabs.TabPane
key={key}
tab={['Missing','Under-Replicated','Over-Replicated','Mis-Replicated','Mismatched Replicas'][Number(key)-1]}>
tab={['Missing','Under-Replicated','Over-Replicated','Mis-Replicated','Mismatched Replicas','Quasi Closed'][Number(key)-1]}>
<ContainerTable
data={tabStates[key].data}
loading={tabStates[key].loading}
Expand All @@ -381,6 +396,7 @@ const Containers: React.FC<{}> = () => {
hasPrevPage={tabStates[key].pageHistory.length > 0}
pageSize={pageSize}
onPageSizeChange={handlePageSizeChange}
sinceColumnTitle={key === '6' ? 'State Enter Time' : 'Unhealthy Since'}
/>
</Tabs.TabPane>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export type ContainersPaginationResponse = {
overReplicatedCount: number;
misReplicatedCount: number;
replicaMismatchCount: number;
quasiClosedCount?: number;
totalCount?: number;
}

export type TabPaginationState = {
Expand All @@ -98,6 +100,7 @@ export type ContainerTableProps = {
hasPrevPage: boolean;
pageSize: number;
onPageSizeChange: (newSize: number) => void;
sinceColumnTitle?: string;
}


Expand All @@ -121,4 +124,5 @@ export type ContainerState = {
overReplicatedCount: number;
misReplicatedCount: number;
replicaMismatchCount: number;
quasiClosedCount: number;
}
Loading