nvazquez commented on a change in pull request #3553: [WIP] [DO NOT MERGE] 
CloudStack Backup & Recovery Framework
URL: https://github.com/apache/cloudstack/pull/3553#discussion_r357928075
 
 

 ##########
 File path: 
plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java
 ##########
 @@ -0,0 +1,661 @@
+// 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.backup.veeam;
+
+import static 
org.apache.cloudstack.backup.VeeamBackupProvider.BACKUP_IDENTIFIER;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.UUID;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.backup.BackupOffering;
+import org.apache.cloudstack.backup.Backup;
+import org.apache.cloudstack.backup.veeam.api.BackupJobCloneInfo;
+import org.apache.cloudstack.backup.veeam.api.CreateObjectInJobSpec;
+import org.apache.cloudstack.backup.veeam.api.EntityReferences;
+import org.apache.cloudstack.backup.veeam.api.HierarchyItem;
+import org.apache.cloudstack.backup.veeam.api.HierarchyItems;
+import org.apache.cloudstack.backup.veeam.api.Job;
+import org.apache.cloudstack.backup.veeam.api.JobCloneSpec;
+import org.apache.cloudstack.backup.veeam.api.Link;
+import org.apache.cloudstack.backup.veeam.api.ObjectInJob;
+import org.apache.cloudstack.backup.veeam.api.ObjectsInJob;
+import org.apache.cloudstack.backup.veeam.api.Ref;
+import org.apache.cloudstack.backup.veeam.api.RestoreSession;
+import org.apache.cloudstack.backup.veeam.api.Task;
+import org.apache.cloudstack.utils.security.SSLUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CookieStore;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.nio.TrustAllManager;
+import com.cloud.utils.ssh.SshHelper;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
+
+public class VeeamClient {
+    private static final Logger LOG = Logger.getLogger(VeeamClient.class);
+
+    private final URI apiURI;
+
+    private final HttpClient httpClient;
+    private final HttpClientContext httpContext = HttpClientContext.create();
+    private final CookieStore httpCookieStore = new BasicCookieStore();
+
+    private String veeamServerIp;
+    private String veeamServerUsername;
+    private String veeamServerPassword;
+    private final int veeamServerPort = 22;
+
+    public VeeamClient(final String url, final String username, final String 
password, final boolean validateCertificate, final int timeout) throws 
URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
+        this.apiURI = new URI(url);
+
+        final CredentialsProvider provider = new BasicCredentialsProvider();
+        provider.setCredentials(AuthScope.ANY, new 
UsernamePasswordCredentials(username, password));
+        final HttpHost adminHost = new HttpHost(this.apiURI.getHost(), 
this.apiURI.getPort(), this.apiURI.getScheme());
+        final AuthCache authCache = new BasicAuthCache();
+        authCache.put(adminHost, new BasicScheme());
+
+        this.httpContext.setCredentialsProvider(provider);
+        this.httpContext.setAuthCache(authCache);
+
+        final RequestConfig config = RequestConfig.custom()
+                .setConnectTimeout(timeout * 1000)
+                .setConnectionRequestTimeout(timeout * 1000)
+                .setSocketTimeout(timeout * 1000)
+                .build();
+
+        if (!validateCertificate) {
+            final SSLContext sslcontext = SSLUtils.getSSLContext();
+            sslcontext.init(null, new X509TrustManager[]{new 
TrustAllManager()}, new SecureRandom());
+            final SSLConnectionSocketFactory factory = new 
SSLConnectionSocketFactory(sslcontext, NoopHostnameVerifier.INSTANCE);
+            this.httpClient = HttpClientBuilder.create()
+                    .setDefaultCredentialsProvider(provider)
+                    .setDefaultCookieStore(httpCookieStore)
+                    .setDefaultRequestConfig(config)
+                    .setSSLSocketFactory(factory)
+                    .build();
+        } else {
+            this.httpClient = HttpClientBuilder.create()
+                    .setDefaultCredentialsProvider(provider)
+                    .setDefaultCookieStore(httpCookieStore)
+                    .setDefaultRequestConfig(config)
+                    .build();
+        }
+
+        try {
+            final HttpResponse response = post("/sessionMngr/", null);
+            if (response.getStatusLine().getStatusCode() != 
HttpStatus.SC_CREATED) {
+                throw new CloudRuntimeException("Failed to create and 
authenticate Veeam API client, please check the settings.");
+            }
+        } catch (final IOException e) {
+            throw new CloudRuntimeException("Failed to authenticate Veeam API 
service due to:" + e.getMessage());
+        }
+
+        setVeeamSshCredentials(this.apiURI.getHost(), username, password);
+    }
+
+    protected void setVeeamSshCredentials(String hostIp, String username, 
String password) {
+        this.veeamServerIp = hostIp;
+        this.veeamServerUsername = username;
+        this.veeamServerPassword = password;
+    }
+
+    private void checkAuthFailure(final HttpResponse response) {
+        if (response != null && response.getStatusLine().getStatusCode() == 
HttpStatus.SC_UNAUTHORIZED) {
+            final Credentials credentials = 
httpContext.getCredentialsProvider().getCredentials(AuthScope.ANY);
+            LOG.error("Veeam API authentication failed, please check Veeam 
configuration. Admin auth principal=" + credentials.getUserPrincipal() + ", 
password=" + credentials.getPassword() + ", API url=" + apiURI.toString());
+            throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, "Veeam B&R 
API call unauthorized, please ask your administrator to fix integration 
issues.");
+        }
+    }
+
+    private void checkResponseOK(final HttpResponse response) {
+        if (response.getStatusLine().getStatusCode() == 
HttpStatus.SC_NO_CONTENT) {
+            LOG.debug("Requested Veeam resource does not exist");
+            return;
+        }
+        if (!(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK ||
+                response.getStatusLine().getStatusCode() == 
HttpStatus.SC_ACCEPTED) &&
+                response.getStatusLine().getStatusCode() != 
HttpStatus.SC_NO_CONTENT) {
+            throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed 
to get valid response from Veeam B&R API call, please ask your administrator to 
diagnose and fix issues.");
+        }
+    }
+
+    private void checkResponseTimeOut(final Exception e) {
+        if (e instanceof ConnectTimeoutException || e instanceof 
SocketTimeoutException) {
+            throw new 
ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, "Veeam API 
operation timed out, please try again.");
+        }
+    }
+
+    private HttpResponse get(final String path) throws IOException {
+        final HttpGet request = new HttpGet(apiURI.toString() + path);
+        final HttpResponse response = httpClient.execute(request, httpContext);
+        checkAuthFailure(response);
+        return response;
+    }
+
+    private HttpResponse post(final String path, final Object obj) throws 
IOException {
+        String xml = null;
+        if (obj != null) {
+            XmlMapper xmlMapper = new XmlMapper();
+            xml = xmlMapper.writer()
+                    .with(ToXmlGenerator.Feature.WRITE_XML_DECLARATION)
+                    .writeValueAsString(obj);
+            // Remove invalid/empty xmlns
+            xml = xml.replace(" xmlns=\"\"", "");
+        }
+
+        final HttpPost request = new HttpPost(apiURI.toString() + path);
+        request.setHeader("Content-type", "application/xml");
+        if (StringUtils.isNotBlank(xml)) {
+            request.setEntity(new StringEntity(xml));
+        }
+
+        final HttpResponse response = httpClient.execute(request, httpContext);
+        checkAuthFailure(response);
+        return response;
+    }
+
+    private HttpResponse delete(final String path) throws IOException {
+        final HttpResponse response = httpClient.execute(new 
HttpDelete(apiURI.toString() + path), httpContext);
+        checkAuthFailure(response);
+        return response;
+    }
+
+    ///////////////////////////////////////////////////////////////////
+    //////////////// Private Veeam Helper Methods /////////////////////
+    ///////////////////////////////////////////////////////////////////
+
+    private String findDCHierarchy(final String vmwareDcName) {
+        LOG.debug("Trying to find hierarchy ID for vmware datacenter: " + 
vmwareDcName);
+
+        try {
+            final HttpResponse response = get("/hierarchyRoots");
+            checkResponseOK(response);
+            final ObjectMapper objectMapper = new XmlMapper();
+            final EntityReferences references = 
objectMapper.readValue(response.getEntity().getContent(), 
EntityReferences.class);
+            for (final Ref ref : references.getRefs()) {
+                if (ref.getName().equals(vmwareDcName) && 
ref.getType().equals("HierarchyRootReference")) {
+                    return ref.getUid();
+                }
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to list Veeam jobs due to:", e);
+            checkResponseTimeOut(e);
+        }
+        throw new CloudRuntimeException("Failed to find hierarchy reference 
for VMware datacenter " + vmwareDcName + " in Veeam, please ask administrator 
to check Veeam B&R manager configuration");
+    }
+
+    private String lookupVM(final String hierarchyId, final String vmName) {
+        LOG.debug("Trying to lookup VM from veeam hierarchy:" + hierarchyId + 
" for vm name:" + vmName);
+
+        try {
+            final HttpResponse response = 
get(String.format("/lookup?host=%s&type=Vm&name=%s", hierarchyId, vmName));
+            checkResponseOK(response);
+            final ObjectMapper objectMapper = new XmlMapper();
+            final HierarchyItems items = 
objectMapper.readValue(response.getEntity().getContent(), HierarchyItems.class);
+            if (items == null || items.getItems() == null || 
items.getItems().isEmpty()) {
+                throw new CloudRuntimeException("Could not find VM " + vmName 
+ " in Veeam, please ask administrator to check Veeam B&R manager");
+            }
+            for (final HierarchyItem item : items.getItems()) {
+                if (item.getObjectName().equals(vmName) && 
item.getObjectType().equals("Vm")) {
+                    return item.getObjectRef();
+                }
+            }
+        } catch (final IOException e) {
+            LOG.error("Failed to list Veeam jobs due to:", e);
+            checkResponseTimeOut(e);
+        }
+        throw new CloudRuntimeException("Failed to lookup VM " + vmName + " in 
Veeam, please ask administrator to check Veeam B&R manager configuration");
+    }
+
+    private Task parseTaskResponse(HttpResponse response) throws IOException {
+        checkResponseOK(response);
+        final ObjectMapper objectMapper = new XmlMapper();
+        return objectMapper.readValue(response.getEntity().getContent(), 
Task.class);
+    }
+
+    private RestoreSession parseRestoreSessionResponse(HttpResponse response) 
throws IOException {
+        checkResponseOK(response);
+        final ObjectMapper objectMapper = new XmlMapper();
+        return objectMapper.readValue(response.getEntity().getContent(), 
RestoreSession.class);
+    }
+
+    // FIXME: configure task timeout/limits
 
 Review comment:
   As the comment says, fixes sleep time and loop number, can these be 
configurable?

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to