minor cleanup and tests

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-04-29 17:55:29 +05:30
parent 574f0ea40a
commit 568c1aab7a
46 changed files with 5383 additions and 217 deletions

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.veeam;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -59,7 +60,7 @@ public class VeeamControlServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
String method = req.getMethod();
String path = normalize(req.getPathInfo());
String path = normalize(req);
Negotiation.OutFormat outFormat = Negotiation.responseFormat(req);
LOGGER.info("Received {} request for {} with out format: {}", method, path, outFormat);
@ -107,9 +108,15 @@ public class VeeamControlServlet extends HttpServlet {
}
}
private String normalize(String pathInfo) {
if (pathInfo == null || pathInfo.isBlank()) return "/";
return pathInfo;
private String normalize(HttpServletRequest req) {
String path = req.getPathInfo();
if (path == null || path.isBlank()) {
path = req.getRequestURI();
}
if (path == null || path.isBlank()) {
return "/";
}
return path;
}
protected void handleRoot(HttpServletRequest req, HttpServletResponse resp, Negotiation.OutFormat outFormat)
@ -117,13 +124,13 @@ public class VeeamControlServlet extends HttpServlet {
String method = req.getMethod();
if (!"GET".equals(method) && !"POST".equals(method)) {
// You didnt list 405; keep it simple with 400
throw Error.badRequest("Unsupported method for root: " + method);
}
Map<String, String> responseData = new HashMap<>();
responseData.put("name", VeeamControlService.PLUGIN_NAME);
responseData.put("pluginVersion", this.getClass().getPackage().getImplementationVersion());
writer.write(resp, 200, Map.of(
"name", VeeamControlService.PLUGIN_NAME,
"pluginVersion", this.getClass().getPackage().getImplementationVersion()), outFormat);
writer.write(resp, 200, responseData, outFormat);
}
public void methodNotAllowed(final HttpServletResponse resp, final String allow, final Negotiation.OutFormat outFormat) throws IOException {

View File

@ -26,17 +26,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
@ -58,7 +52,6 @@ import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd;
import org.apache.cloudstack.api.command.admin.vm.DeployVMCmdByAdmin;
import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd;
import org.apache.cloudstack.api.command.user.job.ListAsyncJobsCmd;
import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworksCmd;
import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd;
import org.apache.cloudstack.api.command.user.tag.ListTagsCmd;
@ -77,11 +70,8 @@ import org.apache.cloudstack.api.command.user.vmsnapshot.RevertToVMSnapshotCmd;
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.ListVolumesCmd;
import org.apache.cloudstack.api.command.user.volume.ResizeVolumeCmd;
import org.apache.cloudstack.api.command.user.volume.UpdateVolumeCmd;
import org.apache.cloudstack.api.command.user.zone.ListZonesCmd;
import org.apache.cloudstack.api.response.ListResponse;
@ -100,7 +90,6 @@ import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.TagsRouteHandler;
import org.apache.cloudstack.veeam.api.converter.AsyncJobJoinVOToJobConverter;
import org.apache.cloudstack.veeam.api.converter.BackupVOToBackupConverter;
import org.apache.cloudstack.veeam.api.converter.ClusterVOToClusterConverter;
@ -190,7 +179,6 @@ import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.DomainService;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.UserDataVO;
import com.cloud.user.dao.UserDataDao;
import com.cloud.uservm.UserVm;
@ -211,28 +199,7 @@ import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
// ToDo: check access for list APIs when not ROOT admin
public class ServerAdapter extends ManagerBase {
private static final String SERVICE_ACCOUNT_NAME = "veemserviceuser";
private static final String SERVICE_ACCOUNT_ROLE_NAME = "Veeam Service Role";
private static final String SERVICE_ACCOUNT_FIRST_NAME = "Veeam";
private static final String SERVICE_ACCOUNT_LAST_NAME = "Service User";
private static final List<Class<?>> SERVICE_ACCOUNT_ROLE_ALLOWED_APIS = Arrays.asList(
QueryAsyncJobResultCmd.class,
ListVMsCmd.class,
DeployVMCmd.class,
StartVMCmd.class,
StopVMCmd.class,
DestroyVMCmd.class,
ListVolumesCmd.class,
CreateVolumeCmd.class,
DeleteVolumeCmd.class,
AttachVolumeCmd.class,
DetachVolumeCmd.class,
ResizeVolumeCmd.class,
ListNetworksCmd.class
);
private static final List<Storage.StoragePoolType> SUPPORTED_STORAGE_TYPES = Arrays.asList(
Storage.StoragePoolType.Filesystem,
Storage.StoragePoolType.NetworkFilesystem,
@ -241,9 +208,6 @@ public class ServerAdapter extends ManagerBase {
private static final String VM_TA_KEY = "veeam_tag";
private static final String WORKER_VM_GUEST_CPU_MODE = "host-passthrough";
@Inject
RoleService roleService;
@Inject
AccountService accountService;
@ -346,17 +310,6 @@ public class ServerAdapter extends ManagerBase {
@Inject
DomainDao domainDao;
protected static Tag getDummyTagByName(String name) {
Tag tag = new Tag();
String id = UUID.nameUUIDFromBytes(String.format("veeam:%s", name.toLowerCase()).getBytes()).toString();
tag.setId(id);
tag.setName(name);
tag.setDescription(String.format("Default %s tag", name.toLowerCase()));
tag.setHref(VeeamControlService.ContextPath.value() + TagsRouteHandler.BASE_ROUTE + "/" + id);
tag.setParent(ResourceTagVOToTagConverter.getRootTagRef());
return tag;
}
protected static Map<String, Tag> getDummyTags() {
Map<String, Tag> tags = new HashMap<>();
Tag rootTag = ResourceTagVOToTagConverter.getRootTag();
@ -364,56 +317,6 @@ public class ServerAdapter extends ManagerBase {
return tags;
}
protected Role createServiceAccountRole() {
Role role = roleService.createRole(SERVICE_ACCOUNT_ROLE_NAME, RoleType.User,
SERVICE_ACCOUNT_ROLE_NAME, false);
for (Class<?> allowedApi : SERVICE_ACCOUNT_ROLE_ALLOWED_APIS) {
final String apiName = BaseCmd.getCommandNameByClass(allowedApi);
roleService.createRolePermission(role, new Rule(apiName), RolePermissionEntity.Permission.ALLOW,
String.format("Allow %s", apiName));
}
roleService.createRolePermission(role, new Rule("*"), RolePermissionEntity.Permission.DENY,
"Deny all");
logger.debug("Created default role for Veeam service account in projects: {}", role);
return role;
}
protected Role getServiceAccountRole() {
List<Role> roles = roleService.findRolesByName(SERVICE_ACCOUNT_ROLE_NAME);
if (CollectionUtils.isNotEmpty(roles)) {
Role role = roles.get(0);
logger.debug("Found default role for Veeam service account in projects: {}", role);
return role;
}
return createServiceAccountRole();
}
protected UserAccount createServiceAccount() {
CallContext.register(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM);
try {
Role role = getServiceAccountRole();
UserAccount userAccount = accountService.createUserAccount(SERVICE_ACCOUNT_NAME,
UUID.randomUUID().toString(), SERVICE_ACCOUNT_FIRST_NAME,
SERVICE_ACCOUNT_LAST_NAME, null, null, SERVICE_ACCOUNT_NAME, Account.Type.NORMAL, role.getId(),
1L, null, null, null, null, User.Source.NATIVE);
logger.debug("Created Veeam service account: {}", userAccount);
return userAccount;
} finally {
CallContext.unregister();
}
}
protected Pair<User, Account> getDefaultServiceAccount() {
UserAccount userAccount = accountService.getActiveUserAccount(SERVICE_ACCOUNT_NAME, 1L);
if (userAccount == null) {
userAccount = createServiceAccount();
} else {
logger.debug("Veeam service user account found: {}", userAccount);
}
return new Pair<>(accountService.getActiveUser(userAccount.getId()),
accountService.getActiveAccountById(userAccount.getAccountId()));
}
protected void waitForJobCompletion(long jobId) {
long timeoutNanos = TimeUnit.MINUTES.toNanos(5);
final long deadline = System.nanoTime() + timeoutNanos;

View File

@ -1,110 +0,0 @@
// 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.veeam.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.VeeamControlServlet;
public class BasicAuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// no-op
}
@Override
public void destroy() {
// no-op
}
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain
) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String expectedUser = VeeamControlService.Username.value();
String expectedPass = VeeamControlService.Password.value();
String auth = req.getHeader("Authorization");
if (auth == null || !auth.regionMatches(true, 0, "Basic ", 0, 6)) {
unauthorized(resp);
return;
}
String decoded;
try {
decoded = new String(
Base64.getDecoder().decode(auth.substring(6)),
StandardCharsets.UTF_8
);
} catch (IllegalArgumentException e) {
unauthorized(resp);
return;
}
int idx = decoded.indexOf(':');
if (idx <= 0) {
unauthorized(resp);
return;
}
String user = decoded.substring(0, idx);
String pass = decoded.substring(idx + 1);
if (!constantTimeEquals(user, expectedUser)
|| !constantTimeEquals(pass, expectedPass)) {
unauthorized(resp);
return;
}
chain.doFilter(request, response);
}
private void unauthorized(HttpServletResponse resp) {
throw VeeamControlServlet.Error.unauthorized("Unauthorized");
}
private boolean constantTimeEquals(String a, String b) {
byte[] x = a.getBytes(StandardCharsets.UTF_8);
byte[] y = b.getBytes(StandardCharsets.UTF_8);
if (x.length != y.length) return false;
int r = 0;
for (int i = 0; i < x.length; i++) {
r |= x[i] ^ y[i];
}
return r == 0;
}
}

View File

@ -0,0 +1,227 @@
// 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.veeam;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.apache.cloudstack.utils.server.ServerPropertiesUtil;
import org.eclipse.jetty.server.Server;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class VeeamControlServerTest {
private static final String KEY_ENABLED = "integration.veeam.control.enabled";
private static final String KEY_PORT = "integration.veeam.control.port";
private static final String KEY_CONTEXT_PATH = "integration.veeam.control.context.path";
private static final String KEY_DEVELOPER_LOGS = "integration.veeam.control.developer.logs";
private ConfigDepotImpl previousDepot;
private Properties previousServerProperties;
private final Map<String, String> globalValues = new HashMap<>();
@Before
public void setUp() throws Exception {
previousDepot = getConfigDepot();
final ConfigDepotImpl depot = mock(ConfigDepotImpl.class);
when(depot.getConfigStringValue(Mockito.anyString(), Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
final String key = invocation.getArgument(0);
final Scope scope = invocation.getArgument(1);
if (scope == Scope.Global) {
return globalValues.get(key);
}
return null;
});
setConfigDepot(depot);
previousServerProperties = getServerProperties();
Properties props = new Properties();
props.setProperty("https.keystore", "");
props.setProperty("https.keystore.password", "");
setServerProperties(props);
}
@After
public void tearDown() throws Exception {
setConfigDepot(previousDepot);
setServerProperties(previousServerProperties);
resetConfigKeyCache(VeeamControlService.Enabled);
resetConfigKeyCache(VeeamControlService.Port);
resetConfigKeyCache(VeeamControlService.ContextPath);
resetConfigKeyCache(VeeamControlService.DeveloperLogs);
}
@Test
public void testConstructorSortsHandlersByPriorityDescending() throws Exception {
final RouteHandler low = mock(RouteHandler.class);
final RouteHandler high = mock(RouteHandler.class);
when(low.priority()).thenReturn(1);
when(high.priority()).thenReturn(10);
final VeeamControlServer server = new VeeamControlServer(List.of(low, high), mock(VeeamControlService.class));
final List<RouteHandler> handlers = getRouteHandlers(server);
assertEquals(high, handlers.get(0));
assertEquals(low, handlers.get(1));
}
@Test
public void testStartIfEnabledReturnsWithoutStartingWhenDisabled() throws Exception {
globalValues.put(KEY_ENABLED, "false");
resetConfigKeyCache(VeeamControlService.Enabled);
final VeeamControlServer server = new VeeamControlServer(List.of(), mock(VeeamControlService.class));
server.startIfEnabled();
assertNull(getJettyServer(server));
}
@Test
public void testStartIfEnabledStartsHttpServerWhenEnabled() throws Exception {
globalValues.put(KEY_ENABLED, "true");
globalValues.put(KEY_PORT, "0");
globalValues.put(KEY_CONTEXT_PATH, "/ovirt-engine");
resetConfigKeyCache(VeeamControlService.Enabled);
resetConfigKeyCache(VeeamControlService.Port);
resetConfigKeyCache(VeeamControlService.ContextPath);
final VeeamControlService service = mock(VeeamControlService.class);
when(service.getCurrentManagementServerHostId()).thenReturn(1L);
final VeeamControlServer server = new VeeamControlServer(List.of(), service);
try {
server.startIfEnabled();
final Server jetty = getJettyServer(server);
assertNotNull(jetty);
assertTrue(jetty.isStarted());
} finally {
server.stop();
}
}
@Test
public void testStopStopsExistingJettyServerAndClearsReference() throws Exception {
final VeeamControlServer server = new VeeamControlServer(List.of(), mock(VeeamControlService.class));
final Server jetty = mock(Server.class);
setJettyServer(server, jetty);
server.stop();
verify(jetty).stop();
assertNull(getJettyServer(server));
}
@Test
public void testGetRequestResponseMetadataIncludesHeadersWhenDeveloperLogsEnabled() throws Exception {
globalValues.put(KEY_DEVELOPER_LOGS, "true");
resetConfigKeyCache(VeeamControlService.DeveloperLogs);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api");
request.setRemoteAddr("127.0.0.1");
request.setQueryString("x=1");
request.addHeader("X-Test", "abc");
final MockHttpServletResponse response = new MockHttpServletResponse();
response.setStatus(202);
final Method metadataMethod = VeeamControlServer.class.getDeclaredMethod("getRequestResponseMetadata",
HttpServletRequest.class, HttpServletResponse.class);
metadataMethod.setAccessible(true);
final String metadata = (String) metadataMethod.invoke(null, request, response);
assertTrue(metadata.contains("remote address: 127.0.0.1"));
assertTrue(metadata.contains("uri: /api?x=1"));
assertTrue(metadata.contains("headers: [X-Test=abc;"));
assertTrue(metadata.contains("status: 202"));
}
@SuppressWarnings("unchecked")
private static List<RouteHandler> getRouteHandlers(final VeeamControlServer server) throws Exception {
final Field field = VeeamControlServer.class.getDeclaredField("routeHandlers");
field.setAccessible(true);
return (List<RouteHandler>) field.get(server);
}
private static Server getJettyServer(final VeeamControlServer server) throws Exception {
final Field field = VeeamControlServer.class.getDeclaredField("server");
field.setAccessible(true);
return (Server) field.get(server);
}
private static void setJettyServer(final VeeamControlServer server, final Server jetty) throws Exception {
final Field field = VeeamControlServer.class.getDeclaredField("server");
field.setAccessible(true);
field.set(server, jetty);
}
private static ConfigDepotImpl getConfigDepot() throws Exception {
final Field field = ConfigKey.class.getDeclaredField("s_depot");
field.setAccessible(true);
return (ConfigDepotImpl) field.get(null);
}
private static void setConfigDepot(final ConfigDepotImpl depot) throws Exception {
final Field field = ConfigKey.class.getDeclaredField("s_depot");
field.setAccessible(true);
field.set(null, depot);
}
@SuppressWarnings("unchecked")
private static Properties getServerProperties() throws Exception {
final Field field = ServerPropertiesUtil.class.getDeclaredField("propertiesRef");
field.setAccessible(true);
final AtomicReference<Properties> ref = (AtomicReference<Properties>) field.get(null);
return ref.get();
}
@SuppressWarnings("unchecked")
private static void setServerProperties(final Properties properties) throws Exception {
final Field field = ServerPropertiesUtil.class.getDeclaredField("propertiesRef");
field.setAccessible(true);
final AtomicReference<Properties> ref = (AtomicReference<Properties>) field.get(null);
ref.set(properties);
}
private static void resetConfigKeyCache(final ConfigKey<?> configKey) throws Exception {
final Field valueField = ConfigKey.class.getDeclaredField("_value");
valueField.setAccessible(true);
valueField.set(configKey, null);
}
}

View File

@ -16,25 +16,170 @@
// under the License.
package org.apache.cloudstack.veeam;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.ConfigKey.Scope;
import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl;
import org.apache.cloudstack.veeam.api.dto.ImageTransfer;
import org.apache.cloudstack.veeam.utils.Mapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.utils.UuidUtils;
@RunWith(MockitoJUnitRunner.class)
public class VeeamControlServiceImplTest {
private static final String KEY_ENABLED = "integration.veeam.control.enabled";
private static final String KEY_PORT = "integration.veeam.control.port";
private static final String KEY_USERNAME = "integration.veeam.control.api.username";
private static final String KEY_PASSWORD = "integration.veeam.control.api.password";
private static final String KEY_ALLOWED_CIDRS = "integration.veeam.control.allowed.client.cidrs";
private ConfigDepotImpl previousDepot;
private final Map<String, String> globalValues = new HashMap<>();
@Before
public void setUp() throws Exception {
previousDepot = getConfigDepot();
final ConfigDepotImpl depot = mock(ConfigDepotImpl.class);
when(depot.getConfigStringValue(Mockito.anyString(), Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
final String key = invocation.getArgument(0);
final Scope scope = invocation.getArgument(1);
if (scope == Scope.Global) {
return globalValues.get(key);
}
return null;
});
setConfigDepot(depot);
}
@After
public void tearDown() throws Exception {
setConfigDepot(previousDepot);
resetConfigKeyCache(VeeamControlService.Enabled);
resetConfigKeyCache(VeeamControlService.Port);
resetConfigKeyCache(VeeamControlService.Username);
resetConfigKeyCache(VeeamControlService.Password);
resetConfigKeyCache(VeeamControlService.AllowedClientCidrs);
}
@Test
public void test_parseImageTransfer() {
public void testParseImageTransfer() {
String data = "{\"active\":false,\"direction\":\"upload\",\"format\":\"cow\",\"inactivity_timeout\":3600,\"phase\":\"cancelled\",\"shallow\":false,\"transferred\":0,\"link\":[],\"disk\":{\"id\":\"dba4d72d-01de-4267-aa8e-305996b53599\"},\"image\":{},\"backup\":{\"creation_date\":0}}";
Mapper mapper = new Mapper();
try {
ImageTransfer request = mapper.jsonMapper().readValue(data, ImageTransfer.class);
mapper.jsonMapper().readValue(data, ImageTransfer.class);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Test
public void testGetAllowedClientCidrsInternalSanitizesAndFiltersInvalidEntries() throws Exception {
globalValues.put(KEY_ALLOWED_CIDRS, " 10.0.0.0/24,invalid-cidr, ,192.168.1.100/32 ");
resetConfigKeyCache(VeeamControlService.AllowedClientCidrs);
final VeeamControlServiceImpl service = new VeeamControlServiceImpl();
final List<String> cidrs = service.getAllowedClientCidrsInternal();
assertEquals(List.of("10.0.0.0/24", "192.168.1.100/32"), cidrs);
}
@Test
public void testGetAllowedClientCidrsInternalReturnsEmptyListForBlankValue() throws Exception {
globalValues.put(KEY_ALLOWED_CIDRS, " ");
resetConfigKeyCache(VeeamControlService.AllowedClientCidrs);
final VeeamControlServiceImpl service = new VeeamControlServiceImpl();
assertTrue(service.getAllowedClientCidrsInternal().isEmpty());
}
@Test
public void testValidateCredentials() throws Exception {
globalValues.put(KEY_USERNAME, "veeam-user");
globalValues.put(KEY_PASSWORD, "veeam-pass");
resetConfigKeyCache(VeeamControlService.Username);
resetConfigKeyCache(VeeamControlService.Password);
final VeeamControlServiceImpl service = new VeeamControlServiceImpl();
assertTrue(service.validateCredentials("veeam-user", "veeam-pass"));
assertFalse(service.validateCredentials("veeam-user", "wrong"));
assertFalse(service.validateCredentials("wrong", "veeam-pass"));
}
@Test
public void testGetInstanceIdReturnsSystemAccountUuid() {
final VeeamControlServiceImpl service = new VeeamControlServiceImpl();
final AccountService accountService = mock(AccountService.class);
final Account account = mock(Account.class);
service.accountService = accountService;
when(accountService.getSystemAccount()).thenReturn(account);
when(account.getUuid()).thenReturn("system-account-uuid");
assertEquals("system-account-uuid", service.getInstanceId());
}
@Test
public void testGetHmacSecretUsesConfiguredInputs() throws Exception {
globalValues.put(KEY_PORT, "8095");
globalValues.put(KEY_USERNAME, "api-user");
globalValues.put(KEY_PASSWORD, "api-pass");
resetConfigKeyCache(VeeamControlService.Port);
resetConfigKeyCache(VeeamControlService.Username);
resetConfigKeyCache(VeeamControlService.Password);
final VeeamControlServiceImpl service = Mockito.spy(new VeeamControlServiceImpl());
Mockito.doReturn("instance-uuid").when(service).getInstanceId();
final String expected = UuidUtils.nameUUIDFromBytes(
"instance-uuid:8095:api-userapi-pass".getBytes(StandardCharsets.UTF_8)).toString();
assertEquals(expected, service.getHmacSecret());
}
@Test
public void testGetConfigKeysContainsExpectedEntries() {
final VeeamControlServiceImpl service = new VeeamControlServiceImpl();
final ConfigKey<?>[] keys = service.getConfigKeys();
assertEquals(10, keys.length);
assertEquals(KEY_ENABLED, keys[0].key());
}
private static ConfigDepotImpl getConfigDepot() throws Exception {
final Field field = ConfigKey.class.getDeclaredField("s_depot");
field.setAccessible(true);
return (ConfigDepotImpl) field.get(null);
}
private static void setConfigDepot(final ConfigDepotImpl depot) throws Exception {
final Field field = ConfigKey.class.getDeclaredField("s_depot");
field.setAccessible(true);
field.set(null, depot);
}
private static void resetConfigKeyCache(final ConfigKey<?> configKey) throws Exception {
final Field valueField = ConfigKey.class.getDeclaredField("_value");
valueField.setAccessible(true);
valueField.set(configKey, null);
}
}

View File

@ -0,0 +1,121 @@
// 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.veeam;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
public class VeeamControlServletTest {
@Test
public void testServiceHandlesRootRequestForGet() throws Exception {
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.emptyList());
final HttpServletRequest request = new MockHttpServletRequest("GET", "/");
final MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals(200, response.getStatus());
assertTrue(response.getContentAsString().contains("CloudStack Veeam Control Service"));
}
@Test
public void testServiceReturnsBadRequestForUnsupportedRootMethod() throws Exception {
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.emptyList());
final HttpServletRequest request = new MockHttpServletRequest("PUT", "/");
final MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals(400, response.getStatus());
assertTrue(response.getContentAsString().contains("Unsupported method for root"));
}
@Test
public void testServiceDelegatesToMatchingRouteHandler() throws Exception {
final RouteHandler handler = mock(RouteHandler.class);
final String path = "/api/path";
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.singletonList(handler));
final HttpServletRequest request = new MockHttpServletRequest("GET", path);
final MockHttpServletResponse response = new MockHttpServletResponse();
when(handler.canHandle("GET", path)).thenReturn(true);
servlet.service(request, response);
verify(handler).handle(request, response, path, Negotiation.OutFormat.XML, servlet);
}
@Test
public void testServiceReturnsNotFoundWhenNoHandlerMatches() throws Exception {
final RouteHandler handler = mock(RouteHandler.class);
final String path = "/api/path";
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.singletonList(handler));
final MockHttpServletRequest request = new MockHttpServletRequest("GET", path);
request.setPathInfo(path);
request.addHeader("Accept", "application/json");
final MockHttpServletResponse response = new MockHttpServletResponse();
when(handler.canHandle("GET", path)).thenReturn(false);
servlet.service(request, response);
assertEquals(404, response.getStatus());
assertTrue(response.getContentAsString().contains("\"reason\":\"Not found\""));
verify(handler).canHandle("GET", path);
}
@Test
public void testServiceConvertsHandlerErrorToFaultResponse() throws Exception {
final RouteHandler handler = mock(RouteHandler.class);
final String path = "/api/faultpath";
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.singletonList(handler));
final HttpServletRequest request = new MockHttpServletRequest("GET", path);
final MockHttpServletResponse response = new MockHttpServletResponse();
when(handler.canHandle("GET", path)).thenReturn(true);
doThrow(VeeamControlServlet.Error.unauthorized("denied")).when(handler)
.handle(request, response, path, Negotiation.OutFormat.XML, servlet);
servlet.service(request, response);
assertEquals(401, response.getStatus());
assertTrue(response.getContentAsString().contains("denied"));
}
@Test
public void testMethodNotAllowedWritesAllowHeaderAndFault() throws Exception {
final VeeamControlServlet servlet = new VeeamControlServlet(Collections.emptyList());
final MockHttpServletResponse response = new MockHttpServletResponse();
servlet.methodNotAllowed(response, "GET, POST", Negotiation.OutFormat.JSON);
assertEquals(405, response.getStatus());
assertEquals("GET, POST", response.getHeader("Allow"));
}
}

View File

@ -0,0 +1,204 @@
// 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.veeam.adapter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.user.zone.ListZonesCmd;
import org.apache.cloudstack.context.CallContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
import com.cloud.utils.Pair;
public class ApiAccessInterceptorTest {
private static final long BASE_USER_ID = 100L;
private static final long BASE_ACCOUNT_ID = 200L;
private static final long SERVICE_USER_ID = 300L;
private static final long SERVICE_ACCOUNT_ID = 400L;
private final ApiAccessInterceptor interceptor = new ApiAccessInterceptor();
private final AccountManager accountManager = mock(AccountManager.class);
@Before
public void setUp() {
interceptor.accountManager = accountManager;
CallContext.unregisterAll();
}
@After
public void tearDown() {
CallContext.unregisterAll();
}
@Test
public void testInvokePassesThroughWhenTargetIsNull() throws Throwable {
final MethodInvocation invocation = mock(MethodInvocation.class);
when(invocation.getThis()).thenReturn(null);
when(invocation.proceed()).thenReturn("ok");
final Object result = interceptor.invoke(invocation);
assertEquals("ok", result);
verify(invocation).proceed();
verifyNoInteractions(accountManager);
}
@Test
public void testInvokePassesThroughWhenMethodHasNoApiAccessAnnotation() throws Throwable {
final TestServerAdapter adapter = new TestServerAdapter(serviceUserAccount());
final MethodInvocation invocation = mock(MethodInvocation.class);
when(invocation.getThis()).thenReturn(adapter);
when(invocation.getMethod()).thenReturn(TestServerAdapter.class.getMethod("noApiAccess"));
when(invocation.proceed()).thenReturn("done");
final Object result = interceptor.invoke(invocation);
assertEquals("done", result);
verify(invocation).proceed();
verifyNoInteractions(accountManager);
}
@Test
public void testInvokeChecksApiAccessForDirectlyAnnotatedMethodAndRestoresCallContext() throws Throwable {
registerBaseContext();
final TestServerAdapter adapter = new TestServerAdapter(serviceUserAccount());
final MethodInvocation invocation = mock(MethodInvocation.class);
when(invocation.getThis()).thenReturn(adapter);
when(invocation.getMethod()).thenReturn(TestServerAdapter.class.getMethod("classAnnotated"));
when(invocation.proceed()).thenAnswer(i -> {
assertEquals(SERVICE_USER_ID, CallContext.current().getCallingUserId());
assertEquals(SERVICE_ACCOUNT_ID, CallContext.current().getCallingAccountId());
return "secured";
});
final Object result = interceptor.invoke(invocation);
assertEquals("secured", result);
verify(accountManager).checkApiAccess(adapter.getServiceAccount().second(),
BaseCmd.getCommandNameByClass(ListZonesCmd.class));
assertEquals(BASE_USER_ID, CallContext.current().getCallingUserId());
assertEquals(BASE_ACCOUNT_ID, CallContext.current().getCallingAccountId());
}
@Test
public void testInvokeFindsAnnotationOnImplementationWhenInterfaceMethodIsUnannotated() throws Throwable {
registerBaseContext();
final TestServerAdapter adapter = new TestServerAdapter(serviceUserAccount());
final Method interfaceMethod = ApiContract.class.getMethod("implAnnotatedThroughInterface");
final MethodInvocation invocation = mock(MethodInvocation.class);
when(invocation.getThis()).thenReturn(adapter);
when(invocation.getMethod()).thenReturn(interfaceMethod);
when(invocation.proceed()).thenReturn("ok");
final Object result = interceptor.invoke(invocation);
assertEquals("ok", result);
verify(accountManager).checkApiAccess(adapter.getServiceAccount().second(),
BaseCmd.getCommandNameByClass(ListZonesCmd.class));
assertEquals(BASE_USER_ID, CallContext.current().getCallingUserId());
assertEquals(BASE_ACCOUNT_ID, CallContext.current().getCallingAccountId());
}
@Test
public void testInvokeUnregistersServiceContextWhenProceedThrows() throws Throwable {
registerBaseContext();
final TestServerAdapter adapter = new TestServerAdapter(serviceUserAccount());
final MethodInvocation invocation = mock(MethodInvocation.class);
final RuntimeException expected = new RuntimeException("boom");
when(invocation.getThis()).thenReturn(adapter);
when(invocation.getMethod()).thenReturn(TestServerAdapter.class.getMethod("classAnnotated"));
when(invocation.proceed()).thenThrow(expected);
try {
interceptor.invoke(invocation);
} catch (RuntimeException e) {
assertSame(expected, e);
}
verify(accountManager).checkApiAccess(adapter.getServiceAccount().second(),
BaseCmd.getCommandNameByClass(ListZonesCmd.class));
assertEquals(BASE_USER_ID, CallContext.current().getCallingUserId());
assertEquals(BASE_ACCOUNT_ID, CallContext.current().getCallingAccountId());
}
private static void registerBaseContext() {
final User baseUser = mock(User.class);
final Account baseAccount = mock(Account.class);
when(baseUser.getId()).thenReturn(BASE_USER_ID);
when(baseAccount.getId()).thenReturn(BASE_ACCOUNT_ID);
CallContext.register(baseUser, baseAccount);
}
private static Pair<User, Account> serviceUserAccount() {
final User serviceUser = mock(User.class);
final Account serviceAccount = mock(Account.class);
when(serviceUser.getId()).thenReturn(SERVICE_USER_ID);
when(serviceAccount.getId()).thenReturn(SERVICE_ACCOUNT_ID);
return new Pair<>(serviceUser, serviceAccount);
}
private interface ApiContract {
String implAnnotatedThroughInterface();
}
private static class TestServerAdapter extends ServerAdapter implements ApiContract {
private final Pair<User, Account> serviceAccount;
private TestServerAdapter(final Pair<User, Account> serviceAccount) {
this.serviceAccount = serviceAccount;
}
@Override
public Pair<User, Account> getServiceAccount() {
return serviceAccount;
}
@ApiAccess(command = ListZonesCmd.class)
public String classAnnotated() {
return "classAnnotated";
}
@Override
@ApiAccess(command = ListZonesCmd.class)
public String implAnnotatedThroughInterface() {
return "implAnnotatedThroughInterface";
}
public String noApiAccess() {
return "noApiAccess";
}
}
}

View File

@ -0,0 +1,972 @@
// 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.veeam.adapter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.backup.BackupVO;
import org.apache.cloudstack.backup.dao.BackupDao;
import org.apache.cloudstack.backup.dao.ImageTransferDao;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.jobs.dao.AsyncJobDao;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.api.dto.Vm;
import org.junit.After;
import org.junit.Before;
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.api.query.dao.AsyncJobJoinDao;
import com.cloud.api.query.dao.DataCenterJoinDao;
import com.cloud.api.query.dao.HostJoinDao;
import com.cloud.api.query.dao.StoragePoolJoinDao;
import com.cloud.api.query.dao.UserVmJoinDao;
import com.cloud.api.query.dao.VolumeJoinDao;
import com.cloud.api.query.vo.AsyncJobJoinVO;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.api.query.vo.HostJoinVO;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.api.query.vo.VolumeJoinVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.NetworkModel;
import com.cloud.network.Networks;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager;
import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
@RunWith(MockitoJUnitRunner.class)
public class ServerAdapterTest {
@InjectMocks
ServerAdapter serverAdapter;
@Mock AccountService accountService;
@Mock DataCenterDao dataCenterDao;
@Mock DataCenterJoinDao dataCenterJoinDao;
@Mock StoragePoolJoinDao storagePoolJoinDao;
@Mock ClusterDao clusterDao;
@Mock HostJoinDao hostJoinDao;
@Mock NetworkDao networkDao;
@Mock UserVmDao userVmDao;
@Mock UserVmJoinDao userVmJoinDao;
@Mock VolumeDao volumeDao;
@Mock VolumeJoinDao volumeJoinDao;
// kept minimal: only mocks used directly by tests
@Mock com.cloud.storage.VolumeApiService volumeApiService;
@Mock PrimaryDataStoreDao primaryDataStoreDao;
@Mock ImageTransferDao imageTransferDao;
@Mock ServiceOfferingDao serviceOfferingDao;
@Mock VMTemplateDao templateDao;
@Mock UserVmManager userVmManager;
@Mock AsyncJobDao asyncJobDao;
@Mock AsyncJobJoinDao asyncJobJoinDao;
@Mock VMSnapshotDao vmSnapshotDao;
@Mock BackupDao backupDao;
@Mock NetworkModel networkModel;
@Mock ProjectManager projectManager;
@Mock DomainDao domainDao;
@Before
public void setupCallContext() {
CallContext.register(Mockito.mock(User.class), Mockito.mock(Account.class));
}
@After
public void cleanupCallContext() {
CallContext.unregister();
}
@Test
public void testGetProvisionedSizeInGb_ExactlyOneGB() {
long gb = 1024L * 1024L * 1024L;
assertEquals(1L, ServerAdapter.getProvisionedSizeInGb(String.valueOf(gb)));
}
@Test
public void testGetProvisionedSizeInGb_MultipleGB() {
long gb = 1024L * 1024L * 1024L;
assertEquals(5L, ServerAdapter.getProvisionedSizeInGb(String.valueOf(5 * gb)));
}
@Test
public void testGetProvisionedSizeInGb_LessThanOneGB_RoundsUpToOne() {
assertEquals(1L, ServerAdapter.getProvisionedSizeInGb("512"));
}
@Test
public void testGetProvisionedSizeInGb_NotExactGB_RoundsUp() {
long gb = 1024L * 1024L * 1024L;
assertEquals(2L, ServerAdapter.getProvisionedSizeInGb(String.valueOf(gb + gb / 2)));
}
@Test(expected = InvalidParameterValueException.class)
public void testGetProvisionedSizeInGb_InvalidString_Throws() {
ServerAdapter.getProvisionedSizeInGb("not-a-number");
}
@Test(expected = InvalidParameterValueException.class)
public void testGetProvisionedSizeInGb_Zero_Throws() {
ServerAdapter.getProvisionedSizeInGb("0");
}
@Test(expected = InvalidParameterValueException.class)
public void testGetProvisionedSizeInGb_Negative_Throws() {
ServerAdapter.getProvisionedSizeInGb("-1073741824");
}
@Test
public void testGetDetailsForInstanceCreation_WithUserdata_AddsCpuMode() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(false);
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation("#!/bin/bash", offering, null);
assertEquals("host-passthrough", result.get(VmDetailConstants.GUEST_CPU_MODE));
}
@Test
public void testGetDetailsForInstanceCreation_NoUserdata_NoCpuMode() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(false);
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation(null, offering, null);
assertFalse(result.containsKey(VmDetailConstants.GUEST_CPU_MODE));
}
@Test
public void testGetDetailsForInstanceCreation_CustomizedOffering_AddsDetails() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(true);
when(offering.getCpu()).thenReturn(4);
when(offering.getRamSize()).thenReturn(2048);
when(offering.getSpeed()).thenReturn(null);
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation(null, offering, null);
assertEquals("4", result.get(VmDetailConstants.CPU_NUMBER));
assertEquals("2048", result.get(VmDetailConstants.MEMORY));
assertEquals("1000", result.get(VmDetailConstants.CPU_SPEED));
}
@Test
public void testGetDetailsForInstanceCreation_CustomizedOffering_WithSpeed_DoesNotAddDefaultCpuSpeed() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(true);
when(offering.getCpu()).thenReturn(2);
when(offering.getRamSize()).thenReturn(1024);
when(offering.getSpeed()).thenReturn(2000);
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation(null, offering, null);
assertFalse(result.containsKey(VmDetailConstants.CPU_SPEED));
}
@Test
public void testGetDetailsForInstanceCreation_SkipsBiosAndUefiKeys() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(false);
Map<String, String> existingDetails = new HashMap<>();
existingDetails.put("BIOS", "bios_value");
existingDetails.put("UEFI", "uefi_value");
existingDetails.put("custom_key", "custom_value");
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation(null, offering, existingDetails);
assertFalse(result.containsKey("BIOS"));
assertFalse(result.containsKey("UEFI"));
assertEquals("custom_value", result.get("custom_key"));
}
@Test
public void testGetDetailsForInstanceCreation_PreservesExistingCpuSpeed() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
when(offering.isCustomized()).thenReturn(true);
when(offering.getCpu()).thenReturn(2);
when(offering.getRamSize()).thenReturn(1024);
when(offering.getSpeed()).thenReturn(null);
Map<String, String> existingDetails = new HashMap<>();
existingDetails.put(VmDetailConstants.CPU_SPEED, "3000");
Map<String, String> result = ServerAdapter.getDetailsForInstanceCreation(null, offering, existingDetails);
assertEquals("3000", result.get(VmDetailConstants.CPU_SPEED));
}
@Test
public void testGetDummyTags_ContainsRootTag() {
Map<String, Tag> tags = ServerAdapter.getDummyTags();
assertNotNull(tags);
assertFalse(tags.isEmpty());
}
@Test
public void testGetTemplateForInstanceCreation_NullUuid_ReturnsNull() {
assertNull(serverAdapter.getTemplateForInstanceCreation(null));
}
@Test
public void testGetTemplateForInstanceCreation_BlankUuid_ReturnsNull() {
assertNull(serverAdapter.getTemplateForInstanceCreation(" "));
}
@Test
public void testGetTemplateForInstanceCreation_TemplateNotFound_ReturnsNull() {
when(templateDao.findByUuid("missing-uuid")).thenReturn(null);
assertNull(serverAdapter.getTemplateForInstanceCreation("missing-uuid"));
}
@Test
public void testGetTemplateForInstanceCreation_TemplateFound_ReturnsTemplate() {
VMTemplateVO template = mock(VMTemplateVO.class);
when(templateDao.findByUuid("valid-uuid")).thenReturn(template);
assertEquals(template, serverAdapter.getTemplateForInstanceCreation("valid-uuid"));
}
@Test
public void testGetZoneById_NullId_ReturnsNull() {
assertNull(serverAdapter.getZoneById(null));
}
@Test
public void testGetZoneById_ReturnsVoFromDao() {
DataCenterJoinVO vo = mock(DataCenterJoinVO.class);
when(dataCenterJoinDao.findById(1L)).thenReturn(vo);
assertEquals(vo, serverAdapter.getZoneById(1L));
}
@Test
public void testGetHostById_NullId_ReturnsNull() {
assertNull(serverAdapter.getHostById(null));
}
@Test
public void testGetHostById_ReturnsVoFromDao() {
HostJoinVO vo = mock(HostJoinVO.class);
when(hostJoinDao.findById(2L)).thenReturn(vo);
assertEquals(vo, serverAdapter.getHostById(2L));
}
@Test
public void testGetVolumeById_NullId_ReturnsNull() {
assertNull(serverAdapter.getVolumeById(null));
}
@Test
public void testGetVolumeById_ReturnsVoFromDao() {
VolumeJoinVO vo = mock(VolumeJoinVO.class);
when(volumeJoinDao.findById(3L)).thenReturn(vo);
assertEquals(vo, serverAdapter.getVolumeById(3L));
}
@Test
public void testGetNetworkById_NullId_ReturnsNull() {
assertNull(serverAdapter.getNetworkById(null));
}
@Test
public void testGetNetworkById_ReturnsVoFromDao() {
NetworkVO vo = mock(NetworkVO.class);
when(networkDao.findById(4L)).thenReturn(vo);
assertEquals(vo, serverAdapter.getNetworkById(4L));
}
@Test
public void testWaitForJobCompletion_JobNotFound_Returns() {
when(asyncJobDao.findById(99L)).thenReturn(null);
serverAdapter.waitForJobCompletion(99L);
verify(asyncJobDao).findById(99L);
}
@Test
public void testWaitForJobCompletion_JobAlreadySucceeded_Returns() {
AsyncJobVO job = mock(AsyncJobVO.class);
when(job.getStatus()).thenReturn(AsyncJobVO.Status.SUCCEEDED);
when(asyncJobDao.findById(1L)).thenReturn(job);
serverAdapter.waitForJobCompletion(1L);
}
@Test
public void testWaitForJobCompletion_JobAlreadyFailed_Returns() {
AsyncJobVO job = mock(AsyncJobVO.class);
when(job.getStatus()).thenReturn(AsyncJobVO.Status.FAILED);
when(asyncJobDao.findById(2L)).thenReturn(job);
serverAdapter.waitForJobCompletion(2L);
}
@Test
public void testWaitForJobCompletion_NullJobJoinVO_Returns() {
AsyncJobJoinVO job = null;
serverAdapter.waitForJobCompletion(job);
}
@Test
public void testWaitForJobCompletion_CompletedJobJoinVO_DelegatesById() {
AsyncJobJoinVO jobVO = mock(AsyncJobJoinVO.class);
when(jobVO.getStatus()).thenReturn(AsyncJobVO.Status.SUCCEEDED.ordinal());
when(jobVO.getId()).thenReturn(5L);
AsyncJobVO job = mock(AsyncJobVO.class);
when(job.getStatus()).thenReturn(AsyncJobVO.Status.SUCCEEDED);
when(asyncJobDao.findById(5L)).thenReturn(job);
serverAdapter.waitForJobCompletion(jobVO);
verify(asyncJobDao).findById(5L);
}
@Test
public void testGetOwnerDetailsForInstanceCreation_NullAccount_ReturnsAllNulls() {
Ternary<Long, String, Long> result = serverAdapter.getOwnerDetailsForInstanceCreation(null);
assertNull(result.first());
assertNull(result.second());
assertNull(result.third());
}
@Test
public void testGetOwnerDetailsForInstanceCreation_NormalAccount_ReturnsDomainAndName() {
Account account = mock(Account.class);
when(account.getType()).thenReturn(Account.Type.NORMAL);
when(account.getDomainId()).thenReturn(2L);
when(account.getAccountName()).thenReturn("myaccount");
Ternary<Long, String, Long> result = serverAdapter.getOwnerDetailsForInstanceCreation(account);
assertEquals(Long.valueOf(2L), result.first());
assertEquals("myaccount", result.second());
assertNull(result.third());
}
@Test
public void testGetOwnerDetailsForInstanceCreation_ProjectAccount_ProjectNotFound_ReturnsAllNulls() {
Account account = mock(Account.class);
when(account.getType()).thenReturn(Account.Type.PROJECT);
when(account.getId()).thenReturn(10L);
when(projectManager.findByProjectAccountId(10L)).thenReturn(null);
Ternary<Long, String, Long> result = serverAdapter.getOwnerDetailsForInstanceCreation(account);
assertNull(result.first());
assertNull(result.second());
assertNull(result.third());
}
@Test
public void testGetOwnerDetailsForInstanceCreation_ProjectAccount_ProjectFound_ReturnsProjectId() {
Account account = mock(Account.class);
when(account.getType()).thenReturn(Account.Type.PROJECT);
when(account.getId()).thenReturn(10L);
when(account.getDomainId()).thenReturn(1L);
Project project = mock(Project.class);
when(project.getId()).thenReturn(5L);
when(projectManager.findByProjectAccountId(10L)).thenReturn(project);
Ternary<Long, String, Long> result = serverAdapter.getOwnerDetailsForInstanceCreation(account);
assertEquals(Long.valueOf(1L), result.first());
assertNull(result.second());
assertEquals(Long.valueOf(5L), result.third());
}
@Test
public void testGetServiceOfferingFromRequest_BlankUuid_ReturnsNull() {
assertNull(serverAdapter.getServiceOfferingFromRequest(null, null, "", 2, 1024));
assertNull(serverAdapter.getServiceOfferingFromRequest(null, null, null, 2, 1024));
}
@Test
public void testGetServiceOfferingFromRequest_OfferingNotFound_ReturnsNull() {
when(serviceOfferingDao.findByUuid("uuid1")).thenReturn(null);
assertNull(serverAdapter.getServiceOfferingFromRequest(null, null, "uuid1", 2, 1024));
}
@Test
public void testGetServiceOfferingFromRequest_AccessDenied_ReturnsNull() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
Account account = mock(Account.class);
when(serviceOfferingDao.findByUuid("uuid2")).thenReturn(offering);
doThrow(new PermissionDeniedException("denied"))
.when(accountService).checkAccess(eq(account), eq(offering), any());
assertNull(serverAdapter.getServiceOfferingFromRequest(null, account, "uuid2", 2, 1024));
}
@Test
public void testGetServiceOfferingFromRequest_NotCustomized_CpuMemoryMismatch_ReturnsNull() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
Account account = mock(Account.class);
when(serviceOfferingDao.findByUuid("uuid3")).thenReturn(offering);
doNothing().when(accountService).checkAccess(eq(account), eq(offering), any());
when(offering.isCustomized()).thenReturn(false);
when(offering.getCpu()).thenReturn(4);
assertNull(serverAdapter.getServiceOfferingFromRequest(null, account, "uuid3", 2, 1024));
}
@Test
public void testGetServiceOfferingFromRequest_NotCustomized_CpuMemoryMatch_ReturnsOffering() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
Account account = mock(Account.class);
when(serviceOfferingDao.findByUuid("uuid4")).thenReturn(offering);
doNothing().when(accountService).checkAccess(eq(account), eq(offering), any());
when(offering.isCustomized()).thenReturn(false);
when(offering.getCpu()).thenReturn(2);
when(offering.getRamSize()).thenReturn(1024);
assertEquals(offering, serverAdapter.getServiceOfferingFromRequest(null, account, "uuid4", 2, 1024));
}
@Test
public void testGetServiceOfferingFromRequest_Customized_ValidParams_SetsAndReturnsOffering() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
Account account = mock(Account.class);
when(serviceOfferingDao.findByUuid("uuid5")).thenReturn(offering);
doNothing().when(accountService).checkAccess(eq(account), eq(offering), any());
when(offering.isCustomized()).thenReturn(true);
doNothing().when(userVmManager).validateCustomParameters(eq(offering), any());
ServiceOfferingVO result = serverAdapter.getServiceOfferingFromRequest(null, account, "uuid5", 2, 1024);
assertEquals(offering, result);
verify(offering).setCpu(2);
verify(offering).setRamSize(1024);
}
@Test
public void testGetServiceOfferingFromRequest_Customized_InvalidParams_ReturnsNull() {
ServiceOfferingVO offering = mock(ServiceOfferingVO.class);
Account account = mock(Account.class);
when(serviceOfferingDao.findByUuid("uuid6")).thenReturn(offering);
doNothing().when(accountService).checkAccess(eq(account), eq(offering), any());
when(offering.isCustomized()).thenReturn(true);
doThrow(new InvalidParameterValueException("invalid"))
.when(userVmManager).validateCustomParameters(eq(offering), any());
assertNull(serverAdapter.getServiceOfferingFromRequest(null, account, "uuid6", 2, 1024));
}
@Test
public void testAccountCannotAccessNetwork_CanAccess_ReturnsFalse() {
NetworkVO network = mock(NetworkVO.class);
Account account = mock(Account.class);
when(accountService.getActiveAccountById(1L)).thenReturn(account);
doNothing().when(networkModel).checkNetworkPermissions(account, network);
assertFalse(serverAdapter.accountCannotAccessNetwork(network, 1L));
}
@Test
public void testAccountCannotAccessNetwork_CannotAccess_ReturnsTrue() {
NetworkVO network = mock(NetworkVO.class);
Account account = mock(Account.class);
when(accountService.getActiveAccountById(1L)).thenReturn(account);
doThrow(new CloudRuntimeException("Access denied"))
.when(networkModel).checkNetworkPermissions(account, network);
assertTrue(serverAdapter.accountCannotAccessNetwork(network, 1L));
}
@Test
public void testValidateInstanceStorage_SupportedStorageType_NoException() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(1L);
VolumeVO vol = mock(VolumeVO.class);
when(vol.getPoolId()).thenReturn(10L);
when(volumeDao.findUsableVolumesForInstance(1L)).thenReturn(List.of(vol));
StoragePoolVO pool = mock(StoragePoolVO.class);
when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(primaryDataStoreDao.listByIds(anyList())).thenReturn(List.of(pool));
serverAdapter.validateInstanceStorage(vm);
}
@Test(expected = InvalidParameterValueException.class)
public void testValidateInstanceStorage_UnsupportedStorageType_Throws() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(2L);
VolumeVO vol = mock(VolumeVO.class);
when(vol.getPoolId()).thenReturn(20L);
when(volumeDao.findUsableVolumesForInstance(2L)).thenReturn(List.of(vol));
StoragePoolVO pool = mock(StoragePoolVO.class);
when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.RBD);
when(pool.getName()).thenReturn("ceph-pool");
when(primaryDataStoreDao.listByIds(anyList())).thenReturn(List.of(pool));
serverAdapter.validateInstanceStorage(vm);
}
@Test
public void testValidateInstanceStorage_NoVolumes_NoException() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(3L);
when(volumeDao.findUsableVolumesForInstance(3L)).thenReturn(Collections.emptyList());
when(primaryDataStoreDao.listByIds(anyList())).thenReturn(Collections.emptyList());
serverAdapter.validateInstanceStorage(vm);
}
@Test
public void testGetBackupDisks_NullVolumeInfos_ReturnsEmptyList() {
BackupVO backup = mock(BackupVO.class);
when(backup.getBackedUpVolumes()).thenReturn(null);
assertTrue(serverAdapter.getBackupDisks(backup).isEmpty());
}
@Test
public void testGetBackupDisks_EmptyVolumeInfos_ReturnsEmptyList() {
BackupVO backup = mock(BackupVO.class);
when(backup.getBackedUpVolumes()).thenReturn(Collections.emptyList());
assertTrue(serverAdapter.getBackupDisks(backup).isEmpty());
}
@Test
public void testGetResourceOwnerFiltersWithDomainIds_NullDomainPath_ReturnsNullDomainIds() {
ServerAdapter spyAdapter = spy(serverAdapter);
doReturn(new Pair<>(List.of(1L, 2L), (String) null)).when(spyAdapter).getResourceOwnerFilters();
Pair<List<Long>, List<Long>> result = spyAdapter.getResourceOwnerFiltersWithDomainIds();
assertEquals(List.of(1L, 2L), result.first());
assertNull(result.second());
}
@Test
public void testGetResourceOwnerFiltersWithDomainIds_WithDomainPath_ReturnsDomainIds() {
ServerAdapter spyAdapter = spy(serverAdapter);
doReturn(new Pair<>(List.of(1L), "ROOT/subdomain")).when(spyAdapter).getResourceOwnerFilters();
when(domainDao.getDomainChildrenIds("ROOT/subdomain")).thenReturn(List.of(10L, 11L));
Pair<List<Long>, List<Long>> result = spyAdapter.getResourceOwnerFiltersWithDomainIds();
assertEquals(List.of(10L, 11L), result.second());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetDataCenter_NotFound_Throws() {
when(dataCenterJoinDao.findByUuid("dc-uuid")).thenReturn(null);
serverAdapter.getDataCenter("dc-uuid");
}
@Test
public void testGetDataCenter_Found_ReturnsDataCenter() {
DataCenterJoinVO vo = mock(DataCenterJoinVO.class);
when(dataCenterJoinDao.findByUuid("dc-uuid")).thenReturn(vo);
DataCenter result = serverAdapter.getDataCenter("dc-uuid");
assertNotNull(result);
}
@Test
public void testListAllDataCenters_ReturnsConvertedList() {
DataCenterJoinVO vo = mock(DataCenterJoinVO.class);
when(dataCenterJoinDao.listAll(any())).thenReturn(List.of(vo));
List<DataCenter> result = serverAdapter.listAllDataCenters(0L, 10L);
assertNotNull(result);
assertEquals(1, result.size());
}
@Test
public void testListAllDataCenters_EmptyList_ReturnsEmpty() {
when(dataCenterJoinDao.listAll(any())).thenReturn(Collections.emptyList());
assertTrue(serverAdapter.listAllDataCenters(0L, 10L).isEmpty());
}
@Test(expected = InvalidParameterValueException.class)
public void testListStorageDomainsByDcId_DataCenterNotFound_Throws() {
when(dataCenterDao.findByUuid("dc-uuid")).thenReturn(null);
serverAdapter.listStorageDomainsByDcId("dc-uuid", 0L, 10L);
}
@Test
public void testListStorageDomainsByDcId_Found_ReturnsList() {
DataCenterVO dcVO = mock(DataCenterVO.class);
when(dcVO.getId()).thenReturn(1L);
when(dataCenterDao.findByUuid("dc-uuid")).thenReturn(dcVO);
StoragePoolJoinVO poolVO = mock(StoragePoolJoinVO.class);
when(storagePoolJoinDao.listByZoneAndType(eq(1L), any(), any())).thenReturn(List.of(poolVO));
assertNotNull(serverAdapter.listStorageDomainsByDcId("dc-uuid", 0L, 10L));
}
@Test(expected = InvalidParameterValueException.class)
public void testListNetworksByDcId_DataCenterNotFound_Throws() {
when(dataCenterJoinDao.findByUuid("dc-uuid")).thenReturn(null);
serverAdapter.listNetworksByDcId("dc-uuid", 0L, 10L);
}
@Test
public void testListNetworksByDcId_Found_ReturnsEmptyListWhenNoNetworks() {
DataCenterJoinVO dcVO = mock(DataCenterJoinVO.class);
when(dcVO.getId()).thenReturn(1L);
when(dataCenterJoinDao.findByUuid("dc-uuid")).thenReturn(dcVO);
when(networkDao.listByZoneAndTrafficType(eq(1L), eq(Networks.TrafficType.Guest), any()))
.thenReturn(Collections.emptyList());
assertTrue(serverAdapter.listNetworksByDcId("dc-uuid", 0L, 10L).isEmpty());
}
@Test
public void testListAllClusters_ReturnsEmptyListWhenNoClusters() {
when(clusterDao.listByHypervisorType(any(), any())).thenReturn(Collections.emptyList());
assertTrue(serverAdapter.listAllClusters(0L, 10L).isEmpty());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetCluster_NotFound_Throws() {
when(clusterDao.findByUuid("cl-uuid")).thenReturn(null);
serverAdapter.getCluster("cl-uuid");
}
@Test
public void testListAllHosts_ReturnsList() {
HostJoinVO hostVO = mock(HostJoinVO.class);
when(hostJoinDao.listRoutingHostsByHypervisor(any(), any())).thenReturn(List.of(hostVO));
assertEquals(1, serverAdapter.listAllHosts(0L, 10L).size());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetHost_NotFound_Throws() {
when(hostJoinDao.findByUuid("host-uuid")).thenReturn(null);
serverAdapter.getHost("host-uuid");
}
@Test
public void testGetHost_Found_ReturnsHost() {
HostJoinVO vo = mock(HostJoinVO.class);
when(hostJoinDao.findByUuid("host-uuid")).thenReturn(vo);
assertNotNull(serverAdapter.getHost("host-uuid"));
}
@Test(expected = InvalidParameterValueException.class)
public void testGetVnicProfile_NotFound_Throws() {
when(networkDao.findByUuid("net-uuid")).thenReturn(null);
serverAdapter.getVnicProfile("net-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testGetInstance_NotFound_Throws() {
when(userVmJoinDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.getInstance("vm-uuid", false, false, false, false);
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteInstance_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.deleteInstance("vm-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testStartInstance_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.startInstance("vm-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testStopInstance_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.stopInstance("vm-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testShutdownInstance_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.shutdownInstance("vm-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testGetDisk_NotFound_Throws() {
when(volumeDao.findByUuid("vol-uuid")).thenReturn(null);
serverAdapter.getDisk("vol-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteDisk_NotFound_Throws() {
when(volumeDao.findByUuid("vol-uuid")).thenReturn(null);
serverAdapter.deleteDisk("vol-uuid");
}
@Test
public void testDeleteDisk_Found_DeletesVolume() {
VolumeVO vo = mock(VolumeVO.class);
when(vo.getId()).thenReturn(10L);
when(volumeDao.findByUuid("vol-uuid")).thenReturn(vo);
Account sysAccount = mock(Account.class);
when(accountService.getSystemAccount()).thenReturn(sysAccount);
serverAdapter.deleteDisk("vol-uuid");
verify(volumeApiService).deleteVolume(10L, sysAccount);
}
@Test(expected = InvalidParameterValueException.class)
public void testCopyDisk_AlwaysThrows() {
serverAdapter.copyDisk("any-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testReduceDisk_AlwaysThrows() {
serverAdapter.reduceDisk("any-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testListNicsByInstanceUuid_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.listNicsByInstanceUuid("vm-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testListDiskAttachmentsByInstanceUuid_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.listDiskAttachmentsByInstanceUuid("vm-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testListSnapshotsByInstanceUuid_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.listSnapshotsByInstanceUuid("vm-uuid");
}
@Test
public void testListSnapshotsByInstanceUuid_Found_ReturnsEmptyList() {
UserVmVO vmVO = mock(UserVmVO.class);
when(vmVO.getId()).thenReturn(1L);
when(vmVO.getUuid()).thenReturn("vm-uuid");
when(userVmDao.findByUuid("vm-uuid")).thenReturn(vmVO);
when(vmSnapshotDao.findByVm(1L)).thenReturn(Collections.emptyList());
assertTrue(serverAdapter.listSnapshotsByInstanceUuid("vm-uuid").isEmpty());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetSnapshot_NotFound_Throws() {
when(vmSnapshotDao.findByUuid("snap-uuid")).thenReturn(null);
serverAdapter.getSnapshot("snap-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteSnapshot_NotFound_Throws() {
when(vmSnapshotDao.findByUuid("snap-uuid")).thenReturn(null);
serverAdapter.deleteSnapshot("snap-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testRevertInstanceToSnapshot_NotFound_Throws() {
when(vmSnapshotDao.findByUuid("snap-uuid")).thenReturn(null);
serverAdapter.revertInstanceToSnapshot("snap-uuid", false);
}
@Test(expected = InvalidParameterValueException.class)
public void testListBackupsByInstanceUuid_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.listBackupsByInstanceUuid("vm-uuid");
}
@Test
public void testListBackupsByInstanceUuid_Found_ReturnsEmptyList() {
UserVmVO vmVO = mock(UserVmVO.class);
when(vmVO.getId()).thenReturn(1L);
when(userVmDao.findByUuid("vm-uuid")).thenReturn(vmVO);
when(backupDao.searchByVmIds(anyList())).thenReturn(Collections.emptyList());
assertTrue(serverAdapter.listBackupsByInstanceUuid("vm-uuid").isEmpty());
}
@Test(expected = InvalidParameterValueException.class)
public void testGetBackup_NotFound_Throws() {
when(backupDao.findByUuidIncludingRemoved("backup-uuid")).thenReturn(null);
serverAdapter.getBackup("backup-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testFinalizeBackup_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.finalizeBackup("vm-uuid", "backup-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testFinalizeBackup_BackupNotFound_Throws() {
UserVmVO vmVO = mock(UserVmVO.class);
when(userVmDao.findByUuid("vm-uuid")).thenReturn(vmVO);
when(backupDao.findByUuid("backup-uuid")).thenReturn(null);
serverAdapter.finalizeBackup("vm-uuid", "backup-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testListCheckpointsByInstanceUuid_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.listCheckpointsByInstanceUuid("vm-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testDeleteCheckpoint_VmNotFound_Throws() {
when(userVmDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.deleteCheckpoint("vm-uuid", "chk-001");
}
@Test(expected = InvalidParameterValueException.class)
public void testGetJob_NotFound_Throws() {
when(asyncJobJoinDao.findByUuidIncludingRemoved("job-uuid")).thenReturn(null);
serverAdapter.getJob("job-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testGetImageTransfer_NotFound_Throws() {
when(imageTransferDao.findByUuidIncludingRemoved("transfer-uuid")).thenReturn(null);
serverAdapter.getImageTransfer("transfer-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testCancelImageTransfer_NotFound_Throws() {
when(imageTransferDao.findByUuid("transfer-uuid")).thenReturn(null);
serverAdapter.cancelImageTransfer("transfer-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testFinalizeImageTransfer_NotFound_Throws() {
when(imageTransferDao.findByUuid("transfer-uuid")).thenReturn(null);
serverAdapter.finalizeImageTransfer("transfer-uuid");
}
@Test(expected = InvalidParameterValueException.class)
public void testUpdateInstance_VmNotFound_Throws() {
when(userVmJoinDao.findByUuid("vm-uuid")).thenReturn(null);
serverAdapter.updateInstance("vm-uuid", new Vm());
}
@Test(expected = InvalidParameterValueException.class)
public void testUpdateDisk_NotFound_Throws() {
when(volumeDao.findByUuid("vol-uuid")).thenReturn(null);
serverAdapter.updateDisk("vol-uuid", new Disk());
}
@Test(expected = InvalidParameterValueException.class)
public void testListDisksByBackupUuid_AlwaysThrows() {
serverAdapter.listDisksByBackupUuid("backup-uuid");
}
}

View File

@ -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.veeam.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.Pair;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.dto.Api;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class ApiRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testCanHandleAndHandleRootApiRequest() throws Exception {
final ApiRouteHandler handler = new ApiRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
handler.veeamControlService = mock(VeeamControlService.class);
final User user = mock(User.class);
when(user.getUuid()).thenReturn("user-1");
when(handler.serverAdapter.getServiceAccount()).thenReturn(new Pair<>(user, mock(Account.class)));
when(handler.veeamControlService.getInstanceId()).thenReturn("instance-1");
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api", Negotiation.OutFormat.JSON, newServlet());
assertTrue(handler.canHandle("GET", "/api?x=1"));
verify(response.response).setStatus(200);
assertContains(response.body(), "\"instance_id\":\"instance-1\"");
assertContains(response.body(), "\"authenticated_user\"");
assertContains(response.body(), "clusters/search");
}
@Test
public void testCreateApiObjectBuildsLinksAndUserReferences() {
final ApiRouteHandler handler = new ApiRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
handler.veeamControlService = mock(VeeamControlService.class);
final User user = mock(User.class);
when(user.getUuid()).thenReturn("service-user");
when(handler.serverAdapter.getServiceAccount()).thenReturn(new Pair<>(user, mock(Account.class)));
when(handler.veeamControlService.getInstanceId()).thenReturn("instance-2");
final Api api = handler.createApiObject("/ctx/api");
assertNotNull(api.getLink());
assertTrue(!api.getLink().isEmpty());
assertEquals("instance-2", api.getProductInfo().getInstanceId());
assertEquals("service-user", api.getAuthenticatedUser().getId());
assertEquals("service-user", api.getEffectiveUser().getId());
assertNotNull(api.getTime());
}
@Test
public void testHandleUnknownPathReturnsNotFound() throws Exception {
final ApiRouteHandler handler = new ApiRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
handler.veeamControlService = mock(VeeamControlService.class);
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/unknown", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "\"reason\":\"Not found\"");
}
}

View File

@ -0,0 +1,80 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.Cluster;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class ClustersRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListReturnsNamedClusterList() throws Exception {
final ClustersRouteHandler handler = new ClustersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllClusters(null, 25L)).thenReturn(List.of(withId(new Cluster(), "cl-1")));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET", Map.of("max", "25"), null, null), response.response,
"/api/clusters", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).listAllClusters(null, 25L);
verify(response.response).setStatus(200);
assertContains(response.body(), "\"cluster\":[");
assertContains(response.body(), "\"id\":\"cl-1\"");
}
@Test
public void testHandleGetByIdReturnsClusterAndMissingClusterIsNotFound() throws Exception {
final ClustersRouteHandler handler = new ClustersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getCluster("cl-1")).thenReturn(withId(new Cluster(), "cl-1"));
when(handler.serverAdapter.getCluster("missing")).thenThrow(new InvalidParameterValueException("missing"));
final ResponseCapture ok = newResponse();
handler.handle(newRequest("GET"), ok.response, "/api/clusters/cl-1", Negotiation.OutFormat.JSON, newServlet());
verify(ok.response).setStatus(200);
assertContains(ok.body(), "\"id\":\"cl-1\"");
final ResponseCapture missing = newResponse();
handler.handle(newRequest("GET"), missing.response, "/api/clusters/missing", Negotiation.OutFormat.JSON, newServlet());
verify(missing.response).setStatus(404);
assertContains(missing.body(), "missing");
}
@Test
public void testHandleRejectsUnsupportedMethod() throws Exception {
final ClustersRouteHandler handler = new ClustersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
final ResponseCapture response = newResponse();
handler.handle(newRequest("POST"), response.response, "/api/clusters", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setHeader("Allow", "GET");
verify(response.response).setStatus(405);
assertContains(response.body(), "Method Not Allowed");
}
}

View File

@ -0,0 +1,91 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
import org.apache.cloudstack.veeam.api.dto.Network;
import org.apache.cloudstack.veeam.api.dto.StorageDomain;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class DataCentersRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndById() throws Exception {
final DataCentersRouteHandler handler = new DataCentersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllDataCenters(null, 20L)).thenReturn(List.of(withId(new DataCenter(), "dc-1")));
when(handler.serverAdapter.getDataCenter("dc-1")).thenReturn(withId(new DataCenter(), "dc-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET", Map.of("max", "20"), null, null), list.response,
"/api/datacenters", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"data_center\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/datacenters/dc-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"dc-1\"");
}
@Test
public void testHandleGetStorageDomainsAndNetworksByDataCenterId() throws Exception {
final DataCentersRouteHandler handler = new DataCentersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listStorageDomainsByDcId("dc-1", null, 15L))
.thenReturn(List.of(withId(new StorageDomain(), "sd-1")));
when(handler.serverAdapter.listNetworksByDcId("dc-1", null, 15L))
.thenReturn(List.of(withId(new Network(), "net-1")));
final ResponseCapture storageDomains = newResponse();
handler.handle(newRequest("GET", Map.of("max", "15"), null, null), storageDomains.response,
"/api/datacenters/dc-1/storagedomains", Negotiation.OutFormat.JSON, newServlet());
verify(storageDomains.response).setStatus(200);
assertContains(storageDomains.body(), "\"storage_domain\":[");
assertContains(storageDomains.body(), "sd-1");
final ResponseCapture networks = newResponse();
handler.handle(newRequest("GET", Map.of("max", "15"), null, null), networks.response,
"/api/datacenters/dc-1/networks", Negotiation.OutFormat.JSON, newServlet());
verify(networks.response).setStatus(200);
assertContains(networks.body(), "\"network\":[");
assertContains(networks.body(), "net-1");
}
@Test
public void testHandleMissingDataCenterReturnsNotFound() throws Exception {
final DataCentersRouteHandler handler = new DataCentersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getDataCenter("missing")).thenThrow(new InvalidParameterValueException("missing dc"));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/datacenters/missing", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "missing dc");
}
}

View File

@ -0,0 +1,122 @@
// 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.veeam.api;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class DisksRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndGetById() throws Exception {
final DisksRouteHandler handler = new DisksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllDisks(null, 10L)).thenReturn(List.of(withId(new Disk(), "disk-1")));
when(handler.serverAdapter.getDisk("disk-1")).thenReturn(withId(new Disk(), "disk-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET", Map.of("max", "10"), null, null), list.response,
"/api/disks", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"disk\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/disks/disk-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"disk-1\"");
}
@Test
public void testHandlePostAndPutParseDiskJson() throws Exception {
final DisksRouteHandler handler = new DisksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
final ArgumentCaptor<Disk> createCaptor = ArgumentCaptor.forClass(Disk.class);
final Disk created = withId(new Disk(), "disk-created");
created.setName("created-disk");
when(handler.serverAdapter.createDisk(createCaptor.capture())).thenReturn(created);
final ResponseCapture post = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"name\":\"created-disk\"}"), post.response,
"/api/disks", Negotiation.OutFormat.JSON, newServlet());
verify(post.response).setStatus(201);
assertEquals("created-disk", createCaptor.getValue().getName());
assertContains(post.body(), "disk-created");
final ArgumentCaptor<Disk> updateCaptor = ArgumentCaptor.forClass(Disk.class);
final Disk updated = withId(new Disk(), "disk-1");
updated.setName("updated-disk");
when(handler.serverAdapter.updateDisk(org.mockito.ArgumentMatchers.eq("disk-1"), updateCaptor.capture())).thenReturn(updated);
final ResponseCapture put = newResponse();
handler.handle(newRequest("PUT", Map.of(), "application/json", "{\"name\":\"updated-disk\"}"), put.response,
"/api/disks/disk-1", Negotiation.OutFormat.JSON, newServlet());
verify(put.response).setStatus(200);
assertEquals("updated-disk", updateCaptor.getValue().getName());
assertContains(put.body(), "updated-disk");
}
@Test
public void testHandleDeleteCopyAndReduceRoutes() throws Exception {
final DisksRouteHandler handler = new DisksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.copyDisk("disk-1")).thenReturn(withId(new Disk(), "copy-1"));
when(handler.serverAdapter.reduceDisk("disk-1")).thenReturn(withId(new Disk(), "reduced-1"));
final ResponseCapture delete = newResponse();
handler.handle(newRequest("DELETE"), delete.response, "/api/disks/disk-1", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).deleteDisk("disk-1");
verify(delete.response).setStatus(200);
assertContains(delete.body(), "Deleted disk ID: disk-1");
final ResponseCapture copy = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{}"), copy.response,
"/api/disks/disk-1/copy", Negotiation.OutFormat.JSON, newServlet());
verify(copy.response).setStatus(200);
assertContains(copy.body(), "copy-1");
final ResponseCapture reduce = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{}"), reduce.response,
"/api/disks/disk-1/reduce", Negotiation.OutFormat.JSON, newServlet());
verify(reduce.response).setStatus(200);
assertContains(reduce.body(), "reduced-1");
}
@Test
public void testHandleCopyRejectsUnsupportedMethod() throws Exception {
final DisksRouteHandler handler = new DisksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/disks/disk-1/copy", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setHeader("Allow", "POST");
verify(response.response).setStatus(405);
assertContains(response.body(), "Method Not Allowed");
}
}

View File

@ -0,0 +1,64 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.Host;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class HostsRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndGetById() throws Exception {
final HostsRouteHandler handler = new HostsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllHosts(null, null)).thenReturn(List.of(withId(new Host(), "host-1")));
when(handler.serverAdapter.getHost("host-1")).thenReturn(withId(new Host(), "host-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/hosts", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"host\":[");
assertContains(list.body(), "host-1");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/hosts/host-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"host-1\"");
}
@Test
public void testHandleGetByIdNotFound() throws Exception {
final HostsRouteHandler handler = new HostsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getHost("missing")).thenThrow(new InvalidParameterValueException("missing host"));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/hosts/missing", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "missing host");
}
}

View File

@ -0,0 +1,102 @@
// 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.veeam.api;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.ImageTransfer;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class ImageTransfersRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndById() throws Exception {
final ImageTransfersRouteHandler handler = new ImageTransfersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllImageTransfers(null, 5L)).thenReturn(List.of(withId(new ImageTransfer(), "transfer-1")));
when(handler.serverAdapter.getImageTransfer("transfer-1")).thenReturn(withId(new ImageTransfer(), "transfer-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET", Map.of("max", "5"), null, null), list.response,
"/api/imagetransfers", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"image_transfer\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/imagetransfers/transfer-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"transfer-1\"");
}
@Test
public void testHandlePostParsesRequestAndCancelFinalizeActions() throws Exception {
final ImageTransfersRouteHandler handler = new ImageTransfersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
final ArgumentCaptor<ImageTransfer> captor = ArgumentCaptor.forClass(ImageTransfer.class);
final ImageTransfer created = withId(new ImageTransfer(), "transfer-created");
created.setPhase("transferring");
when(handler.serverAdapter.createImageTransfer(captor.capture())).thenReturn(created);
final ResponseCapture post = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"phase\":\"transferring\"}"), post.response,
"/api/imagetransfers", Negotiation.OutFormat.JSON, newServlet());
verify(post.response).setStatus(201);
assertEquals("transferring", captor.getValue().getPhase());
assertContains(post.body(), "transfer-created");
final ResponseCapture cancel = newResponse();
handler.handle(newRequest("POST"), cancel.response, "/api/imagetransfers/transfer-1/cancel", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).cancelImageTransfer("transfer-1");
verify(cancel.response).setStatus(200);
assertContains(cancel.body(), "cancelled successfully");
final ResponseCapture finalize = newResponse();
handler.handle(newRequest("POST"), finalize.response, "/api/imagetransfers/transfer-1/finalize", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).finalizeImageTransfer("transfer-1");
verify(finalize.response).setStatus(200);
assertContains(finalize.body(), "finalized successfully");
}
@Test
public void testHandleMissingTransferAndUnsupportedActionMethod() throws Exception {
final ImageTransfersRouteHandler handler = new ImageTransfersRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getImageTransfer("missing")).thenThrow(new InvalidParameterValueException("missing transfer"));
final ResponseCapture missing = newResponse();
handler.handle(newRequest("GET"), missing.response, "/api/imagetransfers/missing", Negotiation.OutFormat.JSON, newServlet());
verify(missing.response).setStatus(404);
assertContains(missing.body(), "missing transfer");
final ResponseCapture wrongMethod = newResponse();
handler.handle(newRequest("GET"), wrongMethod.response, "/api/imagetransfers/transfer-1/cancel", Negotiation.OutFormat.JSON, newServlet());
verify(wrongMethod.response).setHeader("Allow", "POST");
verify(wrongMethod.response).setStatus(405);
assertContains(wrongMethod.body(), "Method Not Allowed");
}
}

View File

@ -0,0 +1,67 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.Job;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class JobsRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndGetById() throws Exception {
final JobsRouteHandler handler = new JobsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listPendingJobs()).thenReturn(List.of(withId(new Job(), "job-1")));
when(handler.serverAdapter.getJob("job-1")).thenReturn(withId(new Job(), "job-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/jobs", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"job\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/jobs/job-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"job-1\"");
}
@Test
public void testHandleMissingJobAndUnsupportedMethod() throws Exception {
final JobsRouteHandler handler = new JobsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getJob("missing")).thenThrow(new InvalidParameterValueException("missing job"));
final ResponseCapture missing = newResponse();
handler.handle(newRequest("GET"), missing.response, "/api/jobs/missing", Negotiation.OutFormat.JSON, newServlet());
verify(missing.response).setStatus(404);
assertContains(missing.body(), "missing job");
final ResponseCapture wrongMethod = newResponse();
handler.handle(newRequest("POST"), wrongMethod.response, "/api/jobs", Negotiation.OutFormat.JSON, newServlet());
verify(wrongMethod.response).setStatus(405);
assertContains(wrongMethod.body(), "Method Not Allowed");
}
}

View File

@ -0,0 +1,63 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.Network;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class NetworksRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndById() throws Exception {
final NetworksRouteHandler handler = new NetworksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllNetworks(null, null)).thenReturn(List.of(withId(new Network(), "net-1")));
when(handler.serverAdapter.getNetwork("net-1")).thenReturn(withId(new Network(), "net-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/networks", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"network\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/networks/net-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"net-1\"");
}
@Test
public void testHandleMissingNetworkReturnsNotFound() throws Exception {
final NetworksRouteHandler handler = new NetworksRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getNetwork("missing")).thenThrow(new InvalidParameterValueException("missing network"));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/networks/missing", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "missing network");
}
}

View File

@ -0,0 +1,102 @@
// 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.veeam.api;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.cloudstack.veeam.VeeamControlServlet;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
class RouteHandlerTestSupport {
protected VeeamControlServlet newServlet() {
return new VeeamControlServlet(Collections.emptyList());
}
protected HttpServletRequest newRequest(final String method) throws Exception {
return newRequest(method, Collections.emptyMap(), null, null);
}
protected HttpServletRequest newRequest(final String method, final Map<String, String> params,
final String contentType, final String body) throws Exception {
final HttpServletRequest request = mock(HttpServletRequest.class);
final Map<String, String> safeParams = params == null ? Collections.emptyMap() : params;
when(request.getMethod()).thenReturn(method);
when(request.getContentType()).thenReturn(contentType);
when(request.getParameterMap()).thenReturn(toParameterMap(safeParams));
when(request.getParameter(org.mockito.ArgumentMatchers.anyString())).thenAnswer(invocation -> safeParams.get(invocation.getArgument(0)));
when(request.getReader()).thenReturn(new BufferedReader(new StringReader(body == null ? "" : body)));
return request;
}
protected ResponseCapture newResponse() throws Exception {
final HttpServletResponse response = mock(HttpServletResponse.class);
final StringWriter sink = new StringWriter();
final PrintWriter writer = new PrintWriter(sink);
when(response.getWriter()).thenReturn(writer);
return new ResponseCapture(response, sink, writer);
}
protected static <T extends BaseDto> T withId(final T dto, final String id) {
dto.setId(id);
dto.setHref("/api/test/" + id);
return dto;
}
protected static void assertContains(final String actual, final String expected) {
assertTrue("Expected body to contain: " + expected + " but was: " + actual, actual.contains(expected));
}
private static Map<String, String[]> toParameterMap(final Map<String, String> params) {
final java.util.LinkedHashMap<String, String[]> result = new java.util.LinkedHashMap<>();
for (Map.Entry<String, String> entry : params.entrySet()) {
result.put(entry.getKey(), new String[]{entry.getValue()});
}
return result;
}
protected static class ResponseCapture {
final HttpServletResponse response;
private final StringWriter sink;
private final PrintWriter writer;
ResponseCapture(final HttpServletResponse response, final StringWriter sink, final PrintWriter writer) {
this.response = response;
this.sink = sink;
this.writer = writer;
}
String body() {
writer.flush();
return sink.toString();
}
}
}

View File

@ -0,0 +1,63 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class TagsRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndById() throws Exception {
final TagsRouteHandler handler = new TagsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllTags(null, null)).thenReturn(List.of(withId(new Tag(), "tag-1")));
when(handler.serverAdapter.getTag("tag-1")).thenReturn(withId(new Tag(), "tag-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/tags", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"tag\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/tags/tag-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"tag-1\"");
}
@Test
public void testHandleMissingTagReturnsNotFound() throws Exception {
final TagsRouteHandler handler = new TagsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getTag("missing")).thenThrow(new InvalidParameterValueException("missing tag"));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/tags/missing", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "missing tag");
}
}

View File

@ -0,0 +1,297 @@
// 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.veeam.api;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.veeam.api.dto.Backup;
import org.apache.cloudstack.veeam.api.dto.Checkpoint;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.DiskAttachment;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.apache.cloudstack.veeam.api.dto.ResourceAction;
import org.apache.cloudstack.veeam.api.dto.Snapshot;
import org.apache.cloudstack.veeam.api.dto.Vm;
import org.apache.cloudstack.veeam.api.dto.VmAction;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
public class VmsRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListUsesFollowFlags() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllInstances(true, true, true, true, null, 10L))
.thenReturn(List.of(withId(new Vm(), "vm-1")));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET", Map.of(
"max", "10",
"all_content", "true",
"follow", "tags,disk_attachments.disk,nics.reporteddevices"), null, null),
response.response, "/api/vms", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).listAllInstances(true, true, true, true, null, 10L);
verify(response.response).setStatus(200);
assertContains(response.body(), "\"vm\":[");
assertContains(response.body(), "vm-1");
}
@Test
public void testHandlePostAndUpdateParseVmJson() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
final ArgumentCaptor<Vm> createCaptor = ArgumentCaptor.forClass(Vm.class);
final Vm created = withId(new Vm(), "vm-created");
created.setName("vm-created");
when(handler.serverAdapter.createInstance(createCaptor.capture())).thenReturn(created);
final ResponseCapture post = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"name\":\"vm-created\"}"), post.response,
"/api/vms", Negotiation.OutFormat.JSON, newServlet());
verify(post.response).setStatus(201);
assertEquals("vm-created", createCaptor.getValue().getName());
assertContains(post.body(), "vm-created");
final ArgumentCaptor<Vm> updateCaptor = ArgumentCaptor.forClass(Vm.class);
final Vm updated = withId(new Vm(), "vm-1");
updated.setName("vm-updated");
when(handler.serverAdapter.updateInstance(eq("vm-1"), updateCaptor.capture())).thenReturn(updated);
final ResponseCapture put = newResponse();
handler.handle(newRequest("PUT", Map.of(), "application/json", "{\"name\":\"vm-updated\"}"), put.response,
"/api/vms/vm-1", Negotiation.OutFormat.JSON, newServlet());
verify(put.response).setStatus(200);
assertEquals("vm-updated", updateCaptor.getValue().getName());
assertContains(put.body(), "vm-updated");
}
@Test
public void testHandleGetByIdDeleteAndPowerActions() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getInstance("vm-1", true, true, true, true)).thenReturn(withId(new Vm(), "vm-1"));
final VmAction deleteAction = new VmAction();
deleteAction.setStatus("deleted");
final VmAction startAction = new VmAction();
startAction.setStatus("starting");
final VmAction stopAction = new VmAction();
stopAction.setStatus("stopping");
final VmAction shutdownAction = new VmAction();
shutdownAction.setStatus("shutting_down");
when(handler.serverAdapter.deleteInstance("vm-1", true)).thenReturn(deleteAction);
when(handler.serverAdapter.startInstance("vm-1", false)).thenReturn(startAction);
when(handler.serverAdapter.stopInstance("vm-1", false)).thenReturn(stopAction);
when(handler.serverAdapter.shutdownInstance("vm-1", false)).thenReturn(shutdownAction);
final ResponseCapture get = newResponse();
handler.handle(newRequest("GET", Map.of(
"all_content", "true",
"follow", "tags,disk_attachments.disk,nics.reporteddevices"), null, null),
get.response, "/api/vms/vm-1", Negotiation.OutFormat.JSON, newServlet());
verify(get.response).setStatus(200);
assertContains(get.body(), "\"id\":\"vm-1\"");
final ResponseCapture delete = newResponse();
handler.handle(newRequest("DELETE", Map.of("async", "true"), null, null), delete.response,
"/api/vms/vm-1", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).deleteInstance("vm-1", true);
verify(delete.response).setStatus(200);
assertContains(delete.body(), "deleted");
final ResponseCapture start = newResponse();
handler.handle(newRequest("POST"), start.response, "/api/vms/vm-1/start", Negotiation.OutFormat.JSON, newServlet());
verify(start.response).setStatus(202);
assertContains(start.body(), "starting");
final ResponseCapture stop = newResponse();
handler.handle(newRequest("POST"), stop.response, "/api/vms/vm-1/stop", Negotiation.OutFormat.JSON, newServlet());
verify(stop.response).setStatus(202);
assertContains(stop.body(), "stopping");
final ResponseCapture shutdown = newResponse();
handler.handle(newRequest("POST"), shutdown.response, "/api/vms/vm-1/shutdown", Negotiation.OutFormat.JSON, newServlet());
verify(shutdown.response).setStatus(202);
assertContains(shutdown.body(), "shutting_down");
}
@Test
public void testHandleDiskAttachmentAndNicRoutes() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listDiskAttachmentsByInstanceUuid("vm-1"))
.thenReturn(List.of(withId(new DiskAttachment(), "attach-1")));
when(handler.serverAdapter.listNicsByInstanceUuid("vm-1"))
.thenReturn(List.of(withId(new Nic(), "nic-1")));
final ArgumentCaptor<DiskAttachment> diskAttachmentCaptor = ArgumentCaptor.forClass(DiskAttachment.class);
final DiskAttachment createdAttachment = withId(new DiskAttachment(), "attach-created");
createdAttachment.setActive("true");
when(handler.serverAdapter.attachInstanceDisk(eq("vm-1"), diskAttachmentCaptor.capture())).thenReturn(createdAttachment);
final ArgumentCaptor<Nic> nicCaptor = ArgumentCaptor.forClass(Nic.class);
final Nic createdNic = withId(new Nic(), "nic-created");
createdNic.setName("nic-created");
when(handler.serverAdapter.attachInstanceNic(eq("vm-1"), nicCaptor.capture())).thenReturn(createdNic);
final ResponseCapture attachments = newResponse();
handler.handle(newRequest("GET"), attachments.response, "/api/vms/vm-1/diskattachments", Negotiation.OutFormat.JSON, newServlet());
verify(attachments.response).setStatus(200);
assertContains(attachments.body(), "\"disk_attachment\":[");
final ResponseCapture postAttachment = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"active\":\"true\"}"), postAttachment.response,
"/api/vms/vm-1/diskattachments", Negotiation.OutFormat.JSON, newServlet());
verify(postAttachment.response).setStatus(201);
assertEquals("true", diskAttachmentCaptor.getValue().getActive());
assertContains(postAttachment.body(), "attach-created");
final ResponseCapture nics = newResponse();
handler.handle(newRequest("GET"), nics.response, "/api/vms/vm-1/nics", Negotiation.OutFormat.JSON, newServlet());
verify(nics.response).setStatus(200);
assertContains(nics.body(), "\"nic\":[");
final ResponseCapture postNic = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"name\":\"nic-created\"}"), postNic.response,
"/api/vms/vm-1/nics", Negotiation.OutFormat.JSON, newServlet());
verify(postNic.response).setStatus(201);
assertEquals("nic-created", nicCaptor.getValue().getName());
assertContains(postNic.body(), "nic-created");
}
@Test
public void testHandleSnapshotRoutes() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listSnapshotsByInstanceUuid("vm-1")).thenReturn(List.of(withId(new Snapshot(), "snap-1")));
when(handler.serverAdapter.getSnapshot("snap-1")).thenReturn(withId(new Snapshot(), "snap-1"));
final ArgumentCaptor<Snapshot> createCaptor = ArgumentCaptor.forClass(Snapshot.class);
final Snapshot createdSnapshot = withId(new Snapshot(), "snap-created");
createdSnapshot.setDescription("created snapshot");
when(handler.serverAdapter.createInstanceSnapshot(eq("vm-1"), createCaptor.capture())).thenReturn(createdSnapshot);
final ResourceAction deleteAction = new ResourceAction();
deleteAction.setStatus("deleted");
final ResourceAction restoreAction = new ResourceAction();
restoreAction.setStatus("restored");
when(handler.serverAdapter.deleteSnapshot("snap-1", true)).thenReturn(deleteAction);
when(handler.serverAdapter.revertInstanceToSnapshot("snap-1", false)).thenReturn(restoreAction);
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/vms/vm-1/snapshots", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"snapshot\":[");
final ResponseCapture post = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"description\":\"created snapshot\"}"), post.response,
"/api/vms/vm-1/snapshots", Negotiation.OutFormat.JSON, newServlet());
verify(post.response).setStatus(202);
assertEquals("created snapshot", createCaptor.getValue().getDescription());
assertContains(post.body(), "snap-created");
final ResponseCapture get = newResponse();
handler.handle(newRequest("GET"), get.response, "/api/vms/vm-1/snapshots/snap-1", Negotiation.OutFormat.JSON, newServlet());
verify(get.response).setStatus(200);
assertContains(get.body(), "snap-1");
final ResponseCapture delete = newResponse();
handler.handle(newRequest("DELETE", Map.of("async", "true"), null, null), delete.response,
"/api/vms/vm-1/snapshots/snap-1", Negotiation.OutFormat.JSON, newServlet());
verify(delete.response).setStatus(202);
assertContains(delete.body(), "deleted");
final ResponseCapture restore = newResponse();
handler.handle(newRequest("POST"), restore.response, "/api/vms/vm-1/snapshots/snap-1/restore", Negotiation.OutFormat.JSON, newServlet());
verify(restore.response).setStatus(202);
assertContains(restore.body(), "restored");
}
@Test
public void testHandleBackupRoutes() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listBackupsByInstanceUuid("vm-1")).thenReturn(List.of(withId(new Backup(), "backup-1")));
when(handler.serverAdapter.getBackup("backup-1")).thenReturn(withId(new Backup(), "backup-1"));
when(handler.serverAdapter.listDisksByBackupUuid("backup-1")).thenReturn(List.of(withId(new Disk(), "disk-1")));
final ArgumentCaptor<Backup> createCaptor = ArgumentCaptor.forClass(Backup.class);
final Backup createdBackup = withId(new Backup(), "backup-created");
createdBackup.setName("backup-created");
when(handler.serverAdapter.createInstanceBackup(eq("vm-1"), createCaptor.capture())).thenReturn(createdBackup);
final Backup finalizedBackup = withId(new Backup(), "backup-1");
finalizedBackup.setPhase("finalized");
when(handler.serverAdapter.finalizeBackup("vm-1", "backup-1")).thenReturn(finalizedBackup);
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/vms/vm-1/backups", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"backup\":[");
final ResponseCapture post = newResponse();
handler.handle(newRequest("POST", Map.of(), "application/json", "{\"name\":\"backup-created\"}"), post.response,
"/api/vms/vm-1/backups", Negotiation.OutFormat.JSON, newServlet());
verify(post.response).setStatus(200);
assertEquals("backup-created", createCaptor.getValue().getName());
assertContains(post.body(), "backup-created");
final ResponseCapture get = newResponse();
handler.handle(newRequest("GET"), get.response, "/api/vms/vm-1/backups/backup-1", Negotiation.OutFormat.JSON, newServlet());
verify(get.response).setStatus(200);
assertContains(get.body(), "backup-1");
final ResponseCapture disks = newResponse();
handler.handle(newRequest("GET"), disks.response, "/api/vms/vm-1/backups/backup-1/disks", Negotiation.OutFormat.JSON, newServlet());
verify(disks.response).setStatus(200);
assertContains(disks.body(), "\"disk\":[");
final ResponseCapture finalize = newResponse();
handler.handle(newRequest("POST"), finalize.response, "/api/vms/vm-1/backups/backup-1/finalize", Negotiation.OutFormat.JSON, newServlet());
verify(finalize.response).setStatus(200);
assertContains(finalize.body(), "finalized");
}
@Test
public void testHandleCheckpointRoutes() throws Exception {
final VmsRouteHandler handler = new VmsRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listCheckpointsByInstanceUuid("vm-1")).thenReturn(List.of(withId(new Checkpoint(), "chk-1")));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/vms/vm-1/checkpoints", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"checkpoints\":[");
final ResponseCapture delete = newResponse();
handler.handle(newRequest("DELETE"), delete.response, "/api/vms/vm-1/checkpoints/chk-1", Negotiation.OutFormat.JSON, newServlet());
verify(handler.serverAdapter).deleteCheckpoint("vm-1", "chk-1");
verify(delete.response).setStatus(200);
}
}

View File

@ -0,0 +1,63 @@
// 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.veeam.api;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.List;
import com.cloud.exception.InvalidParameterValueException;
import org.apache.cloudstack.veeam.api.dto.VnicProfile;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
public class VnicProfilesRouteHandlerTest extends RouteHandlerTestSupport {
@Test
public void testHandleGetListAndById() throws Exception {
final VnicProfilesRouteHandler handler = new VnicProfilesRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.listAllVnicProfiles(null, null)).thenReturn(List.of(withId(new VnicProfile(), "vnic-1")));
when(handler.serverAdapter.getVnicProfile("vnic-1")).thenReturn(withId(new VnicProfile(), "vnic-1"));
final ResponseCapture list = newResponse();
handler.handle(newRequest("GET"), list.response, "/api/vnicprofiles", Negotiation.OutFormat.JSON, newServlet());
verify(list.response).setStatus(200);
assertContains(list.body(), "\"vnic_profile\":[");
final ResponseCapture item = newResponse();
handler.handle(newRequest("GET"), item.response, "/api/vnicprofiles/vnic-1", Negotiation.OutFormat.JSON, newServlet());
verify(item.response).setStatus(200);
assertContains(item.body(), "\"id\":\"vnic-1\"");
}
@Test
public void testHandleMissingVnicProfileReturnsNotFound() throws Exception {
final VnicProfilesRouteHandler handler = new VnicProfilesRouteHandler();
handler.serverAdapter = mock(org.apache.cloudstack.veeam.adapter.ServerAdapter.class);
when(handler.serverAdapter.getVnicProfile("missing")).thenThrow(new InvalidParameterValueException("missing vnic"));
final ResponseCapture response = newResponse();
handler.handle(newRequest("GET"), response.response, "/api/vnicprofiles/missing", Negotiation.OutFormat.JSON, newServlet());
verify(response.response).setStatus(404);
assertContains(response.body(), "missing vnic");
}
}

View File

@ -0,0 +1,85 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.jobs.JobInfo;
import org.apache.cloudstack.veeam.api.dto.Job;
import org.apache.cloudstack.veeam.api.dto.ResourceAction;
import org.junit.Test;
import com.cloud.api.query.vo.AsyncJobJoinVO;
public class AsyncJobJoinVOToJobConverterTest {
@Test
public void testToJob_MapsSucceededStatusAndOwnerRef() {
final AsyncJobJoinVO vo = mock(AsyncJobJoinVO.class);
when(vo.getUuid()).thenReturn("job-1");
when(vo.getUserUuid()).thenReturn("user-1");
when(vo.getCreated()).thenReturn(new Date(1000L));
when(vo.getStatus()).thenReturn(JobInfo.Status.SUCCEEDED.ordinal());
final Job job = AsyncJobJoinVOToJobConverter.toJob(vo);
assertEquals("job-1", job.getId());
assertEquals("finished", job.getStatus());
assertEquals(Long.valueOf(1000L), job.getStartTime());
assertNotNull(job.getEndTime());
assertEquals("user-1", job.getOwner().getId());
}
@Test
public void testToJob_MapsInProgressToStartedAndNoEndTime() {
final AsyncJobJoinVO vo = mock(AsyncJobJoinVO.class);
when(vo.getUuid()).thenReturn("job-2");
when(vo.getUserUuid()).thenReturn("user-2");
when(vo.getCreated()).thenReturn(new Date(2000L));
when(vo.getStatus()).thenReturn(JobInfo.Status.IN_PROGRESS.ordinal());
final Job job = AsyncJobJoinVOToJobConverter.toJob(vo);
assertEquals("started", job.getStatus());
assertNull(job.getEndTime());
}
@Test
public void testToActionAndToJobList() {
final AsyncJobJoinVO vo = mock(AsyncJobJoinVO.class);
when(vo.getUuid()).thenReturn("job-3");
when(vo.getUserUuid()).thenReturn("user-3");
when(vo.getCreated()).thenReturn(new Date(3000L));
when(vo.getStatus()).thenReturn(JobInfo.Status.CANCELLED.ordinal());
final ResourceAction action = AsyncJobJoinVOToJobConverter.toAction(vo);
assertEquals("complete", action.getStatus());
assertEquals("job-3", action.getJob().getId());
final List<Job> jobs = AsyncJobJoinVOToJobConverter.toJobList(List.of(vo));
assertEquals(1, jobs.size());
assertEquals("aborted", jobs.get(0).getStatus());
}
}

View File

@ -0,0 +1,99 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.backup.BackupVO;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.junit.Test;
import com.cloud.api.query.vo.HostJoinVO;
import com.cloud.vm.UserVmVO;
public class BackupVOToBackupConverterTest {
@Test
public void testToBackup_MapsCoreFieldsAndResolvedRefs() {
final BackupVO backupVO = mock(BackupVO.class);
when(backupVO.getUuid()).thenReturn("bkp-1");
when(backupVO.getName()).thenReturn("backup-1");
when(backupVO.getDescription()).thenReturn("desc-1");
when(backupVO.getDate()).thenReturn(new Date(1000L));
when(backupVO.getStatus()).thenReturn(Backup.Status.ReadyForTransfer);
when(backupVO.getFromCheckpointId()).thenReturn("cp-1");
when(backupVO.getToCheckpointId()).thenReturn("cp-2");
when(backupVO.getVmId()).thenReturn(101L);
when(backupVO.getHostId()).thenReturn(201L);
final UserVmVO vmVO = mock(UserVmVO.class);
when(vmVO.getUuid()).thenReturn("vm-1");
final HostJoinVO hostVO = mock(HostJoinVO.class);
when(hostVO.getUuid()).thenReturn("host-1");
final org.apache.cloudstack.veeam.api.dto.Backup backup = BackupVOToBackupConverter.toBackup(
backupVO,
id -> vmVO,
id -> hostVO,
vo -> List.of(new Disk())
);
assertEquals("bkp-1", backup.getId());
assertEquals("backup-1", backup.getName());
assertEquals("desc-1", backup.getDescription());
assertEquals(Long.valueOf(1000L), backup.getCreationDate());
assertEquals("ready", backup.getPhase());
assertEquals("cp-1", backup.getFromCheckpointId());
assertEquals("cp-2", backup.getToCheckpointId());
assertEquals("vm-1", backup.getVm().getId());
assertEquals("host-1", backup.getHost().getId());
assertNotNull(backup.getDisks());
}
@Test
public void testToBackup_PhaseMappingForDifferentStatuses() {
final BackupVO queued = mock(BackupVO.class);
when(queued.getUuid()).thenReturn("b1");
when(queued.getDate()).thenReturn(new Date(1L));
when(queued.getStatus()).thenReturn(Backup.Status.Queued);
when(queued.getVmId()).thenReturn(1L);
final BackupVO finalizing = mock(BackupVO.class);
when(finalizing.getUuid()).thenReturn("b2");
when(finalizing.getDate()).thenReturn(new Date(2L));
when(finalizing.getStatus()).thenReturn(Backup.Status.FinalizingTransfer);
when(finalizing.getVmId()).thenReturn(2L);
final BackupVO failed = mock(BackupVO.class);
when(failed.getUuid()).thenReturn("b3");
when(failed.getDate()).thenReturn(new Date(3L));
when(failed.getStatus()).thenReturn(Backup.Status.Failed);
when(failed.getVmId()).thenReturn(3L);
assertEquals("initializing", BackupVOToBackupConverter.toBackup(queued, null, null, null).getPhase());
assertEquals("finalizing", BackupVOToBackupConverter.toBackup(finalizing, null, null, null).getPhase());
assertEquals("failed", BackupVOToBackupConverter.toBackup(failed, null, null, null).getPhase());
}
}

View File

@ -0,0 +1,81 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.Cluster;
import org.junit.Test;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.cpu.CPU;
import com.cloud.dc.ClusterVO;
public class ClusterVOToClusterConverterTest {
@Test
public void testToCluster_MapsDefaultsAndResolvedDataCenter() {
final ClusterVO vo = mock(ClusterVO.class);
when(vo.getUuid()).thenReturn("cluster-1");
when(vo.getName()).thenReturn("cluster-a");
when(vo.getArch()).thenReturn(CPU.CPUArch.amd64);
when(vo.getDataCenterId()).thenReturn(11L);
final DataCenterJoinVO zone = mock(DataCenterJoinVO.class);
when(zone.getUuid()).thenReturn("dc-1");
final Cluster cluster = ClusterVOToClusterConverter.toCluster(vo, id -> zone);
assertEquals("cluster-1", cluster.getId());
assertEquals("cluster-a", cluster.getName());
assertEquals("x86_64", cluster.getCpu().getArchitecture());
assertEquals("dc-1", cluster.getDataCenter().getId());
assertEquals("urandom", cluster.getRequiredRngSources().requiredRngSource.get(0));
assertEquals("networks", cluster.getLink().get(0).getRel());
assertNotNull(cluster.getSchedulingPolicy());
assertNotNull(cluster.getMacPool());
assertTrue(cluster.getHref().contains("/api/clusters/cluster-1"));
}
@Test
public void testToClusterList_ConvertsAllItems() {
final ClusterVO first = mock(ClusterVO.class);
when(first.getUuid()).thenReturn("c1");
when(first.getName()).thenReturn("c1");
when(first.getArch()).thenReturn(CPU.CPUArch.x86);
when(first.getDataCenterId()).thenReturn(1L);
final ClusterVO second = mock(ClusterVO.class);
when(second.getUuid()).thenReturn("c2");
when(second.getName()).thenReturn("c2");
when(second.getArch()).thenReturn(CPU.CPUArch.amd64);
when(second.getDataCenterId()).thenReturn(2L);
final List<Cluster> clusters = ClusterVOToClusterConverter.toClusterList(List.of(first, second), id -> null);
assertEquals(2, clusters.size());
assertEquals("c1", clusters.get(0).getId());
assertEquals("c2", clusters.get(1).getId());
}
}

View File

@ -0,0 +1,88 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
import org.junit.Test;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.org.Grouping;
public class DataCenterJoinVOToDataCenterConverterTest {
@Test
public void testToDataCenter_MapsIdentityStatusAndLinks() {
final DataCenterJoinVO zone = mock(DataCenterJoinVO.class);
when(zone.getUuid()).thenReturn("dc-1");
when(zone.getName()).thenReturn("zone-a");
when(zone.getDescription()).thenReturn("desc-a");
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
final DataCenter dc = DataCenterJoinVOToDataCenterConverter.toDataCenter(zone);
assertEquals("dc-1", dc.getId());
assertEquals("zone-a", dc.getName());
assertEquals("desc-a", dc.getDescription());
assertEquals("up", dc.getStatus());
assertNotNull(dc.getVersion());
assertNotNull(dc.getSupportedVersions());
assertEquals(3, dc.getLink().size());
assertEquals("cluster", dc.getLink().get(0).getRel());
}
@Test
public void testToDataCenter_DisabledZoneMapsToDown() {
final DataCenterJoinVO zone = mock(DataCenterJoinVO.class);
when(zone.getUuid()).thenReturn("dc-2");
when(zone.getName()).thenReturn("zone-b");
when(zone.getDescription()).thenReturn("desc-b");
when(zone.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
final DataCenter dc = DataCenterJoinVOToDataCenterConverter.toDataCenter(zone);
assertEquals("down", dc.getStatus());
}
@Test
public void testToDcList_ConvertsAllItems() {
final DataCenterJoinVO first = mock(DataCenterJoinVO.class);
when(first.getUuid()).thenReturn("dc-1");
when(first.getName()).thenReturn("zone-a");
when(first.getDescription()).thenReturn("desc-a");
when(first.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
final DataCenterJoinVO second = mock(DataCenterJoinVO.class);
when(second.getUuid()).thenReturn("dc-2");
when(second.getName()).thenReturn("zone-b");
when(second.getDescription()).thenReturn("desc-b");
when(second.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
final List<DataCenter> result = DataCenterJoinVOToDataCenterConverter.toDCList(List.of(first, second));
assertEquals(2, result.size());
assertEquals("dc-1", result.get(0).getId());
assertEquals("dc-2", result.get(1).getId());
}
}

View File

@ -0,0 +1,123 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.Host;
import org.junit.Test;
import com.cloud.api.query.vo.HostJoinVO;
import com.cloud.host.Status;
import com.cloud.resource.ResourceState;
public class HostJoinVOToHostConverterTest {
@Test
public void testToHost_MapsUpHostAndCoreFields() {
final HostJoinVO vo = mock(HostJoinVO.class);
when(vo.getUuid()).thenReturn("host-1");
when(vo.getName()).thenReturn("kvm-1");
when(vo.getPrivateIpAddress()).thenReturn("10.10.10.11");
when(vo.isInMaintenanceStates()).thenReturn(false);
when(vo.getStatus()).thenReturn(Status.Up);
when(vo.getResourceState()).thenReturn(ResourceState.Enabled);
when(vo.getClusterUuid()).thenReturn("cl-1");
when(vo.getSpeed()).thenReturn(2400L);
when(vo.getCpuSockets()).thenReturn(2);
when(vo.getCpus()).thenReturn(8);
when(vo.getTotalMemory()).thenReturn(16000L);
when(vo.getMemUsedCapacity()).thenReturn(4000L);
final Host host = HostJoinVOToHostConverter.toHost(vo);
assertEquals("host-1", host.getId());
assertEquals("kvm-1", host.getName());
assertEquals("10.10.10.11", host.getAddress());
assertEquals("up", host.getStatus());
assertEquals("cl-1", host.getCluster().getId());
assertEquals("2400", host.getCpu().getSpeed());
assertEquals("16000", host.getMemory());
assertEquals("12000", host.getMaxSchedulingMemory());
assertTrue(host.getHref().contains("/api/hosts/host-1"));
}
@Test
public void testToHost_MapsMaintenanceAndFallbackName() {
final HostJoinVO vo = mock(HostJoinVO.class);
when(vo.getUuid()).thenReturn("host-2");
when(vo.getName()).thenReturn(null);
when(vo.getPrivateIpAddress()).thenReturn("10.10.10.12");
when(vo.isInMaintenanceStates()).thenReturn(true);
when(vo.getStatus()).thenReturn(Status.Down);
when(vo.getResourceState()).thenReturn(ResourceState.Disabled);
when(vo.getClusterUuid()).thenReturn("cl-2");
when(vo.getSpeed()).thenReturn(2200L);
when(vo.getCpuSockets()).thenReturn(1);
when(vo.getCpus()).thenReturn(4);
when(vo.getTotalMemory()).thenReturn(8000L);
when(vo.getMemUsedCapacity()).thenReturn(2000L);
final Host host = HostJoinVOToHostConverter.toHost(vo);
assertEquals("host-host-2", host.getName());
assertEquals("maintenance", host.getStatus());
}
@Test
public void testToHostList_ConvertsAllEntries() {
final HostJoinVO first = mock(HostJoinVO.class);
when(first.getUuid()).thenReturn("h1");
when(first.getName()).thenReturn("h1");
when(first.getPrivateIpAddress()).thenReturn("1.1.1.1");
when(first.isInMaintenanceStates()).thenReturn(false);
when(first.getStatus()).thenReturn(Status.Up);
when(first.getResourceState()).thenReturn(ResourceState.Enabled);
when(first.getClusterUuid()).thenReturn("c1");
when(first.getSpeed()).thenReturn(1000L);
when(first.getCpuSockets()).thenReturn(1);
when(first.getCpus()).thenReturn(1);
when(first.getTotalMemory()).thenReturn(1024L);
when(first.getMemUsedCapacity()).thenReturn(24L);
final HostJoinVO second = mock(HostJoinVO.class);
when(second.getUuid()).thenReturn("h2");
when(second.getName()).thenReturn("h2");
when(second.getPrivateIpAddress()).thenReturn("2.2.2.2");
when(second.isInMaintenanceStates()).thenReturn(false);
when(second.getStatus()).thenReturn(Status.Down);
when(second.getResourceState()).thenReturn(ResourceState.Disabled);
when(second.getClusterUuid()).thenReturn("c2");
when(second.getSpeed()).thenReturn(2000L);
when(second.getCpuSockets()).thenReturn(1);
when(second.getCpus()).thenReturn(2);
when(second.getTotalMemory()).thenReturn(2048L);
when(second.getMemUsedCapacity()).thenReturn(48L);
final List<Host> hosts = HostJoinVOToHostConverter.toHostList(List.of(first, second));
assertEquals(2, hosts.size());
assertEquals("h1", hosts.get(0).getId());
assertEquals("h2", hosts.get(1).getId());
}
}

View File

@ -0,0 +1,41 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class ImageTransferVOToImageTransferConverterTest {
@Test
public void testConverterExposesExpectedApiMethods() {
final java.lang.reflect.Method[] methods = ImageTransferVOToImageTransferConverter.class.getDeclaredMethods();
boolean hasSingle = false;
boolean hasList = false;
for (java.lang.reflect.Method method : methods) {
if ("toImageTransfer".equals(method.getName())) {
hasSingle = true;
}
if ("toImageTransferList".equals(method.getName())) {
hasList = true;
}
}
assertTrue(hasSingle);
assertTrue(hasList);
}
}

View File

@ -0,0 +1,113 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.Network;
import org.junit.Test;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.network.Networks;
import com.cloud.network.dao.NetworkVO;
public class NetworkVOToNetworkConverterTest {
@Test
public void testToNetwork_MapsFieldsAndDataCenterRef() {
final NetworkVO vo = mock(NetworkVO.class);
when(vo.getUuid()).thenReturn("net-1");
when(vo.getName()).thenReturn("guest-net");
when(vo.getDisplayText()).thenReturn("Guest network");
when(vo.getPrivateMtu()).thenReturn(1450);
when(vo.getDataCenterId()).thenReturn(10L);
final DataCenterJoinVO dc = mock(DataCenterJoinVO.class);
when(dc.getUuid()).thenReturn("dc-1");
final Network dto = NetworkVOToNetworkConverter.toNetwork(vo, id -> dc);
assertEquals("net-1", dto.getId());
assertEquals("guest-net", dto.getName());
assertEquals("Guest network", dto.getDescription());
assertEquals("", dto.getComment());
assertEquals("1450", dto.getMtu());
assertEquals("false", dto.getPortIsolation());
assertEquals("false", dto.getStp());
assertEquals("guest-net", dto.getVdsmName());
assertNotNull(dto.getUsages());
assertEquals(1, dto.getUsages().getItems().size());
assertEquals("vm", dto.getUsages().getItems().get(0));
assertNotNull(dto.getLink());
assertTrue(dto.getLink().isEmpty());
assertEquals("dc-1", dto.getDataCenter().getId());
assertTrue(dto.getHref().contains("/api/networks/net-1"));
}
@Test
public void testToNetwork_UsesFallbackNameAndSkipsBlankDataCenterUuid() {
final NetworkVO vo = mock(NetworkVO.class);
when(vo.getUuid()).thenReturn("net-2");
when(vo.getName()).thenReturn(null);
when(vo.getTrafficType()).thenReturn(Networks.TrafficType.Guest);
when(vo.getDisplayText()).thenReturn("Fallback network");
when(vo.getPrivateMtu()).thenReturn(null);
when(vo.getDataCenterId()).thenReturn(20L);
final DataCenterJoinVO dc = mock(DataCenterJoinVO.class);
when(dc.getUuid()).thenReturn("");
final Network dto = NetworkVOToNetworkConverter.toNetwork(vo, id -> dc);
assertEquals("Guest-net-2", dto.getName());
assertEquals("0", dto.getMtu());
assertEquals("Guest-net-2", dto.getVdsmName());
assertNull(dto.getDataCenter());
}
@Test
public void testToNetworkList_ConvertsAllItemsInOrder() {
final NetworkVO first = mock(NetworkVO.class);
when(first.getUuid()).thenReturn("net-1");
when(first.getName()).thenReturn("first-net");
when(first.getDisplayText()).thenReturn("First network");
when(first.getPrivateMtu()).thenReturn(1400);
final NetworkVO second = mock(NetworkVO.class);
when(second.getUuid()).thenReturn("net-2");
when(second.getName()).thenReturn(null);
when(second.getTrafficType()).thenReturn(Networks.TrafficType.Control);
when(second.getDisplayText()).thenReturn("Second network");
when(second.getPrivateMtu()).thenReturn(null);
final List<Network> result = NetworkVOToNetworkConverter.toNetworkList(List.of(first, second), null);
assertEquals(2, result.size());
assertEquals("net-1", result.get(0).getId());
assertEquals("first-net", result.get(0).getName());
assertEquals("net-2", result.get(1).getId());
assertEquals("Control-net-2", result.get(1).getName());
}
}

View File

@ -0,0 +1,100 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.VnicProfile;
import org.junit.Test;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.network.Networks;
import com.cloud.network.dao.NetworkVO;
public class NetworkVOToVnicProfileConverterTest {
@Test
public void testToVnicProfile_MapsNetworkAndDataCenterRefs() {
final NetworkVO vo = mock(NetworkVO.class);
when(vo.getUuid()).thenReturn("net-3");
when(vo.getName()).thenReturn("profile-net");
when(vo.getDisplayText()).thenReturn("Profile network");
when(vo.getDataCenterId()).thenReturn(30L);
final DataCenterJoinVO dc = mock(DataCenterJoinVO.class);
when(dc.getUuid()).thenReturn("dc-3");
final VnicProfile profile = NetworkVOToVnicProfileConverter.toVnicProfile(vo, id -> dc);
assertEquals("net-3", profile.getId());
assertEquals("profile-net", profile.getName());
assertEquals("Profile network", profile.getDescription());
assertEquals("net-3", profile.getNetwork().getId());
assertEquals("dc-3", profile.getDataCenter().getId());
assertTrue(profile.getHref().contains("/api/vnicprofiles/net-3"));
assertTrue(profile.getNetwork().getHref().contains("/api/networks/net-3"));
}
@Test
public void testToVnicProfile_UsesFallbackNameAndOmitsBlankDataCenterUuid() {
final NetworkVO vo = mock(NetworkVO.class);
when(vo.getUuid()).thenReturn("net-4");
when(vo.getName()).thenReturn(null);
when(vo.getTrafficType()).thenReturn(Networks.TrafficType.Management);
when(vo.getDisplayText()).thenReturn("Mgmt network");
when(vo.getDataCenterId()).thenReturn(40L);
final DataCenterJoinVO dc = mock(DataCenterJoinVO.class);
when(dc.getUuid()).thenReturn("");
final VnicProfile profile = NetworkVOToVnicProfileConverter.toVnicProfile(vo, id -> dc);
assertEquals("Management-net-4", profile.getName());
assertEquals("Mgmt network", profile.getDescription());
assertEquals("net-4", profile.getNetwork().getId());
assertNull(profile.getDataCenter());
}
@Test
public void testToVnicProfileList_ConvertsAllItemsInOrder() {
final NetworkVO first = mock(NetworkVO.class);
when(first.getUuid()).thenReturn("net-1");
when(first.getName()).thenReturn("profile-a");
when(first.getDisplayText()).thenReturn("Profile A");
final NetworkVO second = mock(NetworkVO.class);
when(second.getUuid()).thenReturn("net-2");
when(second.getName()).thenReturn(null);
when(second.getTrafficType()).thenReturn(Networks.TrafficType.Control);
when(second.getDisplayText()).thenReturn("Profile B");
final List<VnicProfile> result = NetworkVOToVnicProfileConverter.toVnicProfileList(List.of(first, second), null);
assertEquals(2, result.size());
assertEquals("net-1", result.get(0).getId());
assertEquals("profile-a", result.get(0).getName());
assertEquals("net-2", result.get(1).getId());
assertEquals("Control-net-2", result.get(1).getName());
}
}

View File

@ -0,0 +1,66 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.junit.Test;
import com.cloud.network.dao.NetworkVO;
public class NicVOToNicConverterTest {
@Test
public void testToNic_MapsVmHrefIpAndVnicProfile() {
final com.cloud.vm.NicVO vo = mock(com.cloud.vm.NicVO.class);
when(vo.getUuid()).thenReturn("nic-1");
when(vo.getReserver()).thenReturn("eth0");
when(vo.getMacAddress()).thenReturn("02:00:00:00:00:01");
when(vo.getIPv4Address()).thenReturn("10.1.1.10");
when(vo.getIPv4Gateway()).thenReturn("10.1.1.1");
when(vo.getNetworkId()).thenReturn(10L);
final NetworkVO network = mock(NetworkVO.class);
when(network.getUuid()).thenReturn("net-1");
final Nic nic = NicVOToNicConverter.toNic(vo, "vm-1", id -> network);
assertEquals("nic-1", nic.getId());
assertEquals("virtio", nic.getInterfaceType());
assertEquals("vm-1", nic.getVm().getId());
assertTrue(nic.getHref().contains("/api/vms/vm-1/nics/nic-1"));
assertEquals("net-1", nic.getVnicProfile().getId());
assertNotNull(nic.getReportedDevices());
assertEquals("v4", nic.getReportedDevices().getItems().get(0).getIps().getItems().get(0).getVersion());
}
@Test(expected = NullPointerException.class)
public void testToNic_BlankVmUuidCurrentBehaviorThrowsNpe() {
final com.cloud.vm.NicVO vo = mock(com.cloud.vm.NicVO.class);
when(vo.getUuid()).thenReturn("nic-2");
when(vo.getReserver()).thenReturn("eth1");
when(vo.getMacAddress()).thenReturn("02:00:00:00:00:02");
NicVOToNicConverter.toNic(vo, "", null);
}
}

View File

@ -0,0 +1,87 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.junit.Test;
import com.cloud.server.ResourceTag;
import com.cloud.tags.ResourceTagVO;
public class ResourceTagVOToTagConverterTest {
@Test
public void testGetRootTagAndRootRef() {
final Tag root = ResourceTagVOToTagConverter.getRootTag();
assertEquals(BaseDto.ZERO_UUID, root.getId());
assertEquals("root", root.getName());
assertNotNull(ResourceTagVOToTagConverter.getRootTagRef().getHref());
}
@Test
public void testToTag_FromResourceTagVoWithVmReference() {
final ResourceTagVO vo = mock(ResourceTagVO.class);
when(vo.getKey()).thenReturn("env");
when(vo.getValue()).thenReturn("prod");
when(vo.getResourceType()).thenReturn(ResourceTag.ResourceObjectType.UserVm);
when(vo.getResourceUuid()).thenReturn("vm-1");
final Tag tag = ResourceTagVOToTagConverter.toTag(vo);
assertEquals("prod", tag.getId());
assertEquals("prod", tag.getName());
assertNotNull(tag.getParent());
assertNotNull(tag.getVm());
assertEquals("vm-1", tag.getVm().getId());
}
@Test
public void testToTag_FromResourceTagVoWithoutVmReference() {
final ResourceTagVO vo = mock(ResourceTagVO.class);
when(vo.getKey()).thenReturn("scope");
when(vo.getValue()).thenReturn("global");
when(vo.getResourceType()).thenReturn(ResourceTag.ResourceObjectType.Volume);
final Tag tag = ResourceTagVOToTagConverter.toTag(vo);
assertEquals("global", tag.getId());
assertNull(tag.getVm());
}
@Test
public void testToTagsAndToTagsFromValues() {
final ResourceTagVO vo = mock(ResourceTagVO.class);
when(vo.getKey()).thenReturn("team");
when(vo.getValue()).thenReturn("ops");
when(vo.getResourceType()).thenReturn(ResourceTag.ResourceObjectType.UserVm);
when(vo.getResourceUuid()).thenReturn("vm-2");
assertEquals(1, ResourceTagVOToTagConverter.toTags(List.of(vo)).size());
assertEquals("ops", ResourceTagVOToTagConverter.toTagsFromValues(List.of("ops")).get(0).getId());
}
}

View File

@ -0,0 +1,99 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.StorageDomain;
import org.junit.Test;
import com.cloud.api.query.vo.ImageStoreJoinVO;
import com.cloud.api.query.vo.StoragePoolJoinVO;
import com.cloud.storage.Storage;
import com.cloud.storage.StoragePoolStatus;
public class StoreVOToStorageDomainConverterTest {
@Test
public void testToStorageDomain_FromPrimaryPool() {
final StoragePoolJoinVO pool = mock(StoragePoolJoinVO.class);
when(pool.getUuid()).thenReturn("pool-1");
when(pool.getName()).thenReturn("Primary-1");
when(pool.getCapacityBytes()).thenReturn(1000L);
when(pool.getUsedBytes()).thenReturn(250L);
when(pool.getStatus()).thenReturn(StoragePoolStatus.Up);
when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem);
when(pool.getZoneUuid()).thenReturn("dc-1");
final StorageDomain sd = StoreVOToStorageDomainConverter.toStorageDomain(pool);
assertEquals("pool-1", sd.getId());
assertEquals("data", sd.getType());
assertEquals("active", sd.getStatus());
assertEquals("750", sd.getAvailable());
assertEquals("250", sd.getUsed());
assertEquals("1000", sd.getCommitted());
assertEquals("nfs", sd.getStorage().getType());
assertEquals("dc-1", sd.getDataCenters().getItems().get(0).getId());
assertTrue(sd.getLink().stream().anyMatch(l -> "disks".equals(l.getRel())));
}
@Test
public void testToStorageDomain_FromImageStore() {
final ImageStoreJoinVO store = mock(ImageStoreJoinVO.class);
when(store.getUuid()).thenReturn("img-1");
when(store.getName()).thenReturn("Secondary-1");
when(store.getProviderName()).thenReturn("glance");
when(store.getZoneUuid()).thenReturn("dc-2");
final StorageDomain sd = StoreVOToStorageDomainConverter.toStorageDomain(store);
assertEquals("img-1", sd.getId());
assertEquals("image", sd.getType());
assertEquals("unattached", sd.getStatus());
assertEquals("glance", sd.getStorage().getType());
assertEquals("dc-2", sd.getDataCenters().getItems().get(0).getId());
assertTrue(sd.getLink().stream().anyMatch(l -> "images".equals(l.getRel())));
}
@Test
public void testListConverters() {
final StoragePoolJoinVO pool = mock(StoragePoolJoinVO.class);
when(pool.getUuid()).thenReturn("p");
when(pool.getName()).thenReturn("P");
when(pool.getCapacityBytes()).thenReturn(10L);
when(pool.getUsedBytes()).thenReturn(2L);
when(pool.getStatus()).thenReturn(StoragePoolStatus.Up);
when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.Filesystem);
when(pool.getZoneUuid()).thenReturn("z");
final ImageStoreJoinVO store = mock(ImageStoreJoinVO.class);
when(store.getUuid()).thenReturn("s");
when(store.getName()).thenReturn("S");
when(store.getProviderName()).thenReturn("glance");
when(store.getZoneUuid()).thenReturn("z");
assertEquals(1, StoreVOToStorageDomainConverter.toStorageDomainListFromPools(List.of(pool)).size());
assertEquals(1, StoreVOToStorageDomainConverter.toStorageDomainListFromStores(List.of(store)).size());
}
}

View File

@ -0,0 +1,138 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.veeam.api.dto.DiskAttachment;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.api.dto.Vm;
import org.junit.Test;
import org.apache.cloudstack.api.ApiConstants;
import com.cloud.api.query.vo.HostJoinVO;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.vm.VirtualMachine;
public class UserVmJoinVOToVmConverterTest {
@Test
public void testToVm_MapsUpStateWithBasicFields() {
final UserVmJoinVO src = mock(UserVmJoinVO.class);
when(src.getId()).thenReturn(101L);
when(src.getUuid()).thenReturn("vm-1");
when(src.getName()).thenReturn("vm-1-name");
when(src.getDisplayName()).thenReturn("vm-1-display");
when(src.getInstanceName()).thenReturn("i-101");
when(src.getState()).thenReturn(VirtualMachine.State.Running);
when(src.getCreated()).thenReturn(new Date(1000L));
when(src.getLastUpdated()).thenReturn(new Date(2000L));
when(src.getTemplateUuid()).thenReturn("tmpl-1");
when(src.getHostUuid()).thenReturn("host-1");
when(src.getRamSize()).thenReturn(512);
when(src.getArch()).thenReturn("x86_64");
when(src.getCpu()).thenReturn(2);
when(src.getGuestOsDisplayName()).thenReturn("Linux");
when(src.getServiceOfferingUuid()).thenReturn("offering-1");
when(src.getAccountUuid()).thenReturn("acct-1");
when(src.getAffinityGroupUuid()).thenReturn("ag-1");
when(src.getUserDataUuid()).thenReturn("ud-1");
final Vm vm = UserVmJoinVOToVmConverter.toVm(src, null, null, null, null, null, false);
assertEquals("vm-1", vm.getId());
assertEquals("vm-1-name", vm.getName());
assertEquals("vm-1-display", vm.getDescription());
assertEquals("up", vm.getStatus());
assertEquals(Long.valueOf(1000L), vm.getCreationTime());
assertEquals(Long.valueOf(2000L), vm.getStartTime());
assertNull(vm.getStopTime());
assertEquals("536870912", vm.getMemory());
assertEquals("x86_64", vm.getCpu().getArchitecture());
assertEquals("host-1", vm.getHost().getId());
assertNotNull(vm.getActions());
assertEquals(3, vm.getActions().getItems().size());
}
@Test
public void testToVm_UsesResolversAndMapsDownState() {
final UserVmJoinVO src = mock(UserVmJoinVO.class);
when(src.getId()).thenReturn(202L);
when(src.getUuid()).thenReturn("vm-2");
when(src.getName()).thenReturn("vm-2-name");
when(src.getDisplayName()).thenReturn("vm-2-display");
when(src.getInstanceName()).thenReturn("i-202");
when(src.getState()).thenReturn(VirtualMachine.State.Stopped);
when(src.getCreated()).thenReturn(new Date(3000L));
when(src.getLastUpdated()).thenReturn(new Date(4000L));
when(src.getTemplateUuid()).thenReturn("tmpl-2");
when(src.getHostUuid()).thenReturn(null);
when(src.getHostId()).thenReturn(22L);
when(src.getLastHostId()).thenReturn(null);
when(src.getRamSize()).thenReturn(1024);
when(src.getArch()).thenReturn("x86_64");
when(src.getCpu()).thenReturn(4);
when(src.getGuestOsDisplayName()).thenReturn("Linux");
when(src.getServiceOfferingUuid()).thenReturn("offering-2");
when(src.getAccountUuid()).thenReturn("acct-2");
when(src.getAffinityGroupUuid()).thenReturn("ag-2");
when(src.getUserDataUuid()).thenReturn("ud-2");
final HostJoinVO hostVo = mock(HostJoinVO.class);
when(hostVo.getUuid()).thenReturn("host-2");
when(hostVo.getClusterUuid()).thenReturn("cluster-2");
final Tag tag = new Tag();
tag.setId("tag-1");
final DiskAttachment disk = new DiskAttachment();
disk.setId("da-1");
final Nic nic = new Nic();
nic.setId("nic-1");
final Vm vm = UserVmJoinVOToVmConverter.toVm(
src,
id -> hostVo,
id -> Map.of(ApiConstants.BootType.UEFI.toString(), "true"),
id -> List.of(tag),
id -> List.of(disk),
ignored -> List.of(nic),
false);
assertEquals("down", vm.getStatus());
assertEquals(Long.valueOf(4000L), vm.getStopTime());
assertEquals("host-2", vm.getHost().getId());
assertEquals("cluster-2", vm.getCluster().getId());
assertEquals(1, vm.getTags().getItems().size());
assertEquals(1, vm.getDiskAttachments().getItems().size());
assertEquals(1, vm.getNics().getItems().size());
assertEquals("acct-2", vm.getAccountId());
assertEquals("ag-2", vm.getAffinityGroupId());
assertEquals("ud-2", vm.getUserDataId());
}
}

View File

@ -0,0 +1,56 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.apache.cloudstack.veeam.api.dto.Checkpoint;
import org.junit.Test;
public class UserVmVOToCheckpointConverterTest {
@Test
public void testToCheckpoint_ReturnsNullWhenCheckpointIdMissing() {
assertNull(UserVmVOToCheckpointConverter.toCheckpoint(null, "10"));
assertNull(UserVmVOToCheckpointConverter.toCheckpoint("", "10"));
}
@Test
public void testToCheckpoint_ParsesCreationTimeWhenProvided() {
final Checkpoint checkpoint = UserVmVOToCheckpointConverter.toCheckpoint("chk-1", "1700000000");
assertNotNull(checkpoint);
assertEquals("chk-1", checkpoint.getId());
assertEquals("chk-1", checkpoint.getName());
assertEquals(String.valueOf(1700000000L * 1000L), checkpoint.getCreationDate());
assertEquals("created", checkpoint.getState());
}
@Test
public void testToCheckpoint_UsesNowWhenCreateTimeInvalid() {
final long before = System.currentTimeMillis();
final Checkpoint checkpoint = UserVmVOToCheckpointConverter.toCheckpoint("chk-2", "not-a-number");
final long after = System.currentTimeMillis();
final long creation = Long.parseLong(checkpoint.getCreationDate());
assertTrue(creation >= before && creation <= after);
}
}

View File

@ -0,0 +1,74 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.veeam.api.dto.Snapshot;
import org.junit.Test;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotVO;
public class VmSnapshotVOToSnapshotConverterTest {
@Test
public void testToSnapshot_MapsReadyDiskAndMemorySnapshot() {
final VMSnapshotVO vo = mock(VMSnapshotVO.class);
when(vo.getUuid()).thenReturn("snap-1");
when(vo.getDescription()).thenReturn("desc");
when(vo.getCreated()).thenReturn(new Date(1234L));
when(vo.getType()).thenReturn(VMSnapshotVO.Type.DiskAndMemory);
when(vo.getState()).thenReturn(VMSnapshot.State.Ready);
final Snapshot snapshot = VmSnapshotVOToSnapshotConverter.toSnapshot(vo, "vm-1");
assertEquals("snap-1", snapshot.getId());
assertTrue(snapshot.getHref().contains("/api/vms/vm-1/snapshots/snap-1"));
assertEquals("vm-1", snapshot.getVm().getId());
assertEquals("desc", snapshot.getDescription());
assertEquals(Long.valueOf(1234L), snapshot.getDate());
assertEquals("true", snapshot.getPersistMemorystate());
assertEquals("ok", snapshot.getSnapshotStatus());
assertEquals("link", snapshot.getActions().asMap().keySet().iterator().next());
}
@Test
public void testToSnapshot_MapsNonReadyToLockedAndToSnapshotList() {
final VMSnapshotVO vo = mock(VMSnapshotVO.class);
when(vo.getUuid()).thenReturn("snap-2");
when(vo.getDescription()).thenReturn("desc2");
when(vo.getCreated()).thenReturn(new Date(5678L));
when(vo.getType()).thenReturn(VMSnapshotVO.Type.Disk);
when(vo.getState()).thenReturn(VMSnapshot.State.Creating);
final Snapshot snapshot = VmSnapshotVOToSnapshotConverter.toSnapshot(vo, "vm-2");
assertEquals("false", snapshot.getPersistMemorystate());
assertEquals("locked", snapshot.getSnapshotStatus());
final List<Snapshot> list = VmSnapshotVOToSnapshotConverter.toSnapshotList(List.of(vo), "vm-2");
assertEquals(1, list.size());
assertEquals("snap-2", list.get(0).getId());
}
}

View File

@ -0,0 +1,100 @@
// 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.veeam.api.converter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.List;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.DiskAttachment;
import org.junit.Test;
import com.cloud.api.query.vo.VolumeJoinVO;
import com.cloud.storage.Storage;
import com.cloud.storage.Volume;
public class VolumeJoinVOToDiskConverterTest {
@Test
public void testToDisk_MapsCoreFieldsAndResolverOverridesActualSize() {
final VolumeJoinVO vol = mock(VolumeJoinVO.class);
when(vol.getUuid()).thenReturn("vol-1");
when(vol.getVolumeType()).thenReturn(Volume.Type.ROOT);
when(vol.getName()).thenReturn("root-disk");
when(vol.getSize()).thenReturn(1000L);
when(vol.getVolumeStoreSize()).thenReturn(500L);
when(vol.getFormat()).thenReturn(Storage.ImageFormat.RAW);
when(vol.getState()).thenReturn(Volume.State.Ready);
when(vol.getPath()).thenReturn("path-1");
when(vol.getDiskOfferingUuid()).thenReturn("do-1");
when(vol.getPoolUuid()).thenReturn("pool-1");
final Disk disk = VolumeJoinVOToDiskConverter.toDisk(vol, v -> 700L);
assertEquals("vol-1", disk.getId());
assertEquals("true", disk.getBootable());
assertEquals("raw", disk.getFormat());
assertEquals("ok", disk.getStatus());
assertEquals("700", disk.getActualSize());
assertNotNull(disk.getStorageDomains());
assertTrue(disk.getHref().contains("/api/disks/vol-1"));
}
@Test
public void testToDiskAttachment_MapsVmAndDisk() {
final VolumeJoinVO vol = mock(VolumeJoinVO.class);
when(vol.getUuid()).thenReturn("vol-2");
when(vol.getVmUuid()).thenReturn("vm-2");
when(vol.getVolumeType()).thenReturn(Volume.Type.DATADISK);
when(vol.getName()).thenReturn("data-disk");
when(vol.getSize()).thenReturn(2048L);
when(vol.getVolumeStoreSize()).thenReturn(1024L);
when(vol.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
when(vol.getState()).thenReturn(Volume.State.Allocated);
when(vol.getPath()).thenReturn("path-2");
when(vol.getDiskOfferingUuid()).thenReturn("do-2");
when(vol.getPoolUuid()).thenReturn("pool-2");
final DiskAttachment da = VolumeJoinVOToDiskConverter.toDiskAttachment(vol, null);
assertEquals("vol-2", da.getId());
assertEquals("vm-2", da.getVm().getId());
assertEquals("false", da.getBootable());
assertEquals("virtio_scsi", da.getIface());
assertEquals("vol-2", da.getDisk().getId());
}
@Test
public void testToDiskListFromVolumeInfos_MapsBootableByVolumeType() {
final Backup.VolumeInfo root = new Backup.VolumeInfo("root-id", "p1", Volume.Type.ROOT, 10L, 0L, "do", 0L, 0L);
final Backup.VolumeInfo data = new Backup.VolumeInfo("data-id", "p2", Volume.Type.DATADISK, 20L, 1L, "do", 0L, 0L);
final List<Disk> result = VolumeJoinVOToDiskConverter.toDiskListFromVolumeInfos(List.of(root, data));
assertEquals(2, result.size());
assertEquals("true", result.get(0).getBootable());
assertEquals("false", result.get(1).getBootable());
assertEquals("20", result.get(1).getTotalSize());
}
}

View File

@ -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.veeam.api.request;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
public class ListQueryTest {
@Test
public void testFromRequest_WithNoParametersReturnsDefaults() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(Collections.emptyMap());
final ListQuery query = ListQuery.fromRequest(request);
assertFalse(query.isAllContent());
assertNull(query.getLimit());
assertNull(query.getOffset());
assertFalse(query.followContains("tags"));
}
@Test
public void testFromRequest_ParsesAllContentMaxAndFollow() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(Map.of(
"all_content", new String[]{"true"},
"max", new String[]{"25"},
"follow", new String[]{"tags, disk_attachments.disk, ,nics.reporteddevices"}
));
when(request.getParameter("all_content")).thenReturn("true");
when(request.getParameter("max")).thenReturn("25");
when(request.getParameter("follow")).thenReturn("tags, disk_attachments.disk, ,nics.reporteddevices");
when(request.getParameter("search")).thenReturn(null);
final ListQuery query = ListQuery.fromRequest(request);
assertTrue(query.isAllContent());
assertTrue(query.followContains("tags"));
assertTrue(query.followContains("disk_attachments.disk"));
assertTrue(query.followContains("nics.reporteddevices"));
}
@Test
public void testFromRequest_SearchParserIgnoresNonEqualsAndUsesPageValueAsMaxCurrentBehavior() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameterMap()).thenReturn(Map.of("search", new String[]{"name=vm and page=3 and status!=down and x>=1"}));
when(request.getParameter("all_content")).thenReturn(null);
when(request.getParameter("max")).thenReturn(null);
when(request.getParameter("follow")).thenReturn(null);
when(request.getParameter("search")).thenReturn("name=vm and page=3 and status!=down and x>=1");
final ListQuery query = ListQuery.fromRequest(request);
// Document existing behavior: when search contains page=..., max is set from it.
org.junit.Assert.assertEquals(Long.valueOf(3L), query.getLimit());
}
@Test
public void testGetOffset_UsesPageAndMax() {
final ListQuery query = new ListQuery();
query.page = 3L;
query.max = 10L;
org.junit.Assert.assertEquals(Long.valueOf(20L), query.getOffset());
}
}

View File

@ -0,0 +1,130 @@
// 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.veeam.filter;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@RunWith(MockitoJUnitRunner.class)
public class AllowedClientCidrsFilterTest {
@Mock
private VeeamControlService veeamControlService;
@Mock
private FilterChain chain;
@Test
public void testDoFilterAllowsNonHttpRequestsToPassThrough() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(veeamControlService);
final ServletRequest request = mock(ServletRequest.class);
final ServletResponse response = mock(ServletResponse.class);
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
verifyNoInteractions(veeamControlService);
}
@Test
public void testDoFilterRejectsWhenServiceIsUnavailable() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(null);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
assertEquals(503, response.getStatus());
assertEquals("Service Unavailable", response.getErrorMessage());
verifyNoInteractions(chain);
}
@Test
public void testDoFilterAllowsRequestWhenNoCidrsConfigured() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
when(veeamControlService.getAllowedClientCidrs()).thenReturn(Collections.emptyList());
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
}
@Test
public void testDoFilterAllowsRequestFromConfiguredCidr() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setRemoteAddr("192.168.10.25");
when(veeamControlService.getAllowedClientCidrs()).thenReturn(List.of("192.168.10.0/24"));
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
assertEquals(200, response.getStatus());
}
@Test
public void testDoFilterRejectsRequestOutsideConfiguredCidrs() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setRemoteAddr("10.10.10.10");
when(veeamControlService.getAllowedClientCidrs()).thenReturn(List.of("192.168.10.0/24"));
filter.doFilter(request, response, chain);
assertEquals(403, response.getStatus());
assertEquals("Forbidden", response.getErrorMessage());
verifyNoInteractions(chain);
}
@Test
public void testDoFilterRejectsMalformedRemoteAddress() throws Exception {
final AllowedClientCidrsFilter filter = new AllowedClientCidrsFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setRemoteAddr("not-a-valid-ip-address");
when(veeamControlService.getAllowedClientCidrs()).thenReturn(List.of("192.168.10.0/24"));
filter.doFilter(request, response, chain);
assertEquals(403, response.getStatus());
assertEquals("Forbidden", response.getErrorMessage());
verifyNoInteractions(chain);
}
}

View File

@ -0,0 +1,129 @@
// 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.veeam.filter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.servlet.FilterChain;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.utils.JwtUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@RunWith(MockitoJUnitRunner.class)
public class BearerOrBasicAuthFilterTest {
private static final String SECRET = "very-secret";
@Mock
private VeeamControlService veeamControlService;
@Mock
private FilterChain chain;
@Test
public void testDoFilterRejectsMissingAuthorizationWithJsonPayload() throws Exception {
final BearerOrBasicAuthFilter filter = new BearerOrBasicAuthFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader("Accept", "application/json");
filter.doFilter(request, response, chain);
assertEquals(401, response.getStatus());
assertEquals("application/json; charset=UTF-8", response.getContentType());
assertNotNull(response.getHeader("WWW-Authenticate"));
assertTrue(response.getHeader("WWW-Authenticate").contains("error=\"invalid_token\""));
assertTrue(response.getContentAsString().contains("\"error\":\"invalid_token\""));
assertTrue(response.getContentAsString().contains("Missing Authorization"));
verifyNoInteractions(chain);
}
@Test
public void testDoFilterRejectsInvalidBearerToken() throws Exception {
final BearerOrBasicAuthFilter filter = new BearerOrBasicAuthFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader("Accept", "application/json");
request.addHeader("Authorization", "Bearer not-a-jwt");
filter.doFilter(request, response, chain);
assertEquals(401, response.getStatus());
assertTrue(response.getContentAsString().contains("Invalid or expired token"));
verifyNoInteractions(chain);
}
@Test
public void testDoFilterAllowsValidBearerTokenWithRequiredScopes() throws Exception {
final BearerOrBasicAuthFilter filter = new BearerOrBasicAuthFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
final String token = JwtUtil.issueHs256Jwt("service-user", "ovirt-app-admin ovirt-app-portal", 60L, SECRET);
request.addHeader("Authorization", "Bearer " + token);
when(veeamControlService.getHmacSecret()).thenReturn(SECRET);
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
}
@Test
public void testDoFilterAllowsValidBasicCredentials() throws Exception {
final BearerOrBasicAuthFilter filter = new BearerOrBasicAuthFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
final String credentials = Base64.getEncoder().encodeToString("veeam:secret".getBytes(StandardCharsets.UTF_8));
request.addHeader("Authorization", "Basic " + credentials);
when(veeamControlService.validateCredentials("veeam", "secret")).thenReturn(true);
filter.doFilter(request, response, chain);
verify(chain).doFilter(request, response);
}
@Test
public void testDoFilterRejectsInvalidBasicCredentialsWithHtmlPayload() throws Exception {
final BearerOrBasicAuthFilter filter = new BearerOrBasicAuthFilter(veeamControlService);
final MockHttpServletRequest request = new MockHttpServletRequest();
final MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader("Authorization", "Basic !!!not-base64!!!");
filter.doFilter(request, response, chain);
assertEquals(401, response.getStatus());
assertEquals("text/html; charset=UTF-8", response.getContentType());
assertTrue(response.getContentAsString().contains("Unauthorized"));
assertNotNull(response.getHeader("WWW-Authenticate"));
assertTrue(response.getHeader("WWW-Authenticate").contains("invalid_client"));
verifyNoInteractions(chain);
}
}

View File

@ -0,0 +1,127 @@
// 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.veeam.services;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.veeam.VeeamControlServlet;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@RunWith(MockitoJUnitRunner.class)
public class PkiResourceRouteHandlerTest {
@Test
public void testCanHandleSanitizesQueryParameters() {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
assertTrue(handler.canHandle("GET", "/services/pki-resource?resource=ca-certificate"));
}
@Test
public void testHandleReturnsCertificateDownload() throws Exception {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
final CAManager caManager = mock(CAManager.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/pki-resource");
final MockHttpServletResponse response = new MockHttpServletResponse();
final String certificate = "-----BEGIN CERTIFICATE-----\nveeam\n-----END CERTIFICATE-----\n";
handler.caManager = caManager;
when(caManager.getCaCertificate(null)).thenReturn(certificate);
handler.handle(request, response, "/services/pki-resource", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(200, response.getStatus());
assertEquals("no-store", response.getHeader("Cache-Control"));
assertEquals("attachment; filename=\"pki-resource.cer\"", response.getHeader("Content-Disposition"));
assertEquals("application/x-x509-ca-cert; charset=ISO-8859-1", response.getContentType());
assertArrayEquals(certificate.getBytes(StandardCharsets.ISO_8859_1), response.getContentAsByteArray());
}
@Test
public void testHandleRejectsUnsupportedResource() throws Exception {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
handler.caManager = mock(CAManager.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/pki-resource");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("resource", "unsupported");
handler.handle(request, response, "/services/pki-resource", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(400, response.getStatus());
assertEquals("Unsupported resource", response.getErrorMessage());
}
@Test
public void testHandleRejectsUnsupportedFormat() throws Exception {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
handler.caManager = mock(CAManager.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/pki-resource");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("format", "PEM");
handler.handle(request, response, "/services/pki-resource", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(400, response.getStatus());
assertEquals("Unsupported format", response.getErrorMessage());
}
@Test
public void testHandleRejectsEmptyCertificateData() throws Exception {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
final CAManager caManager = mock(CAManager.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/pki-resource");
final MockHttpServletResponse response = new MockHttpServletResponse();
handler.caManager = caManager;
when(caManager.getCaCertificate(null)).thenReturn("");
handler.handle(request, response, "/services/pki-resource", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(500, response.getStatus());
assertEquals("No certificate data available", response.getErrorMessage());
}
@Test
public void testHandleReturnsNotFoundForUnknownPath() throws Exception {
final PkiResourceRouteHandler handler = new PkiResourceRouteHandler();
handler.caManager = mock(CAManager.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/pki-resource/unknown");
final MockHttpServletResponse response = new MockHttpServletResponse();
handler.handle(request, response, "/services/pki-resource/unknown", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(404, response.getStatus());
assertTrue(response.getContentAsString().contains("\"reason\":\"Not found\""));
}
}

View File

@ -0,0 +1,219 @@
// 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.veeam.sso;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Map;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.VeeamControlServlet;
import org.apache.cloudstack.veeam.utils.Mapper;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import com.fasterxml.jackson.core.type.TypeReference;
@RunWith(MockitoJUnitRunner.class)
public class SsoServiceTest {
private final Mapper mapper = new Mapper();
@Test
public void testCanHandleSanitizesQueryParameters() {
final SsoService service = new SsoService();
assertTrue(service.canHandle("POST", "/sso/oauth/token?scope=abc"));
}
@Test
public void testHandleReturnsNotFoundForUnknownPath() throws Exception {
final SsoService service = new SsoService();
service.veeamControlService = mock(VeeamControlService.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/sso/unknown");
final MockHttpServletResponse response = new MockHttpServletResponse();
service.handle(request, response, "/sso/unknown", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(404, response.getStatus());
assertTrue(response.getContentAsString().contains("\"reason\":\"Not found\""));
}
@Test
public void testHandleTokenRejectsNonPostMethod() throws Exception {
final SsoService service = new SsoService();
service.veeamControlService = mock(VeeamControlService.class);
final MockHttpServletRequest request = new MockHttpServletRequest("GET", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(405, response.getStatus());
assertEquals("POST", response.getHeader("Allow"));
assertTrue(response.getContentAsString().contains("method_not_allowed"));
}
@Test
public void testHandleTokenRejectsMissingGrantType() throws Exception {
final SsoService service = new SsoService();
service.veeamControlService = mock(VeeamControlService.class);
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(400, response.getStatus());
assertTrue(response.getContentAsString().contains("Missing parameter: grant_type"));
}
@Test
public void testHandleTokenRejectsUnsupportedGrantType() throws Exception {
final SsoService service = new SsoService();
service.veeamControlService = mock(VeeamControlService.class);
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "client_credentials");
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(400, response.getStatus());
assertTrue(response.getContentAsString().contains("unsupported_grant_type"));
}
@Test
public void testHandleTokenRejectsMissingCredentials() throws Exception {
final SsoService service = new SsoService();
service.veeamControlService = mock(VeeamControlService.class);
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "password");
request.setParameter("username", "veeam");
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(400, response.getStatus());
assertTrue(response.getContentAsString().contains("Missing username/password"));
}
@Test
public void testHandleTokenRejectsInvalidCredentials() throws Exception {
final SsoService service = new SsoService();
final VeeamControlService controlService = mock(VeeamControlService.class);
service.veeamControlService = controlService;
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "password");
request.setParameter("username", "veeam");
request.setParameter("password", "wrong");
when(controlService.validateCredentials("veeam", "wrong")).thenReturn(false);
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(401, response.getStatus());
assertTrue(response.getContentAsString().contains("invalid_grant"));
}
@Test
public void testHandleTokenReturnsServerErrorWhenTokenIssuanceFails() throws Exception {
final SsoService service = new SsoService();
final VeeamControlService controlService = mock(VeeamControlService.class);
service.veeamControlService = controlService;
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "password");
request.setParameter("username", "veeam");
request.setParameter("password", "secret");
when(controlService.validateCredentials("veeam", "secret")).thenReturn(true);
when(controlService.getHmacSecret()).thenReturn(null);
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(500, response.getStatus());
assertTrue(response.getContentAsString().contains("Failed to issue token"));
}
@Test
public void testHandleTokenIssuesTokenWithDefaultScopes() throws Exception {
final SsoService service = new SsoService();
final VeeamControlService controlService = mock(VeeamControlService.class);
service.veeamControlService = controlService;
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "password");
request.setParameter("username", "veeam");
request.setParameter("password", "secret");
when(controlService.validateCredentials("veeam", "secret")).thenReturn(true);
when(controlService.getHmacSecret()).thenReturn("very-secret");
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(200, response.getStatus());
final Map<String, Object> payload = mapper.jsonMapper().readValue(response.getContentAsByteArray(), new TypeReference<>() {
});
assertEquals("bearer", payload.get("token_type"));
assertEquals(3600, ((Number) payload.get("expires_in")).intValue());
assertEquals(String.join(" ", SsoService.REQUIRED_SCOPES), payload.get("scope"));
final String accessToken = (String) payload.get("access_token");
final String jwtPayload = new String(Base64.getUrlDecoder().decode(accessToken.split("\\.")[1]), StandardCharsets.UTF_8);
final Map<String, Object> jwtClaims = mapper.jsonMapper().readValue(jwtPayload, new TypeReference<>() {
});
assertEquals("veeam", jwtClaims.get("sub"));
assertEquals(String.join(" ", SsoService.REQUIRED_SCOPES), jwtClaims.get("scope"));
}
@Test
public void testHandleTokenHonorsCustomScope() throws Exception {
final SsoService service = new SsoService();
final VeeamControlService controlService = mock(VeeamControlService.class);
service.veeamControlService = controlService;
final MockHttpServletRequest request = new MockHttpServletRequest("POST", "/sso/oauth/token");
final MockHttpServletResponse response = new MockHttpServletResponse();
request.setParameter("grant_type", "password");
request.setParameter("username", "veeam");
request.setParameter("password", "secret");
request.setParameter("scope", "custom-scope");
when(controlService.validateCredentials("veeam", "secret")).thenReturn(true);
when(controlService.getHmacSecret()).thenReturn("very-secret");
service.handle(request, response, "/sso/oauth/token", Negotiation.OutFormat.JSON,
new VeeamControlServlet(Collections.emptyList()));
assertEquals(200, response.getStatus());
assertTrue(response.getContentAsString().contains("\"scope\":\"custom-scope\""));
}
}

View File

@ -0,0 +1,57 @@
// 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.veeam.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.nio.charset.StandardCharsets;
import org.junit.Test;
public class DataUtilTest {
@Test
public void testB64Url_UsesUrlSafeAlphabetAndNoPadding() {
final String encoded = DataUtil.b64Url(new byte[]{(byte)0xfb, (byte)0xff});
assertEquals("-_8", encoded);
}
@Test
public void testJsonEscape_NullAndEscapedCharacters() {
assertEquals("", DataUtil.jsonEscape(null));
assertEquals("a\\\\b\\\"c", DataUtil.jsonEscape("a\\b\"c"));
}
@Test
public void testConstantTimeEquals_StringOverload() {
assertTrue(DataUtil.constantTimeEquals("abc", "abc"));
assertFalse(DataUtil.constantTimeEquals("abc", "abd"));
assertFalse(DataUtil.constantTimeEquals(null, "abc"));
assertFalse(DataUtil.constantTimeEquals("abc", null));
}
@Test
public void testConstantTimeEquals_ByteArrayOverload() {
final byte[] left = "sample".getBytes(StandardCharsets.UTF_8);
assertTrue(DataUtil.constantTimeEquals(left, "sample".getBytes(StandardCharsets.UTF_8)));
assertFalse(DataUtil.constantTimeEquals(left, "samples".getBytes(StandardCharsets.UTF_8)));
assertFalse(DataUtil.constantTimeEquals(left, "samplE".getBytes(StandardCharsets.UTF_8)));
}
}

View File

@ -0,0 +1,79 @@
// 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.veeam.utils;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import org.junit.Test;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class JwtUtilTest {
@Test
public void testHmacSha256_KnownVector() throws Exception {
final byte[] actual = JwtUtil.hmacSha256("data".getBytes(StandardCharsets.UTF_8), "key".getBytes(StandardCharsets.UTF_8));
final byte[] expected = hexToBytes("5031fe3d989c6d1537a013fa6e739da23463fdaec3b70137d828e36ace221bd0");
assertArrayEquals(expected, actual);
}
@Test
public void testIssueHs256Jwt_BuildsValidTokenAndClaims() throws Exception {
final long before = Instant.now().getEpochSecond();
final String token = JwtUtil.issueHs256Jwt("sub-1", "scope-a", 120L, "very-secret");
final long after = Instant.now().getEpochSecond();
final String[] parts = token.split("\\.");
assertEquals(3, parts.length);
final JsonObject header = JsonParser.parseString(new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8)).getAsJsonObject();
final JsonObject payload = JsonParser.parseString(new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8)).getAsJsonObject();
assertEquals("HS256", header.get("alg").getAsString());
assertEquals("JWT", header.get("typ").getAsString());
assertEquals(JwtUtil.ISSUER, payload.get("iss").getAsString());
assertEquals("sub-1", payload.get("sub").getAsString());
assertEquals("scope-a", payload.get("scope").getAsString());
final long iat = payload.get("iat").getAsLong();
final long exp = payload.get("exp").getAsLong();
assertTrue(iat >= before && iat <= after);
assertEquals(120L, exp - iat);
final byte[] expectedSig = JwtUtil.hmacSha256((parts[0] + "." + parts[1]).getBytes(StandardCharsets.UTF_8), "very-secret".getBytes(StandardCharsets.UTF_8));
final byte[] actualSig = Base64.getUrlDecoder().decode(parts[2]);
assertArrayEquals(expectedSig, actualSig);
}
private static byte[] hexToBytes(final String hex) {
final byte[] out = new byte[hex.length() / 2];
for (int i = 0; i < out.length; i++) {
final int hi = Character.digit(hex.charAt(i * 2), 16);
final int lo = Character.digit(hex.charAt(i * 2 + 1), 16);
out[i] = (byte)((hi << 4) + lo);
}
return out;
}
}

View File

@ -0,0 +1,72 @@
// 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.veeam.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class MapperTest {
@Test
public void testToJson_UsesSnakeCaseAndOmitsNulls() throws Exception {
final Mapper mapper = new Mapper();
final String json = mapper.toJson(new Sample("John", "Doe", null));
assertTrue(json.contains("\"first_name\":\"John\""));
assertTrue(json.contains("\"last_name\":\"Doe\""));
assertFalse(json.contains("optional_field"));
}
@Test
public void testToXml_UsesSnakeCaseAndOmitsNulls() throws Exception {
final Mapper mapper = new Mapper();
final String xml = mapper.toXml(new Sample("John", "Doe", null));
assertTrue(xml.contains("<first_name>John</first_name>"));
assertTrue(xml.contains("<last_name>Doe</last_name>"));
assertFalse(xml.contains("optional_field"));
}
@Test
public void testJsonMapper_IgnoresUnknownProperties() throws Exception {
final Mapper mapper = new Mapper();
final Sample sample = mapper.jsonMapper().readValue("{\"first_name\":\"Alice\",\"unknown\":\"x\"}", Sample.class);
assertNotNull(sample);
assertEquals("Alice", sample.firstName);
}
static class Sample {
public String firstName;
public String lastName;
public String optionalField;
Sample() {
}
Sample(final String firstName, final String lastName, final String optionalField) {
this.firstName = firstName;
this.lastName = lastName;
this.optionalField = optionalField;
}
}
}

View File

@ -0,0 +1,66 @@
// 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.veeam.utils;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
public class NegotiationTest {
@Test
public void testResponseFormat_DefaultsToXmlForNullBlankWildcardAndUnknown() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader("Accept")).thenReturn(null);
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
when(request.getHeader("Accept")).thenReturn(" ");
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
when(request.getHeader("Accept")).thenReturn("*/*");
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
when(request.getHeader("Accept")).thenReturn("application/octet-stream");
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
}
@Test
public void testResponseFormat_ResolvesJsonAndXmlMediaTypesCaseInsensitively() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader("Accept")).thenReturn("Application/JSON");
assertEquals(Negotiation.OutFormat.JSON, Negotiation.responseFormat(request));
when(request.getHeader("Accept")).thenReturn("application/xml");
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
when(request.getHeader("Accept")).thenReturn("text/xml");
assertEquals(Negotiation.OutFormat.XML, Negotiation.responseFormat(request));
}
@Test
public void testContentType_ReturnsMimeTypeForEachFormat() {
assertEquals("application/json", Negotiation.contentType(Negotiation.OutFormat.JSON));
assertEquals("application/xml", Negotiation.contentType(Negotiation.OutFormat.XML));
}
}

View File

@ -0,0 +1,54 @@
// 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.veeam.utils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.List;
import org.junit.Test;
public class PathUtilTest {
@Test
public void testExtractIdAndSubPath_ReturnsNullForBlankOrInvalidPath() {
assertNull(PathUtil.extractIdAndSubPath(null, "/api/datacenters"));
assertNull(PathUtil.extractIdAndSubPath(" ", "/api/datacenters"));
assertNull(PathUtil.extractIdAndSubPath("api/datacenters/123", "/api/datacenters"));
assertNull(PathUtil.extractIdAndSubPath("/api/datacenters", "/api/datacenters"));
}
@Test
public void testExtractIdAndSubPath_RemovesBaseRouteAndReturnsSegments() {
final List<String> parts = PathUtil.extractIdAndSubPath("/api/datacenters/123/sub/path", "/api/datacenters");
assertEquals(List.of("123", "sub", "path"), parts);
}
@Test
public void testExtractIdAndSubPath_HandlesTrailingSlashBaseAndRepeatedSlashes() {
final List<String> parts = PathUtil.extractIdAndSubPath("/api/datacenters//123///child/", "/api/datacenters/");
assertEquals(List.of("123", "child"), parts);
}
@Test
public void testExtractIdAndSubPath_WithoutBaseRouteUsesPathDirectly() {
final List<String> parts = PathUtil.extractIdAndSubPath("/id-1/sub", "");
assertEquals(List.of("id-1", "sub"), parts);
}
}

View File

@ -0,0 +1,116 @@
// 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.veeam.utils;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
public class ResponseWriterTest {
@Test
public void testWrite_NullBodySetsStatusAndZeroContentLength() throws Exception {
final Mapper mapper = new Mapper();
final ResponseWriter responseWriter = new ResponseWriter(mapper);
final HttpServletResponse response = mock(HttpServletResponse.class);
responseWriter.write(response, 204, null, Negotiation.OutFormat.XML);
verify(response).setStatus(204);
verify(response).setContentLength(0);
verify(response, never()).getWriter();
}
@Test
public void testWrite_JsonBodyWritesPayloadAndHeaders() throws Exception {
final ResponseWriter responseWriter = new ResponseWriter(new Mapper());
final HttpServletResponse response = mock(HttpServletResponse.class);
final StringWriter sink = new StringWriter();
when(response.getWriter()).thenReturn(new PrintWriter(sink));
responseWriter.write(response, 200, new Payload("item-1"), Negotiation.OutFormat.JSON);
verify(response).setStatus(200);
verify(response).setHeader("Content-Type", "application/json");
assertTrue(sink.toString().contains("\"name\":\"item-1\""));
}
@Test
public void testWrite_XmlBodyWritesPayloadAndHeaders() throws Exception {
final ResponseWriter responseWriter = new ResponseWriter(new Mapper());
final HttpServletResponse response = mock(HttpServletResponse.class);
final StringWriter sink = new StringWriter();
when(response.getWriter()).thenReturn(new PrintWriter(sink));
responseWriter.write(response, 200, new Payload("item-2"), Negotiation.OutFormat.XML);
verify(response).setHeader("Content-Type", "application/xml");
assertTrue(sink.toString().contains("<name>item-2</name>"));
}
@Test
public void testWrite_WhenMappingFailsReturnsInternalServerError() throws Exception {
final Mapper mapper = mock(Mapper.class);
doThrow(new RuntimeException("boom")).when(mapper).toJson(org.mockito.ArgumentMatchers.any());
final ResponseWriter responseWriter = new ResponseWriter(mapper);
final HttpServletResponse response = mock(HttpServletResponse.class);
final StringWriter sink = new StringWriter();
when(response.getWriter()).thenReturn(new PrintWriter(sink));
responseWriter.write(response, 200, new Payload("ignored"), Negotiation.OutFormat.JSON);
verify(response).setStatus(500);
verify(response).setHeader("Content-Type", "text/plain");
assertTrue(sink.toString().contains("Internal Server Error"));
verify(response, never()).setContentLength(anyInt());
}
@Test
public void testWriteFault_JsonWritesFaultStructure() throws Exception {
final ResponseWriter responseWriter = new ResponseWriter(new Mapper());
final HttpServletResponse response = mock(HttpServletResponse.class);
final StringWriter sink = new StringWriter();
when(response.getWriter()).thenReturn(new PrintWriter(sink));
responseWriter.writeFault(response, 404, "Not Found", "missing vm", Negotiation.OutFormat.JSON);
verify(response).setStatus(404);
assertTrue(sink.toString().contains("\"reason\":\"Not Found\""));
assertTrue(sink.toString().contains("\"detail\":\"missing vm\""));
}
static class Payload {
public String name;
Payload(final String name) {
this.name = name;
}
}
}