mirror of https://github.com/apache/cloudstack.git
wip
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
parent
7c23b2610a
commit
065ec85589
|
|
@ -31,4 +31,13 @@ public interface RouteHandler extends Adapter {
|
|||
boolean canHandle(String method, String path) throws IOException;
|
||||
void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io)
|
||||
throws IOException;
|
||||
|
||||
default String getSanitizedPath(String path) {
|
||||
// remove query params if exists
|
||||
int qIdx = path.indexOf('?');
|
||||
if (qIdx != -1) {
|
||||
return path.substring(0, qIdx);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,19 +17,36 @@
|
|||
|
||||
package org.apache.cloudstack.veeam;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.cloudstack.veeam.filter.BasicAuthFilter;
|
||||
import org.apache.cloudstack.utils.server.ServerPropertiesUtil;
|
||||
import org.apache.cloudstack.veeam.api.ApiService;
|
||||
import org.apache.cloudstack.veeam.filter.BearerOrBasicAuthFilter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.RequestLog;
|
||||
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||
import org.eclipse.jetty.server.handler.HandlerList;
|
||||
import org.eclipse.jetty.server.handler.RequestLogHandler;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class VeeamControlServer {
|
||||
private static final Logger LOGGER = LogManager.getLogger(VeeamControlServer.class);
|
||||
|
|
@ -49,6 +66,13 @@ public class VeeamControlServer {
|
|||
return;
|
||||
}
|
||||
|
||||
final String keystorePath = ServerPropertiesUtil.getKeystoreFile();
|
||||
final String keystorePassword = ServerPropertiesUtil.getKeystorePassword();
|
||||
final String keyManagerPassword = ServerPropertiesUtil.getKeystorePassword();
|
||||
final boolean sslConfigured = StringUtils.isNotEmpty(keystorePath) &&
|
||||
StringUtils.isNotEmpty(keystorePassword) &&
|
||||
StringUtils.isNotEmpty(keyManagerPassword) &&
|
||||
Files.exists(Paths.get(keystorePath));
|
||||
final String bind = VeeamControlService.BindAddress.value();
|
||||
final int port = VeeamControlService.Port.value();
|
||||
String ctxPath = VeeamControlService.ContextPath.value();
|
||||
|
|
@ -56,28 +80,119 @@ public class VeeamControlServer {
|
|||
routeHandlers != null ? routeHandlers.size() : 0);
|
||||
|
||||
|
||||
server = new Server(new InetSocketAddress(bind, port));
|
||||
server = new Server();
|
||||
|
||||
if (sslConfigured) {
|
||||
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
sslContextFactory.setKeyStorePath(keystorePath);
|
||||
sslContextFactory.setKeyStorePassword(keystorePassword);
|
||||
sslContextFactory.setKeyManagerPassword(keyManagerPassword);
|
||||
|
||||
final HttpConfiguration https = new HttpConfiguration();
|
||||
https.setSecureScheme("https");
|
||||
https.setSecurePort(port);
|
||||
https.addCustomizer(new SecureRequestCustomizer());
|
||||
|
||||
final ServerConnector httpsConnector = new ServerConnector(
|
||||
server,
|
||||
new SslConnectionFactory(sslContextFactory, "http/1.1"),
|
||||
new HttpConnectionFactory(https)
|
||||
);
|
||||
httpsConnector.setHost(bind);
|
||||
httpsConnector.setPort(port);
|
||||
server.addConnector(httpsConnector);
|
||||
|
||||
LOGGER.info("Veeam Control API server HTTPS enabled on {}:{}", bind, port);
|
||||
} else {
|
||||
final HttpConfiguration http = new HttpConfiguration();
|
||||
final ServerConnector httpConnector = new ServerConnector(server, new HttpConnectionFactory(http));
|
||||
httpConnector.setHost(bind);
|
||||
httpConnector.setPort(port);
|
||||
server.addConnector(httpConnector);
|
||||
|
||||
LOGGER.warn("Veeam Control API server HTTPS is NOT configured (missing keystore path/passwords). " +
|
||||
"Starting HTTP on {}:{} instead.", bind, port);
|
||||
}
|
||||
|
||||
final ServletContextHandler ctx =
|
||||
new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||
ctx.setContextPath(ctxPath);
|
||||
|
||||
// Basic Auth for all routes
|
||||
ctx.addFilter(BasicAuthFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
// Bearer or Basic Auth for all routes
|
||||
ctx.addFilter(BearerOrBasicAuthFilter.class, ApiService.BASE_ROUTE + "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
// Front controller servlet
|
||||
ctx.addServlet(new ServletHolder(new VeeamControlServlet(routeHandlers)), "/*");
|
||||
|
||||
server.setHandler(ctx);
|
||||
// Create a RequestLog that logs every request handled by the server (all contexts/paths)
|
||||
server.setHandler(buildContextHandler(ctx));
|
||||
|
||||
server.start();
|
||||
|
||||
LOGGER.info("Started Veeam Control API server on {}:{} with context {}", bind, port, ctxPath);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Handler buildContextHandler(ServletContextHandler ctx) {
|
||||
// Handler for root ('/') path
|
||||
final ServletContextHandler root = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||
root.setContextPath("/");
|
||||
root.addServlet(new ServletHolder(new javax.servlet.http.HttpServlet() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp)
|
||||
throws java.io.IOException {
|
||||
resp.setContentType("text/plain");
|
||||
resp.setStatus(javax.servlet.http.HttpServletResponse.SC_OK);
|
||||
resp.getWriter().println("Veeam Control API");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp)
|
||||
throws java.io.IOException {
|
||||
doGet(req, resp);
|
||||
}
|
||||
}), "/*");
|
||||
|
||||
final RequestLog requestLog = (request, response) -> {
|
||||
final String uri = request.getRequestURI() +
|
||||
(request.getQueryString() != null ? "?" + request.getQueryString() : "");
|
||||
LOGGER.info("Request - remoteAddr: {}, method: {}, uri: {}, headers: {}, status: {}",
|
||||
request.getRemoteAddr(),
|
||||
request.getMethod(),
|
||||
uri,
|
||||
dumpRequestHeaders(request),
|
||||
response.getStatus());
|
||||
};
|
||||
|
||||
final RequestLogHandler requestLogHandler = new RequestLogHandler();
|
||||
requestLogHandler.setRequestLog(requestLog);
|
||||
|
||||
// Attach both the configured context and the root handler; keep ctx first so contextPath has priority
|
||||
final HandlerList handlers = new HandlerList();
|
||||
handlers.setHandlers(new Handler[] { ctx, root });
|
||||
requestLogHandler.setHandler(handlers);
|
||||
return requestLogHandler;
|
||||
}
|
||||
|
||||
public void stop() throws Exception {
|
||||
if (server != null) {
|
||||
server.stop();
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String dumpRequestHeaders(HttpServletRequest request) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
final Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
final String name = headerNames.nextElement();
|
||||
final Enumeration<String> values = request.getHeaders(name);
|
||||
while (values.hasMoreElements()) {
|
||||
sb.append(name).append("=").append(values.nextElement()).append("; ");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ public interface VeeamControlService extends PluggableService, Configurable {
|
|||
ConfigKey<Integer> Port = new ConfigKey<>("Advanced", Integer.class, "integration.veeam.control.port",
|
||||
"8090", "Port for Veeam Integration REST API server", false);
|
||||
ConfigKey<String> ContextPath = new ConfigKey<>("Advanced", String.class, "integration.veeam.control.context.path",
|
||||
"/integrations/veeam", "Context path for Veeam Integration REST API server", false);
|
||||
"/ovirt-engine", "Context path for Veeam Integration REST API server", false);
|
||||
ConfigKey<String> Username = new ConfigKey<>("Advanced", String.class, "integration.veeam.api.username",
|
||||
"veeam", "Username for Basic Auth on Veeam Integration REST API server", true);
|
||||
ConfigKey<String> Password = new ConfigKey<>("Advanced", String.class, "integration.veeam.api.password",
|
||||
|
|
|
|||
|
|
@ -58,6 +58,34 @@ public class VeeamControlServlet extends HttpServlet {
|
|||
|
||||
LOGGER.info("Received {} request for {} with out format: {}", method, path, outFormat);
|
||||
|
||||
// Add a log to give all info about the request
|
||||
try {
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append("Request details: Method: ").append(method).append(", Path: ").append(path);
|
||||
details.append(", Query: ").append(req.getQueryString() == null ? "" : req.getQueryString());
|
||||
details.append(", Headers: ");
|
||||
java.util.Enumeration<String> headerNames = req.getHeaderNames();
|
||||
while (headerNames != null && headerNames.hasMoreElements()) {
|
||||
String name = headerNames.nextElement();
|
||||
details.append(name).append("=").append(req.getHeader(name)).append("; ");
|
||||
}
|
||||
// String body = "";
|
||||
// if (!"GET".equalsIgnoreCase(method)) {
|
||||
// StringBuilder bodySb = new StringBuilder();
|
||||
// java.io.BufferedReader reader = req.getReader();
|
||||
// if (reader != null) {
|
||||
// String line;
|
||||
// while ((line = reader.readLine()) != null) {
|
||||
// bodySb.append(line).append('\n');
|
||||
// }
|
||||
// }
|
||||
// body = bodySb.toString().trim();
|
||||
// }
|
||||
// details.append(", Body: ").append(body);
|
||||
LOGGER.debug(details.toString());
|
||||
} catch (Exception e) {
|
||||
LOGGER.debug("Failed to capture request details", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if ("/".equals(path)) {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,28 @@
|
|||
package org.apache.cloudstack.veeam.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.cloudstack.veeam.RouteHandler;
|
||||
import org.apache.cloudstack.veeam.VeeamControlService;
|
||||
import org.apache.cloudstack.veeam.VeeamControlServlet;
|
||||
import org.apache.cloudstack.veeam.api.dto.Api;
|
||||
import org.apache.cloudstack.veeam.api.dto.EmptyElement;
|
||||
import org.apache.cloudstack.veeam.api.dto.Link;
|
||||
import org.apache.cloudstack.veeam.api.dto.ProductInfo;
|
||||
import org.apache.cloudstack.veeam.api.dto.Ref;
|
||||
import org.apache.cloudstack.veeam.api.dto.SpecialObjectRef;
|
||||
import org.apache.cloudstack.veeam.api.dto.SpecialObjects;
|
||||
import org.apache.cloudstack.veeam.api.dto.Summary;
|
||||
import org.apache.cloudstack.veeam.api.dto.SummaryCount;
|
||||
import org.apache.cloudstack.veeam.api.dto.Version;
|
||||
import org.apache.cloudstack.veeam.utils.Negotiation;
|
||||
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
|
|
@ -33,12 +49,101 @@ public class ApiService extends ManagerBase implements RouteHandler {
|
|||
|
||||
@Override
|
||||
public boolean canHandle(String method, String path) {
|
||||
return path.startsWith("/api");
|
||||
return getSanitizedPath(path).startsWith("/api");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
// ToDo: handle root API requests
|
||||
final String sanitizedPath = getSanitizedPath(path);
|
||||
if (sanitizedPath.equals(BASE_ROUTE)) {
|
||||
handleRootApiRequest(req, resp, outFormat, io);
|
||||
return;
|
||||
}
|
||||
io.getWriter().writeFault(resp, HttpServletResponse.SC_NOT_FOUND, "Not found", null, outFormat);
|
||||
}
|
||||
|
||||
private void handleRootApiRequest(HttpServletRequest req, HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
io.getWriter().write(resp, 200,
|
||||
createDummyApi(VeeamControlService.ContextPath.value() + BASE_ROUTE),
|
||||
outFormat);
|
||||
}
|
||||
|
||||
private static Api createDummyApi(String basePath) {
|
||||
Api api = new Api();
|
||||
|
||||
/* ---------------- Links ---------------- */
|
||||
List<Link> links = new ArrayList<>();
|
||||
add(links, basePath + "/clusters", "clusters");
|
||||
add(links, basePath + "/clusters?search={query}", "clusters/search");
|
||||
add(links, basePath + "/datacenters", "datacenters");
|
||||
add(links, basePath + "/datacenters?search={query}", "datacenters/search");
|
||||
add(links, basePath + "/events", "events");
|
||||
add(links, basePath + "/events;from={event_id}?search={query}", "events/search");
|
||||
add(links, basePath + "/hosts", "hosts");
|
||||
add(links, basePath + "/hosts?search={query}", "hosts/search");
|
||||
add(links, basePath + "/networks", "networks");
|
||||
add(links, basePath + "/networks?search={query}", "networks/search");
|
||||
add(links, basePath + "/storagedomains", "storagedomains");
|
||||
add(links, basePath + "/storagedomains?search={query}", "storagedomains/search");
|
||||
add(links, basePath + "/templates", "templates");
|
||||
add(links, basePath + "/templates?search={query}", "templates/search");
|
||||
add(links, basePath + "/vms", "vms");
|
||||
add(links, basePath + "/vms?search={query}", "vms/search");
|
||||
add(links, basePath + "/disks", "disks");
|
||||
add(links, basePath + "/disks?search={query}", "disks/search");
|
||||
|
||||
api.link = links;
|
||||
|
||||
/* ---------------- Engine backup ---------------- */
|
||||
api.engineBackup = new EmptyElement();
|
||||
|
||||
/* ---------------- Product info ---------------- */
|
||||
ProductInfo productInfo = new ProductInfo();
|
||||
productInfo.instanceId = UUID.randomUUID().toString();
|
||||
productInfo.name = "oVirt Engine";
|
||||
|
||||
Version version = new Version();
|
||||
version.build = "8";
|
||||
version.fullVersion = "4.5.8-0.master.fake.el9";
|
||||
version.major = 4;
|
||||
version.minor = 5;
|
||||
version.revision = 0;
|
||||
|
||||
productInfo.version = version;
|
||||
api.productInfo = productInfo;
|
||||
|
||||
/* ---------------- Special objects ---------------- */
|
||||
SpecialObjects specialObjects = new SpecialObjects();
|
||||
specialObjects.blankTemplate = new SpecialObjectRef(
|
||||
basePath + "/templates/00000000-0000-0000-0000-000000000000",
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
);
|
||||
specialObjects.rootTag = new SpecialObjectRef(
|
||||
basePath + "/tags/00000000-0000-0000-0000-000000000000",
|
||||
"00000000-0000-0000-0000-000000000000"
|
||||
);
|
||||
api.specialObjects = specialObjects;
|
||||
|
||||
/* ---------------- Summary ---------------- */
|
||||
Summary summary = new Summary();
|
||||
summary.hosts = new SummaryCount(1, 1);
|
||||
summary.storageDomains = new SummaryCount(1, 2);
|
||||
summary.users = new SummaryCount(1, 1);
|
||||
summary.vms = new SummaryCount(1, 8);
|
||||
api.summary = summary;
|
||||
|
||||
/* ---------------- Time ---------------- */
|
||||
api.time = OffsetDateTime.now(ZoneOffset.ofHours(2)).toInstant().toEpochMilli();
|
||||
|
||||
/* ---------------- Users ---------------- */
|
||||
String userId = UUID.randomUUID().toString();
|
||||
api.authenticatedUser = Ref.of(basePath + "/users/" + userId, userId);
|
||||
api.effectiveUser = Ref.of(basePath + "/users/" + userId, userId);
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
private static void add(List<Link> links, String href, String rel) {
|
||||
links.add(new Link(href, rel));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
// 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 java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.cloudstack.veeam.RouteHandler;
|
||||
import org.apache.cloudstack.veeam.VeeamControlServlet;
|
||||
import org.apache.cloudstack.veeam.api.converter.DataCenterVOToDataCenterConverter;
|
||||
import org.apache.cloudstack.veeam.api.converter.StoreVOToStorageDomainConverter;
|
||||
import org.apache.cloudstack.veeam.api.dto.DataCenter;
|
||||
import org.apache.cloudstack.veeam.api.dto.DataCenters;
|
||||
import org.apache.cloudstack.veeam.api.dto.StorageDomain;
|
||||
import org.apache.cloudstack.veeam.api.dto.StorageDomains;
|
||||
import org.apache.cloudstack.veeam.api.request.VmListQuery;
|
||||
import org.apache.cloudstack.veeam.utils.Negotiation;
|
||||
import org.apache.cloudstack.veeam.utils.PathUtil;
|
||||
|
||||
import com.cloud.api.query.dao.ImageStoreJoinDao;
|
||||
import com.cloud.api.query.dao.StoragePoolJoinDao;
|
||||
import com.cloud.api.query.vo.ImageStoreJoinVO;
|
||||
import com.cloud.api.query.vo.StoragePoolJoinVO;
|
||||
import com.cloud.dc.DataCenterVO;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
|
||||
public class DataCentersRouteHandler extends ManagerBase implements RouteHandler {
|
||||
public static final String BASE_ROUTE = "/api/datacenters";
|
||||
private static final int DEFAULT_MAX = 50;
|
||||
private static final int HARD_CAP_MAX = 1000;
|
||||
private static final int DEFAULT_PAGE = 1;
|
||||
|
||||
@Inject
|
||||
DataCenterDao dataCenterDao;
|
||||
|
||||
@Inject
|
||||
StoragePoolJoinDao storagePoolJoinDao;
|
||||
|
||||
@Inject
|
||||
ImageStoreJoinDao imageStoreJoinDao;
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(String method, String path) {
|
||||
return getSanitizedPath(path).startsWith(BASE_ROUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
final String method = req.getMethod();
|
||||
if (!"GET".equalsIgnoreCase(method)) {
|
||||
io.methodNotAllowed(resp, "GET", outFormat);
|
||||
return;
|
||||
}
|
||||
final String sanitizedPath = getSanitizedPath(path);
|
||||
if (sanitizedPath.equals(BASE_ROUTE)) {
|
||||
handleGet(req, resp, outFormat, io);
|
||||
return;
|
||||
}
|
||||
|
||||
Pair<String, String> idAndSubPath = PathUtil.extractIdAndSubPath(sanitizedPath, BASE_ROUTE);
|
||||
if (idAndSubPath != null) {
|
||||
// /api/datacenters/{id}
|
||||
if (idAndSubPath.first() != null) {
|
||||
if (idAndSubPath.second() == null) {
|
||||
handleGetById(idAndSubPath.first(), resp, outFormat, io);
|
||||
return;
|
||||
}
|
||||
if ("storagedomains".equals(idAndSubPath.second())) {
|
||||
handleGetStorageDomainsByDcId(idAndSubPath.first(), resp, outFormat, io);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches /api/datacenters/{id} where {id} is a single path segment (no extra '/').
|
||||
* Returns id or null.
|
||||
*/
|
||||
private static String matchSinglePathParam(final String path, final String prefix) {
|
||||
if (!path.startsWith(prefix)) return null;
|
||||
final String rest = path.substring(prefix.length()); // after "/api/datacenters/"
|
||||
if (rest.isEmpty()) return null;
|
||||
if (rest.contains("/")) return null; // ensure only 1 segment
|
||||
return rest;
|
||||
}
|
||||
|
||||
public void handleGet(final HttpServletRequest req, final HttpServletResponse resp,
|
||||
Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
final List<DataCenter> result = DataCenterVOToDataCenterConverter.toDCList(listDCs());
|
||||
final DataCenters response = new DataCenters(result);
|
||||
|
||||
io.getWriter().write(resp, 200, response, outFormat);
|
||||
}
|
||||
|
||||
private static VmListQuery fromRequest(final HttpServletRequest req) {
|
||||
final VmListQuery q = new VmListQuery();
|
||||
q.setSearch(req.getParameter("search"));
|
||||
q.setMax(parseIntOrNull(req.getParameter("max")));
|
||||
q.setPage(parseIntOrNull(req.getParameter("page")));
|
||||
return q;
|
||||
}
|
||||
|
||||
private static Integer parseIntOrNull(final String s) {
|
||||
if (s == null || s.trim().isEmpty()) return null;
|
||||
try {
|
||||
return Integer.parseInt(s.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return Integer.valueOf(-1); // will be rejected by validation above
|
||||
}
|
||||
}
|
||||
|
||||
protected List<DataCenterVO> listDCs() {
|
||||
return dataCenterDao.listAll();
|
||||
}
|
||||
|
||||
public void handleGetById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat,
|
||||
final VeeamControlServlet io) throws IOException {
|
||||
final DataCenterVO dataCenterVO = dataCenterDao.findByUuid(id);
|
||||
if (dataCenterVO == null) {
|
||||
io.notFound(resp, "DataCenter not found: " + id, outFormat);
|
||||
return;
|
||||
}
|
||||
DataCenter response = DataCenterVOToDataCenterConverter.toDataCenter(dataCenterVO);
|
||||
|
||||
io.getWriter().write(resp, 200, response, outFormat);
|
||||
}
|
||||
|
||||
protected List<StoragePoolJoinVO> listStoragePoolsByDcId(final long dcId) {
|
||||
return storagePoolJoinDao.listAll();
|
||||
}
|
||||
|
||||
protected List<ImageStoreJoinVO> listImageStoresByDcId(final long dcId) {
|
||||
return imageStoreJoinDao.listAll();
|
||||
}
|
||||
|
||||
public void handleGetStorageDomainsByDcId(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat,
|
||||
final VeeamControlServlet io) throws IOException {
|
||||
final DataCenterVO dataCenterVO = dataCenterDao.findByUuid(id);
|
||||
if (dataCenterVO == null) {
|
||||
io.notFound(resp, "DataCenter not found: " + id, outFormat);
|
||||
return;
|
||||
}
|
||||
List<StorageDomain> storageDomains = StoreVOToStorageDomainConverter.toStorageDomainListFromPools(listStoragePoolsByDcId(dataCenterVO.getId()));
|
||||
storageDomains.addAll(StoreVOToStorageDomainConverter.toStorageDomainListFromStores(listImageStoresByDcId(dataCenterVO.getId())));
|
||||
|
||||
StorageDomains response = new StorageDomains(storageDomains);
|
||||
|
||||
io.getWriter().write(resp, 200, response, outFormat);
|
||||
}
|
||||
}
|
||||
|
|
@ -68,13 +68,14 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
|
|||
|
||||
@Override
|
||||
public boolean canHandle(String method, String path) {
|
||||
return path.startsWith(BASE_ROUTE);
|
||||
return getSanitizedPath(path).startsWith(BASE_ROUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
final String method = req.getMethod();
|
||||
if (path.equals(BASE_ROUTE)) {
|
||||
final String sanitizedPath = getSanitizedPath(path);
|
||||
if (sanitizedPath.equals(BASE_ROUTE)) {
|
||||
if (!"GET".equalsIgnoreCase(method)) {
|
||||
io.methodNotAllowed(resp, "GET", outFormat);
|
||||
return;
|
||||
|
|
@ -84,7 +85,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
|
|||
}
|
||||
|
||||
// /api/vms/{id}
|
||||
final String vmId = matchSinglePathParam(path, BASE_ROUTE + "/");
|
||||
final String vmId = matchSinglePathParam(sanitizedPath, BASE_ROUTE + "/");
|
||||
if (vmId != null) {
|
||||
if (!"GET".equalsIgnoreCase(method)) {
|
||||
io.methodNotAllowed(resp, "GET", outFormat);
|
||||
|
|
|
|||
|
|
@ -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.converter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cloudstack.veeam.VeeamControlService;
|
||||
import org.apache.cloudstack.veeam.api.DataCentersRouteHandler;
|
||||
import org.apache.cloudstack.veeam.api.dto.DataCenter;
|
||||
import org.apache.cloudstack.veeam.api.dto.Link;
|
||||
import org.apache.cloudstack.veeam.api.dto.Ref;
|
||||
import org.apache.cloudstack.veeam.api.dto.SupportedVersions;
|
||||
import org.apache.cloudstack.veeam.api.dto.Version;
|
||||
|
||||
import com.cloud.dc.DataCenterVO;
|
||||
import com.cloud.org.Grouping;
|
||||
|
||||
public class DataCenterVOToDataCenterConverter {
|
||||
public static DataCenter toDataCenter(final DataCenterVO zone) {
|
||||
final String id = zone.getUuid();
|
||||
final String basePath = VeeamControlService.ContextPath.value();
|
||||
final String href = basePath + DataCentersRouteHandler.BASE_ROUTE + "/datacenters/" + id;
|
||||
|
||||
final DataCenter dc = new DataCenter();
|
||||
|
||||
// ---- Identity ----
|
||||
dc.id = id;
|
||||
dc.href = href;
|
||||
dc.name = zone.getName();
|
||||
dc.description = zone.getDescription();
|
||||
|
||||
// ---- State ----
|
||||
dc.status = Grouping.AllocationState.Enabled.equals(zone.getAllocationState()) ? "up" : "down";
|
||||
dc.local = "false";
|
||||
dc.quotaMode = "disabled";
|
||||
dc.storageFormat = "v5";
|
||||
|
||||
// ---- Versions (static but valid) ----
|
||||
final Version v48 = new Version();
|
||||
v48.major = 4;
|
||||
v48.minor = 8;
|
||||
dc.version = v48;
|
||||
dc.supportedVersions = new SupportedVersions(List.of(v48));
|
||||
|
||||
// ---- mac_pool (static placeholder) ----
|
||||
dc.macPool = Ref.of(basePath + "/macpools/default","default");
|
||||
|
||||
// ---- Related links ----
|
||||
dc.link = Arrays.asList(
|
||||
new Link(href + "/clusters", "clusters"),
|
||||
new Link(href + "/networks", "networks"),
|
||||
new Link(href + "/storagedomains", "storagedomains")
|
||||
);
|
||||
|
||||
return dc;
|
||||
}
|
||||
|
||||
public static List<DataCenter> toDCList(final List<DataCenterVO> srcList) {
|
||||
return srcList.stream()
|
||||
.map(DataCenterVOToDataCenterConverter::toDataCenter)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
// 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 java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.cloudstack.veeam.VeeamControlService;
|
||||
import org.apache.cloudstack.veeam.api.ApiService;
|
||||
import org.apache.cloudstack.veeam.api.DataCentersRouteHandler;
|
||||
import org.apache.cloudstack.veeam.api.dto.DataCenter;
|
||||
import org.apache.cloudstack.veeam.api.dto.DataCenters;
|
||||
import org.apache.cloudstack.veeam.api.dto.Link;
|
||||
import org.apache.cloudstack.veeam.api.dto.Storage;
|
||||
import org.apache.cloudstack.veeam.api.dto.StorageDomain;
|
||||
|
||||
import com.cloud.api.query.vo.ImageStoreJoinVO;
|
||||
import com.cloud.api.query.vo.StoragePoolJoinVO;
|
||||
|
||||
public class StoreVOToStorageDomainConverter {
|
||||
|
||||
/** Primary storage -> oVirt storage_domain (type=data) */
|
||||
public static StorageDomain toStorageDomain(final StoragePoolJoinVO pool) {
|
||||
final String basePath = VeeamControlService.ContextPath.value();
|
||||
|
||||
final String id = pool.getUuid();
|
||||
|
||||
StorageDomain sd = new StorageDomain();
|
||||
sd.id = id;
|
||||
final String href = href(basePath, ApiService.BASE_ROUTE + "/storagedomains/" + id);
|
||||
sd.href = href;
|
||||
|
||||
sd.name = pool.getName();
|
||||
sd.description = ""; // oVirt often returns empty string
|
||||
sd.comment = "";
|
||||
|
||||
// oVirt sample returns numbers as strings
|
||||
sd.available = Long.toString(pool.getCapacityBytes() - pool.getUsedBytes());
|
||||
sd.used = Long.toString(pool.getUsedBytes());
|
||||
sd.committed = Long.toString(pool.getCapacityBytes());
|
||||
|
||||
sd.type = "data";
|
||||
sd.status = mapPoolStatus(pool); // "active"/"inactive"/"maintenance" (approx)
|
||||
sd.master = "true"; // if you don’t have a concept, choose stable default
|
||||
sd.backup = "false";
|
||||
|
||||
sd.blockSize = "512"; // stable default unless you can compute it
|
||||
sd.externalStatus = "ok";
|
||||
sd.storageFormat = "v5";
|
||||
|
||||
sd.discardAfterDelete = "false";
|
||||
sd.wipeAfterDelete = "false";
|
||||
sd.supportsDiscard = "false";
|
||||
sd.supportsDiscardZeroesData = "false";
|
||||
|
||||
sd.warningLowSpaceIndicator = "10";
|
||||
sd.criticalSpaceActionBlocker = "5";
|
||||
|
||||
// Nested storage (try to extract if available)
|
||||
sd.storage = buildPrimaryStorage(pool);
|
||||
|
||||
// dc attachment
|
||||
String dcId = pool.getZoneUuid();
|
||||
DataCenter dc = new DataCenter();
|
||||
dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + dcId);
|
||||
dc.id = dcId;
|
||||
sd.dataCenters = new DataCenters(List.of(dc));
|
||||
|
||||
sd.link = defaultStorageDomainLinks(href, true, /*includeTemplates*/ true);
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
public static List<StorageDomain> toStorageDomainListFromPools(final List<StoragePoolJoinVO> pools) {
|
||||
return pools.stream().map(StoreVOToStorageDomainConverter::toStorageDomain).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Secondary/Image store -> oVirt storage_domain (type=image) */
|
||||
public static StorageDomain toStorageDomain(final ImageStoreJoinVO store) {
|
||||
final String basePath = VeeamControlService.ContextPath.value();
|
||||
|
||||
final String id = store.getUuid();
|
||||
|
||||
StorageDomain sd = new StorageDomain();
|
||||
sd.id = id;
|
||||
final String href = href(basePath, ApiService.BASE_ROUTE + "/storagedomains/" + id);
|
||||
sd.href = href;
|
||||
|
||||
sd.name = store.getName();
|
||||
sd.description = "";
|
||||
sd.comment = "";
|
||||
|
||||
// Many image repos don’t have these values readily; keep as "0" or omit (null)
|
||||
sd.committed = "0";
|
||||
sd.available = null; // oVirt’s glance example omitted available/used
|
||||
sd.used = null;
|
||||
|
||||
sd.type = "image";
|
||||
sd.status = "unattached"; // matches your sample for glance-like repo
|
||||
sd.master = "false";
|
||||
sd.backup = "false";
|
||||
|
||||
sd.blockSize = "512";
|
||||
sd.externalStatus = "ok";
|
||||
sd.storageFormat = "v1";
|
||||
|
||||
sd.discardAfterDelete = "false";
|
||||
sd.wipeAfterDelete = "false";
|
||||
sd.supportsDiscard = "false";
|
||||
sd.supportsDiscardZeroesData = "false";
|
||||
|
||||
sd.warningLowSpaceIndicator = "0";
|
||||
sd.criticalSpaceActionBlocker = "0";
|
||||
|
||||
sd.storage = buildImageStoreStorage(store);
|
||||
|
||||
// Optionally include dc attachment (your first object had it; second didn’t)
|
||||
String dcId = store.getZoneUuid();
|
||||
DataCenter dc = new DataCenter();
|
||||
dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + dcId);
|
||||
dc.id = dcId;
|
||||
sd.dataCenters = new DataCenters(List.of(dc));
|
||||
|
||||
sd.link = defaultStorageDomainLinks(href, false, /*includeTemplates*/ false);
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
public static List<StorageDomain> toStorageDomainListFromStores(final List<ImageStoreJoinVO> stores) {
|
||||
return stores.stream().map(StoreVOToStorageDomainConverter::toStorageDomain).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// ----------- Helpers -----------
|
||||
|
||||
private static Storage buildPrimaryStorage(StoragePoolJoinVO pool) {
|
||||
Storage st = new Storage();
|
||||
st.type = mapPrimaryStorageType(pool);
|
||||
|
||||
// If you can parse details/url, fill these. If not, keep empty strings like oVirt.
|
||||
// For NFS pools in CloudStack, URL is often like: nfs://10.0.32.4/path or 10.0.32.4:/path
|
||||
String url = null;
|
||||
try {
|
||||
url = pool.getHostAddress(); // sometimes exists in VO; if not, ignore
|
||||
} catch (Exception ignored) { }
|
||||
|
||||
if ("nfs".equals(st.type)) {
|
||||
// best-effort placeholders
|
||||
st.address = ""; // fill if you can parse
|
||||
st.path = ""; // fill if you can parse
|
||||
st.mountOptions = "";
|
||||
st.nfsVersion = "auto";
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
private static Storage buildImageStoreStorage(ImageStoreJoinVO store) {
|
||||
Storage st = new Storage();
|
||||
|
||||
// Match your sample: glance store => type=glance
|
||||
// If you want "nfs" for secondary, map based on provider/protocol instead.
|
||||
st.type = mapImageStorageType(store);
|
||||
|
||||
if ("nfs".equals(st.type)) {
|
||||
st.address = "";
|
||||
st.path = "";
|
||||
st.mountOptions = "";
|
||||
st.nfsVersion = "auto";
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
private static List<Link> defaultStorageDomainLinks(String basePath, boolean includeDisks, boolean includeTemplates) {
|
||||
// Mirrors the rels you pasted; keep stable order.
|
||||
// You can add/remove based on what endpoints you actually implement.
|
||||
List<Link> common = new java.util.ArrayList<>();
|
||||
common.add(new Link("diskprofiles", href(basePath, "/diskprofiles")));
|
||||
if (includeDisks) {
|
||||
common.add(new Link("disks", href(basePath, "/disks")));
|
||||
common.add(new Link("storageconnections", href(basePath, "/storageconnections")));
|
||||
}
|
||||
common.add(new Link("permissions", href(basePath, "/permissions")));
|
||||
if (includeTemplates) {
|
||||
common.add(new Link("templates", href(basePath, "/templates")));
|
||||
common.add(new Link("vms", href(basePath, "/vms")));
|
||||
} else {
|
||||
common.add(new Link("images", href(basePath, "/images")));
|
||||
}
|
||||
common.add(new Link("disksnapshots", href(basePath, "/disksnapshots")));
|
||||
return common;
|
||||
}
|
||||
|
||||
private static String mapPoolStatus(StoragePoolJoinVO pool) {
|
||||
// This is approximate; adjust if you have better signals.
|
||||
try {
|
||||
Object status = pool.getStatus(); // often StoragePoolStatus enum
|
||||
if (status != null) {
|
||||
String s = status.toString().toLowerCase();
|
||||
if (s.contains("up") || s.contains("enabled")) return "active";
|
||||
if (s.contains("maintenance")) return "maintenance";
|
||||
}
|
||||
} catch (Exception ignored) { }
|
||||
return "inactive";
|
||||
}
|
||||
|
||||
private static String mapPrimaryStorageType(StoragePoolJoinVO pool) {
|
||||
try {
|
||||
Object t = pool.getPoolType(); // often StoragePoolType enum
|
||||
if (t != null) {
|
||||
String s = t.toString().toLowerCase();
|
||||
if (s.contains("networkfilesystem") || s.contains("nfs")) return "nfs";
|
||||
if (s.contains("iscsi")) return "iscsi";
|
||||
if (s.contains("filesystem")) return "posixfs";
|
||||
if (s.contains("rbd") || s.contains("ceph")) return "cinder"; // not perfect; pick stable
|
||||
}
|
||||
} catch (Exception ignored) { }
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
private static String mapImageStorageType(ImageStoreJoinVO store) {
|
||||
// If your secondary store is S3/NFS/etc, you may want different mapping.
|
||||
// For your oVirt sample, "glance" is used for an image repo.
|
||||
try {
|
||||
String provider = store.getProviderName(); // may exist
|
||||
if (provider != null && provider.toLowerCase().contains("glance")) return "glance";
|
||||
} catch (Exception ignored) { }
|
||||
return "glance";
|
||||
}
|
||||
|
||||
private static String href(String baseUrl, String path) {
|
||||
if (baseUrl.endsWith("/")) baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
|
||||
return baseUrl + path;
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,10 @@ import java.util.stream.Collectors;
|
|||
import org.apache.cloudstack.veeam.VeeamControlService;
|
||||
import org.apache.cloudstack.veeam.api.ApiService;
|
||||
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
|
||||
import org.apache.cloudstack.veeam.api.dto.Bios;
|
||||
import org.apache.cloudstack.veeam.api.dto.Cpu;
|
||||
import org.apache.cloudstack.veeam.api.dto.Link;
|
||||
import org.apache.cloudstack.veeam.api.dto.Os;
|
||||
import org.apache.cloudstack.veeam.api.dto.Ref;
|
||||
import org.apache.cloudstack.veeam.api.dto.Topology;
|
||||
import org.apache.cloudstack.veeam.api.dto.Vm;
|
||||
|
|
@ -76,13 +79,25 @@ public final class UserVmJoinVOToVmConverter {
|
|||
basePath + ApiService.BASE_ROUTE,
|
||||
"cluster",
|
||||
src.getHostUuid());
|
||||
dst.memory = (long) src.getRamSize();
|
||||
dst.memory = src.getRamSize() * 1024L * 1024L;
|
||||
|
||||
dst.cpu = new Cpu(src.getArch(), new Topology(src.getCpu(), 1, 1));
|
||||
dst.os = null;
|
||||
dst.bios = null;
|
||||
dst.actions = null;
|
||||
dst.link = null;
|
||||
dst.os = new Os();
|
||||
dst.os.type = src.getGuestOsId() % 2 == 0
|
||||
? "windows"
|
||||
: "linux";
|
||||
dst.bios = new Bios();
|
||||
dst.bios.type = "legacy";
|
||||
dst.type = "server";
|
||||
dst.origin = "ovirt";
|
||||
dst.actions = null;dst.link = List.of(
|
||||
new Link("diskattachments",
|
||||
dst.href + "/diskattachments"),
|
||||
new Link("nics",
|
||||
dst.href + "/nics"),
|
||||
new Link("snapshots",
|
||||
dst.href + "/snapshots")
|
||||
);
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
/**
|
||||
* Root response for GET /ovirt-engine/api
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JacksonXmlRootElement(localName = "api")
|
||||
public final class Api {
|
||||
|
||||
// <link .../> repeated
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<Link> link;
|
||||
|
||||
// <engine_backup/> (empty element)
|
||||
@JacksonXmlProperty(localName = "engine_backup")
|
||||
public EmptyElement engineBackup;
|
||||
|
||||
@JacksonXmlProperty(localName = "product_info")
|
||||
public ProductInfo productInfo;
|
||||
|
||||
@JacksonXmlProperty(localName = "special_objects")
|
||||
public SpecialObjects specialObjects;
|
||||
|
||||
@JacksonXmlProperty(localName = "summary")
|
||||
public Summary summary;
|
||||
|
||||
// Keep as String to avoid timezone/date parsing friction; you control formatting.
|
||||
@JacksonXmlProperty(localName = "time")
|
||||
public Long time;
|
||||
|
||||
@JacksonXmlProperty(localName = "authenticated_user")
|
||||
public Ref authenticatedUser;
|
||||
|
||||
@JacksonXmlProperty(localName = "effective_user")
|
||||
public Ref effectiveUser;
|
||||
|
||||
public Api() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
// 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JacksonXmlRootElement(localName = "data_center")
|
||||
public final class DataCenter {
|
||||
|
||||
// keep strings to match oVirt JSON ("false", "disabled", "up", "v5", etc.)
|
||||
public String local;
|
||||
|
||||
@JsonProperty("quota_mode")
|
||||
public String quotaMode;
|
||||
|
||||
public String status;
|
||||
|
||||
@JsonProperty("storage_format")
|
||||
public String storageFormat;
|
||||
|
||||
@JsonProperty("supported_versions")
|
||||
public SupportedVersions supportedVersions;
|
||||
|
||||
public Version version;
|
||||
|
||||
@JsonProperty("mac_pool")
|
||||
public Ref macPool;
|
||||
|
||||
public Actions actions;
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<Link> link;
|
||||
|
||||
public String href;
|
||||
public String id;
|
||||
|
||||
public DataCenter() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
/**
|
||||
* Root collection wrapper:
|
||||
* {
|
||||
* "data_center": [ { ... } ]
|
||||
* }
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JacksonXmlRootElement(localName = "data_centers")
|
||||
@JsonPropertyOrder({ "data_center" })
|
||||
public final class DataCenters {
|
||||
|
||||
@JsonProperty("data_center")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<DataCenter> dataCenter;
|
||||
|
||||
public DataCenters() {}
|
||||
public DataCenters(final List<DataCenter> dataCenter) {
|
||||
this.dataCenter = dataCenter;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
@JsonSerialize(using = EmptyElementSerializer.class)
|
||||
public final class EmptyElement {
|
||||
public EmptyElement() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Serializes as an "empty object".
|
||||
* With Jackson XML this becomes an empty element: <engine_backup/>.
|
||||
*/
|
||||
public final class EmptyElementSerializer extends JsonSerializer<EmptyElement> {
|
||||
@Override
|
||||
public void serialize(EmptyElement value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
gen.writeStartObject();
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class ProductInfo {
|
||||
|
||||
@JacksonXmlProperty(localName = "instance_id")
|
||||
public String instanceId;
|
||||
|
||||
@JacksonXmlProperty(localName = "name")
|
||||
public String name;
|
||||
|
||||
@JacksonXmlProperty(localName = "version")
|
||||
public Version version;
|
||||
|
||||
public ProductInfo() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class SpecialObjectRef {
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "href")
|
||||
public String href;
|
||||
|
||||
@JacksonXmlProperty(isAttribute = true, localName = "id")
|
||||
public String id;
|
||||
|
||||
public SpecialObjectRef() {}
|
||||
|
||||
public SpecialObjectRef(String href, String id) {
|
||||
this.href = href;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class SpecialObjects {
|
||||
|
||||
@JacksonXmlProperty(localName = "blank_template")
|
||||
public SpecialObjectRef blankTemplate;
|
||||
|
||||
@JacksonXmlProperty(localName = "root_tag")
|
||||
public SpecialObjectRef rootTag;
|
||||
|
||||
public SpecialObjects() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class Storage {
|
||||
|
||||
public String type; // nfs / glance
|
||||
|
||||
// nfs-ish fields (optional)
|
||||
public String address;
|
||||
public String path;
|
||||
|
||||
@JsonProperty("mount_options")
|
||||
@JacksonXmlProperty(localName = "mount_options")
|
||||
public String mountOptions;
|
||||
|
||||
@JsonProperty("nfs_version")
|
||||
@JacksonXmlProperty(localName = "nfs_version")
|
||||
public String nfsVersion;
|
||||
|
||||
public Storage() {}
|
||||
}
|
||||
|
|
@ -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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JacksonXmlRootElement(localName = "storage_domain")
|
||||
public final class StorageDomain {
|
||||
|
||||
// Identifiers
|
||||
public String id;
|
||||
public String href;
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
public String comment;
|
||||
|
||||
// oVirt returns these as strings in your sample
|
||||
public String available;
|
||||
public String used;
|
||||
public String committed;
|
||||
|
||||
@JsonProperty("block_size")
|
||||
@JacksonXmlProperty(localName = "block_size")
|
||||
public String blockSize;
|
||||
|
||||
@JsonProperty("warning_low_space_indicator")
|
||||
@JacksonXmlProperty(localName = "warning_low_space_indicator")
|
||||
public String warningLowSpaceIndicator;
|
||||
|
||||
@JsonProperty("critical_space_action_blocker")
|
||||
@JacksonXmlProperty(localName = "critical_space_action_blocker")
|
||||
public String criticalSpaceActionBlocker;
|
||||
|
||||
public String status; // e.g. "unattached" (optional in your first object)
|
||||
public String type; // data / image / iso / export
|
||||
|
||||
public String master; // "true"/"false"
|
||||
public String backup; // "true"/"false"
|
||||
|
||||
@JsonProperty("external_status")
|
||||
@JacksonXmlProperty(localName = "external_status")
|
||||
public String externalStatus; // "ok"
|
||||
|
||||
@JsonProperty("storage_format")
|
||||
@JacksonXmlProperty(localName = "storage_format")
|
||||
public String storageFormat; // v5 / v1
|
||||
|
||||
@JsonProperty("discard_after_delete")
|
||||
@JacksonXmlProperty(localName = "discard_after_delete")
|
||||
public String discardAfterDelete;
|
||||
|
||||
@JsonProperty("wipe_after_delete")
|
||||
@JacksonXmlProperty(localName = "wipe_after_delete")
|
||||
public String wipeAfterDelete;
|
||||
|
||||
@JsonProperty("supports_discard")
|
||||
@JacksonXmlProperty(localName = "supports_discard")
|
||||
public String supportsDiscard;
|
||||
|
||||
@JsonProperty("supports_discard_zeroes_data")
|
||||
@JacksonXmlProperty(localName = "supports_discard_zeroes_data")
|
||||
public String supportsDiscardZeroesData;
|
||||
|
||||
// Nested
|
||||
public Storage storage;
|
||||
|
||||
@JsonProperty("data_centers")
|
||||
@JacksonXmlProperty(localName = "data_centers")
|
||||
public DataCenters dataCenters;
|
||||
|
||||
public Actions actions;
|
||||
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<Link> link;
|
||||
|
||||
public StorageDomain() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
@JacksonXmlRootElement(localName = "storage_domains")
|
||||
public final class StorageDomains {
|
||||
|
||||
@JsonProperty("storage_domain")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<StorageDomain> storageDomain;
|
||||
|
||||
public StorageDomains() {}
|
||||
public StorageDomains(List<StorageDomain> storageDomain) {
|
||||
this.storageDomain = storageDomain;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class Summary {
|
||||
|
||||
@JacksonXmlProperty(localName = "hosts")
|
||||
public SummaryCount hosts;
|
||||
|
||||
@JacksonXmlProperty(localName = "storage_domains")
|
||||
public SummaryCount storageDomains;
|
||||
|
||||
@JacksonXmlProperty(localName = "users")
|
||||
public SummaryCount users;
|
||||
|
||||
@JacksonXmlProperty(localName = "vms")
|
||||
public SummaryCount vms;
|
||||
|
||||
public Summary() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class SummaryCount {
|
||||
|
||||
@JacksonXmlProperty(localName = "active")
|
||||
public Integer active;
|
||||
|
||||
@JacksonXmlProperty(localName = "total")
|
||||
public Integer total;
|
||||
|
||||
public SummaryCount() {}
|
||||
|
||||
public SummaryCount(Integer active, Integer total) {
|
||||
this.active = active;
|
||||
this.total = total;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class SupportedVersions {
|
||||
|
||||
@JsonProperty("version")
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<Version> version;
|
||||
|
||||
public SupportedVersions() {}
|
||||
public SupportedVersions(final List<Version> version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// 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.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public final class Version {
|
||||
|
||||
@JacksonXmlProperty(localName = "build")
|
||||
public String build;
|
||||
|
||||
@JacksonXmlProperty(localName = "full_version")
|
||||
public String fullVersion;
|
||||
|
||||
@JacksonXmlProperty(localName = "major")
|
||||
public Integer major;
|
||||
|
||||
@JacksonXmlProperty(localName = "minor")
|
||||
public Integer minor;
|
||||
|
||||
@JacksonXmlProperty(localName = "revision")
|
||||
public Integer revision;
|
||||
|
||||
public Version() {}
|
||||
}
|
||||
|
|
@ -61,6 +61,10 @@ public final class Vm {
|
|||
public Os os;
|
||||
public Bios bios;
|
||||
|
||||
public boolean stateless; // true|false
|
||||
public String type; // "server"
|
||||
public String origin; // "ovirt"
|
||||
|
||||
public Actions actions; // actions.link[]
|
||||
@JacksonXmlElementWrapper(useWrapping = false)
|
||||
public List<Link> link; // related resources
|
||||
|
|
|
|||
|
|
@ -0,0 +1,249 @@
|
|||
// 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.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
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;
|
||||
|
||||
public class BearerOrBasicAuthFilter implements Filter {
|
||||
|
||||
// Keep these aligned with SsoService (move to ConfigKeys later)
|
||||
public static final List<String> REQUIRED_SCOPES = List.of("ovirt-app-admin", "ovirt-app-portal");
|
||||
public static final String ISSUER = "veeam-control";
|
||||
private static final String HMAC_SECRET = "change-this-super-secret-key-change-this";
|
||||
|
||||
@Override public void init(FilterConfig filterConfig) {}
|
||||
@Override public void destroy() {}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
final HttpServletRequest req = (HttpServletRequest) request;
|
||||
final HttpServletResponse resp = (HttpServletResponse) response;
|
||||
|
||||
final String auth = req.getHeader("Authorization");
|
||||
if (auth != null && auth.regionMatches(true, 0, "Bearer ", 0, 7)) {
|
||||
final String token = auth.substring(7).trim();
|
||||
if (token.isEmpty()) {
|
||||
unauthorized(req, resp, "invalid_token", "Missing Bearer token");
|
||||
return;
|
||||
}
|
||||
if (!verifyJwtHs256(token)) {
|
||||
unauthorized(req, resp, "invalid_token", "Invalid or expired token");
|
||||
return;
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// Optional fallback: Basic (handy for manual testing).
|
||||
if (auth != null && auth.regionMatches(true, 0, "Basic ", 0, 6)) {
|
||||
if (!verifyBasic(auth.substring(6))) {
|
||||
unauthorized(req, resp, "invalid_client", "Invalid Basic credentials");
|
||||
return;
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
unauthorized(req, resp, "invalid_token", "Missing Authorization");
|
||||
}
|
||||
|
||||
private boolean verifyBasic(String b64) {
|
||||
final String expectedUser = VeeamControlService.Username.value();
|
||||
final String expectedPass = VeeamControlService.Password.value();
|
||||
|
||||
final String decoded;
|
||||
try {
|
||||
decoded = new String(Base64.getDecoder().decode(b64), StandardCharsets.UTF_8);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int idx = decoded.indexOf(':');
|
||||
if (idx <= 0) return false;
|
||||
|
||||
final String user = decoded.substring(0, idx);
|
||||
final String pass = decoded.substring(idx + 1);
|
||||
|
||||
return constantTimeEquals(user, expectedUser) && constantTimeEquals(pass, expectedPass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal JWT verification:
|
||||
* - HS256 signature
|
||||
* - "iss" matches
|
||||
* - "exp" not expired
|
||||
* - "scope" contains REQUIRED_SCOPES (space-separated)
|
||||
*
|
||||
* NOTE: This does not parse JSON robustly; it’s sufficient for the token you mint in SsoService.
|
||||
* If you want robust parsing, switch to Nimbus and keep the rest the same.
|
||||
*/
|
||||
private boolean verifyJwtHs256(String token) {
|
||||
final String[] parts = token.split("\\.");
|
||||
if (parts.length != 3) return false;
|
||||
|
||||
final String headerB64 = parts[0];
|
||||
final String payloadB64 = parts[1];
|
||||
final String sigB64 = parts[2];
|
||||
|
||||
final byte[] expectedSig;
|
||||
try {
|
||||
expectedSig = hmacSha256((headerB64 + "." + payloadB64).getBytes(StandardCharsets.UTF_8),
|
||||
HMAC_SECRET.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] providedSig;
|
||||
try {
|
||||
providedSig = Base64.getUrlDecoder().decode(sigB64);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!constantTimeEquals(expectedSig, providedSig)) return false;
|
||||
|
||||
final String payloadJson;
|
||||
try {
|
||||
payloadJson = new String(Base64.getUrlDecoder().decode(payloadB64), StandardCharsets.UTF_8);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Super small “claims” extraction (good enough for our minting format)
|
||||
final String iss = JsonMini.getString(payloadJson, "iss");
|
||||
final String scope = JsonMini.getString(payloadJson, "scope");
|
||||
final Long exp = JsonMini.getLong(payloadJson, "exp");
|
||||
|
||||
if (iss == null || !ISSUER.equals(iss)) return false;
|
||||
if (exp == null || Instant.now().getEpochSecond() >= exp) return false;
|
||||
if (scope == null || !hasRequiredScopes(scope)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasRequiredScopes(String scope) {
|
||||
String[] scopes = scope.split("\\s+");
|
||||
for (String required : REQUIRED_SCOPES) {
|
||||
if (!hasScope(scopes, required)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean hasScope(String[] scopes, String required) {
|
||||
for (String scope : scopes) {
|
||||
if (scope.equals(required)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static byte[] hmacSha256(byte[] data, byte[] key) throws Exception {
|
||||
final Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
}
|
||||
|
||||
private static void unauthorized(HttpServletRequest req, HttpServletResponse resp,
|
||||
String error, String desc) throws IOException {
|
||||
|
||||
// IMPORTANT: don’t throw (your current filter throws and Jetty turns it into 500) :contentReference[oaicite:3]{index=3}
|
||||
resp.resetBuffer();
|
||||
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
|
||||
// Helpful for OAuth clients:
|
||||
resp.setHeader("WWW-Authenticate",
|
||||
"Bearer realm=\"Veeam Integration\", error=\"" + esc(error) + "\", error_description=\"" + esc(desc) + "\"");
|
||||
|
||||
final String accept = req.getHeader("Accept");
|
||||
final boolean wantsJson = accept != null && accept.toLowerCase().contains("application/json");
|
||||
|
||||
resp.setCharacterEncoding("UTF-8");
|
||||
if (wantsJson) {
|
||||
resp.setContentType("application/json; charset=UTF-8");
|
||||
resp.getWriter().write("{\"error\":\"" + esc(error) + "\",\"error_description\":\"" + esc(desc) + "\"}");
|
||||
} else {
|
||||
resp.setContentType("text/html; charset=UTF-8");
|
||||
resp.getWriter().write("<html><head><title>Error</title></head><body>Unauthorized</body></html>");
|
||||
}
|
||||
resp.getWriter().flush();
|
||||
}
|
||||
|
||||
private static String esc(String s) {
|
||||
return s == null ? "" : s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
private static boolean constantTimeEquals(String a, String b) {
|
||||
if (a == null || b == null) return false;
|
||||
return constantTimeEquals(a.getBytes(StandardCharsets.UTF_8), b.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private static boolean constantTimeEquals(byte[] x, byte[] y) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Tiny JSON extractor for flat string/number claims. Good enough for tokens you mint.
|
||||
static final class JsonMini {
|
||||
static String getString(String json, String key) {
|
||||
final String needle = "\"" + key + "\":";
|
||||
int i = json.indexOf(needle);
|
||||
if (i < 0) return null;
|
||||
i += needle.length();
|
||||
while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;
|
||||
if (i >= json.length() || json.charAt(i) != '"') return null;
|
||||
i++;
|
||||
int j = json.indexOf('"', i);
|
||||
if (j < 0) return null;
|
||||
return json.substring(i, j);
|
||||
}
|
||||
|
||||
static Long getLong(String json, String key) {
|
||||
final String needle = "\"" + key + "\":";
|
||||
int i = json.indexOf(needle);
|
||||
if (i < 0) return null;
|
||||
i += needle.length();
|
||||
while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;
|
||||
int j = i;
|
||||
while (j < json.length() && (Character.isDigit(json.charAt(j)))) j++;
|
||||
if (j == i) return null;
|
||||
return Long.parseLong(json.substring(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,27 +18,40 @@
|
|||
package org.apache.cloudstack.veeam.sso;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.cloudstack.veeam.RouteHandler;
|
||||
import org.apache.cloudstack.veeam.VeeamControlService;
|
||||
import org.apache.cloudstack.veeam.VeeamControlServlet;
|
||||
import org.apache.cloudstack.veeam.filter.BearerOrBasicAuthFilter;
|
||||
import org.apache.cloudstack.veeam.utils.Negotiation;
|
||||
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
|
||||
public class SsoService extends ManagerBase implements RouteHandler {
|
||||
private static final String BASE_ROUTE = "/sso";
|
||||
private static final String HMAC_SECRET = "change-this-super-secret-key-change-this"; // >= 32 chars recommended
|
||||
private static final long DEFAULT_TTL_SECONDS = 3600;
|
||||
|
||||
// Replace with your real credential validation (CloudStack account, config, etc.)
|
||||
private final PasswordAuthenticator authenticator = new StaticPasswordAuthenticator();
|
||||
|
||||
@Override
|
||||
public boolean canHandle(String method, String path) {
|
||||
return path.startsWith("/sso");
|
||||
return getSanitizedPath(path).startsWith(BASE_ROUTE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
if ("/sso/oauth/token".equals(path)) {
|
||||
final String sanitizedPath = getSanitizedPath(path);
|
||||
if (sanitizedPath.equals(BASE_ROUTE + "/oauth/token")) {
|
||||
handleToken(req, resp, outFormat, io);
|
||||
return;
|
||||
}
|
||||
|
|
@ -46,27 +59,127 @@ public class SsoService extends ManagerBase implements RouteHandler {
|
|||
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Not found");
|
||||
}
|
||||
|
||||
protected void handleToken(HttpServletRequest req, HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io)
|
||||
throws IOException {
|
||||
protected void handleToken(HttpServletRequest req, HttpServletResponse resp,
|
||||
Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
|
||||
|
||||
// Typically POST; you only listed 200/400/401 -> treat others as 400
|
||||
if (!"POST".equals(req.getMethod())) {
|
||||
throw VeeamControlServlet.Error.badRequest("token endpoint requires POST");
|
||||
if (!"POST".equalsIgnoreCase(req.getMethod())) {
|
||||
// oVirt-like: 405 is fine; if you strictly want 400, change to SC_BAD_REQUEST
|
||||
resp.setHeader("Allow", "POST");
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_METHOD_NOT_ALLOWED,
|
||||
Map.of("error", "method_not_allowed", "message", "token endpoint requires POST"), outFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume x-www-form-urlencoded for OAuth token requests (common)
|
||||
String grantType = req.getParameter("grant_type");
|
||||
if (grantType == null || grantType.isBlank()) {
|
||||
throw VeeamControlServlet.Error.badRequest("Missing parameter: grant_type");
|
||||
// OAuth password grant uses x-www-form-urlencoded. With servlet containers this usually populates getParameter().
|
||||
final String grantType = trimToNull(req.getParameter("grant_type"));
|
||||
final String scope = trimToNull(req.getParameter("scope")); // typically "ovirt-app-api"
|
||||
final String username = trimToNull(req.getParameter("username"));
|
||||
final String password = trimToNull(req.getParameter("password"));
|
||||
|
||||
if (grantType == null) {
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_BAD_REQUEST,
|
||||
Map.of("error", "invalid_request", "error_description", "Missing parameter: grant_type"), outFormat);
|
||||
return;
|
||||
}
|
||||
if (!"password".equals(grantType)) {
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_BAD_REQUEST,
|
||||
Map.of("error", "unsupported_grant_type", "error_description", "Only grant_type=password is supported"), outFormat);
|
||||
return;
|
||||
}
|
||||
if (username == null || password == null) {
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_BAD_REQUEST,
|
||||
Map.of("error", "invalid_request", "error_description", "Missing username/password"), outFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: 401 is normally handled by BasicAuthFilter; keep hook here if you later move auth here.
|
||||
// if (!authorized) throw VeeamControlServlet.Error.unauthorized("Unauthorized");
|
||||
if (!authenticator.authenticate(username, password)) {
|
||||
// 401 for bad creds
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_UNAUTHORIZED,
|
||||
Map.of("error", "invalid_grant", "error_description", "Invalid credentials"), outFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
io.getWriter().write(resp, 200, Map.of(
|
||||
"access_token", "dummy-token",
|
||||
"token_type", "bearer",
|
||||
"expires_in", 3600
|
||||
), outFormat);
|
||||
final String effectiveScope = (scope == null) ? "ovirt-app-api" : scope;
|
||||
|
||||
final long ttl = DEFAULT_TTL_SECONDS;
|
||||
final String token;
|
||||
try {
|
||||
token = JwtUtil.issueHs256Jwt(BearerOrBasicAuthFilter.ISSUER, username, effectiveScope, ttl, HMAC_SECRET);
|
||||
} catch (Exception e) {
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
|
||||
Map.of("error", "server_error", "error_description", "Failed to issue token"), outFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
final Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("access_token", token);
|
||||
payload.put("token_type", "bearer");
|
||||
payload.put("expires_in", ttl);
|
||||
payload.put("scope", effectiveScope);
|
||||
|
||||
io.getWriter().write(resp, HttpServletResponse.SC_OK, payload, outFormat);
|
||||
}
|
||||
|
||||
private static String trimToNull(String s) {
|
||||
if (s == null) return null;
|
||||
s = s.trim();
|
||||
return s.isEmpty() ? null : s;
|
||||
}
|
||||
|
||||
// ---------- Minimal auth helpers (replace later) ----------
|
||||
|
||||
interface PasswordAuthenticator {
|
||||
boolean authenticate(String username, String password);
|
||||
}
|
||||
|
||||
static final class StaticPasswordAuthenticator implements PasswordAuthenticator {
|
||||
StaticPasswordAuthenticator() {
|
||||
}
|
||||
@Override
|
||||
public boolean authenticate(String username, String password) {
|
||||
return VeeamControlService.Username.value().equals(username) &&
|
||||
VeeamControlService.Password.value().equals(password);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Minimal JWT HS256 without extra libs ----------
|
||||
// (If you prefer Nimbus, I can convert this to nimbus-jose-jwt; this keeps dependencies tiny.)
|
||||
|
||||
static final class JwtUtil {
|
||||
static String issueHs256Jwt(String issuer, String subject, String scope, long ttlSeconds, String secret) throws Exception {
|
||||
long now = Instant.now().getEpochSecond();
|
||||
long exp = now + ttlSeconds;
|
||||
|
||||
String headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
||||
String payloadJson =
|
||||
"{"
|
||||
+ "\"iss\":\"" + jsonEscape(issuer) + "\","
|
||||
+ "\"sub\":\"" + jsonEscape(subject) + "\","
|
||||
+ "\"scope\":\"" + jsonEscape(scope) + "\","
|
||||
+ "\"iat\":" + now + ","
|
||||
+ "\"exp\":" + exp
|
||||
+ "}";
|
||||
|
||||
String header = b64Url(headerJson.getBytes("UTF-8"));
|
||||
String payload = b64Url(payloadJson.getBytes("UTF-8"));
|
||||
String signingInput = header + "." + payload;
|
||||
|
||||
byte[] sig = hmacSha256(signingInput.getBytes("UTF-8"), secret.getBytes("UTF-8"));
|
||||
return signingInput + "." + b64Url(sig);
|
||||
}
|
||||
|
||||
static byte[] hmacSha256(byte[] data, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(key, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
}
|
||||
|
||||
static String b64Url(byte[] in) {
|
||||
return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(in);
|
||||
}
|
||||
|
||||
static String jsonEscape(String s) {
|
||||
return s.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// 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 com.cloud.utils.Pair;
|
||||
|
||||
public class PathUtil {
|
||||
|
||||
public static Pair<String, String> extractIdAndSubPath(final String path, final String baseRoute) {
|
||||
|
||||
// baseRoute = "/api/datacenters"
|
||||
if (!path.startsWith(baseRoute)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove base route
|
||||
String rest = path.substring(baseRoute.length());
|
||||
|
||||
// Expect "" or "/{id}" or "/{id}/{sub}"
|
||||
if (rest.isEmpty()) {
|
||||
return null; // /api/datacenters (no id)
|
||||
}
|
||||
|
||||
if (!rest.startsWith("/")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
rest = rest.substring(1); // remove leading '/'
|
||||
|
||||
final String[] parts = rest.split("/", -1);
|
||||
|
||||
if (parts.length == 1) {
|
||||
// /api/datacenters/{id}
|
||||
if (parts[0].isEmpty()) return null;
|
||||
return new Pair<>(parts[0], null);
|
||||
}
|
||||
|
||||
if (parts.length == 2) {
|
||||
// /api/datacenters/{id}/{subPath}
|
||||
if (parts[0].isEmpty() || parts[1].isEmpty()) return null;
|
||||
return new Pair<>(parts[0], parts[1]);
|
||||
}
|
||||
|
||||
// deeper paths not handled here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ package org.apache.cloudstack.veeam.utils;
|
|||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
public class ResponseMapper {
|
||||
|
|
@ -36,6 +37,7 @@ public class ResponseMapper {
|
|||
|
||||
private static void configure(final ObjectMapper mapper) {
|
||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
|
||||
// If you ever add enums etc:
|
||||
// mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||
// mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
|
||||
|
|
|
|||
|
|
@ -24,8 +24,11 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.apache.cloudstack.veeam.api.dto.Fault;
|
||||
import org.apache.cloudstack.veeam.api.response.FaultResponse;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public final class ResponseWriter {
|
||||
private static final Logger LOGGER = LogManager.getLogger(ResponseWriter.class);
|
||||
|
||||
private final ResponseMapper mapper;
|
||||
|
||||
|
|
@ -62,6 +65,8 @@ public final class ResponseWriter {
|
|||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Writing response: {}\n{}", status, payload);
|
||||
|
||||
resp.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||
resp.setHeader("Content-Type", contentType);
|
||||
resp.getWriter().write(payload);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
<bean id="veeamControlApiService" class="org.apache.cloudstack.veeam.api.ApiService" />
|
||||
<bean id="vmsRouteHandler" class="org.apache.cloudstack.veeam.api.VmsRouteHandler"/>
|
||||
<bean id="dataCentersRouteHandler" class="org.apache.cloudstack.veeam.api.DataCentersRouteHandler"/>
|
||||
<bean id="veeamControlSsoService" class="org.apache.cloudstack.veeam.sso.SsoService"/>
|
||||
|
||||
<bean id="veeamControlService" class="org.apache.cloudstack.veeam.VeeamControlServiceImpl" >
|
||||
|
|
|
|||
|
|
@ -30,8 +30,11 @@ import com.cloud.utils.PropertiesUtil;
|
|||
|
||||
public class ServerPropertiesUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ServerPropertiesUtil.class);
|
||||
|
||||
protected static final String PROPERTIES_FILE = "server.properties";
|
||||
protected static final AtomicReference<Properties> propertiesRef = new AtomicReference<>();
|
||||
protected static final String KEYSTORE_FILE = "https.keystore";
|
||||
protected static final String KEYSTORE_PASSWORD = "https.keystore.password";
|
||||
|
||||
public static String getProperty(String name) {
|
||||
Properties props = propertiesRef.get();
|
||||
|
|
@ -55,4 +58,12 @@ public class ServerPropertiesUtil {
|
|||
}
|
||||
return tempProps.getProperty(name);
|
||||
}
|
||||
|
||||
public static String getKeystoreFile() {
|
||||
return getProperty(KEYSTORE_FILE);
|
||||
}
|
||||
|
||||
public static String getKeystorePassword() {
|
||||
return getProperty(KEYSTORE_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue