From f17a177c9064e894a46df550d2f255579495f83d Mon Sep 17 00:00:00 2001 From: Brad House Date: Mon, 9 Nov 2026 19:03:19 +0000 Subject: [PATCH] Return 534 when a network restart fails on an unavailable resource RestartNetworkCmd.execute() let ResourceUnavailableException, ConcurrentOperationException and InsufficientCapacityException reach the async job dispatcher, which logs them at ERROR as "Unexpected exception while executing" and fails the job with 530. RestartVPCCmd catches the same exceptions and maps them to ServerApiException, so a refused restartVPC fails with 534 and a WARN line. Catch them in RestartNetworkCmd the same way: 534 for ResourceUnavailableException, 530 for ConcurrentOperationException and 533 for InsufficientCapacityException, each with the exception message. Signed-off-by: Brad House --- .../user/network/RestartNetworkCmd.java | 27 ++++-- .../user/network/RestartNetworkCmdTest.java | 92 +++++++++++++++++++ 2 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 api/src/test/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmdTest.java diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java index c04a50cb05d8..39958ae73b33 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java @@ -32,7 +32,6 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.Network; @@ -93,13 +92,25 @@ public static String getResultObjectName() { } @Override - public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { - boolean result = _networkService.restartNetwork(this); - if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); - } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to restart network"); + public void execute() { + try { + boolean result = _networkService.restartNetwork(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to restart network"); + } + } catch (ResourceUnavailableException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (InsufficientCapacityException ex) { + logger.info(ex); + logger.trace(ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmdTest.java new file mode 100644 index 000000000000..a7dc3f069ae9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmdTest.java @@ -0,0 +1,92 @@ +// 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.cloudstack.api.command.user.network; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientNetworkCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.NetworkService; + +@RunWith(MockitoJUnitRunner.class) +public class RestartNetworkCmdTest { + + @Mock + NetworkService networkService; + @InjectMocks + RestartNetworkCmd cmd = new RestartNetworkCmd(); + + private ServerApiException executeExpectingFailure() { + try { + cmd.execute(); + } catch (ServerApiException e) { + return e; + } + Assert.fail("Expected a ServerApiException"); + return null; + } + + @Test + public void executeSetsSuccessResponse() throws Exception { + Mockito.when(networkService.restartNetwork(cmd)).thenReturn(true); + cmd.execute(); + Assert.assertTrue(cmd.getResponseObject() instanceof SuccessResponse); + } + + @Test + public void executeFailsWithInternalErrorWhenRestartReturnsFalse() throws Exception { + Mockito.when(networkService.restartNetwork(cmd)).thenReturn(false); + ServerApiException e = executeExpectingFailure(); + Assert.assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + } + + @Test + public void executeMapsResourceUnavailableToResourceUnavailableError() throws Exception { + ResourceUnavailableException cause = new ResourceUnavailableException("router host is not Up", Network.class, 1L); + Mockito.when(networkService.restartNetwork(cmd)).thenThrow(cause); + ServerApiException e = executeExpectingFailure(); + Assert.assertEquals(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, e.getErrorCode()); + Assert.assertEquals(cause.getMessage(), e.getDescription()); + } + + @Test + public void executeMapsConcurrentOperationToInternalError() throws Exception { + Mockito.when(networkService.restartNetwork(cmd)).thenThrow(new ConcurrentOperationException("busy")); + ServerApiException e = executeExpectingFailure(); + Assert.assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + Assert.assertEquals("busy", e.getDescription()); + } + + @Test + public void executeMapsInsufficientCapacityToInsufficientCapacityError() throws Exception { + Mockito.when(networkService.restartNetwork(cmd)).thenThrow(new InsufficientNetworkCapacityException("no capacity", Network.class, 1L)); + ServerApiException e = executeExpectingFailure(); + Assert.assertEquals(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, e.getErrorCode()); + Assert.assertEquals("no capacity", e.getDescription()); + } +}