From dc4bf22487e2dc90da399d6c4b0da5ad9d692625 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Tue, 15 May 2018 16:45:10 +0530 Subject: [PATCH] veeam: add wip API client auth and API listing works for backups and jobs, parsing of XML is next step Signed-off-by: Rohit Yadav --- .../backup/VeeamBackupProvider.java | 75 ++++--- .../cloudstack/backup/veeam/VeeamBackup.java | 53 +++++ .../cloudstack/backup/veeam/VeeamClient.java | 196 ++++++++++++++++++ .../cloudstack/backup/veeam/VeeamObject.java | 28 +++ .../backup/veeam/VeeamObjectType.java | 27 +++ .../backup/veeam/VeeamClientTest.java | 36 ++++ 6 files changed, 390 insertions(+), 25 deletions(-) create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java create mode 100644 plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObjectType.java create mode 100644 plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index e8fbf812de6..774d8bfa0f0 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -17,6 +17,12 @@ package org.apache.cloudstack.backup; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +import org.apache.cloudstack.backup.veeam.VeeamClient; import org.apache.cloudstack.framework.backup.BackupPolicy; import org.apache.cloudstack.framework.backup.BackupProvider; import org.apache.cloudstack.framework.backup.BackupService; @@ -25,50 +31,43 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.log4j.Logger; import com.cloud.utils.component.AdapterBase; - -import java.util.List; +import com.cloud.utils.exception.CloudRuntimeException; public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class); private ConfigKey VeeamUrl = new ConfigKey<>("Advanced", String.class, "backup.plugin.veeam.url", - "", + "http://localhost:9399/api/", "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone); private ConfigKey VeeamUsername = new ConfigKey<>("Advanced", String.class, "backup.plugin.veeam.username", - "", + "administrator", "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); private ConfigKey VeeamPassword = new ConfigKey<>("Advanced", String.class, "backup.plugin.veeam.password", - "", + "P@ssword123", "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); + private ConfigKey VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "true", + "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); - @Override - public String getConfigComponentName() { - return BackupService.class.getSimpleName(); - } - @Override - public ConfigKey[] getConfigKeys() { - return new ConfigKey[]{ - VeeamUrl, - VeeamUsername, - VeeamPassword - }; - } + private ConfigKey VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", + "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); - @Override - public String getName() { - return "veeam"; - } - - @Override - public String getDescription() { - return "Veeam B&R Plugin"; + private VeeamClient getClient(final Long zoneId) { + try { + return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId), + VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId)); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOG.error("Failed to build Veeam API client due to: ", e); + } + throw new CloudRuntimeException("Failed to build Veeam API client"); } @Override @@ -85,4 +84,30 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider, public boolean isBackupPolicy(String uuid) { return false; } + + @Override + public String getConfigComponentName() { + return BackupService.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[]{ + VeeamUrl, + VeeamUsername, + VeeamPassword, + VeeamValidateSSLSecurity, + VeeamApiRequestTimeout + }; + } + + @Override + public String getName() { + return "veeam"; + } + + @Override + public String getDescription() { + return "Veeam B&R Plugin"; + } } diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java new file mode 100644 index 00000000000..bec49b06b24 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamBackup.java @@ -0,0 +1,53 @@ +// 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 java.util.List; + +public class VeeamBackup implements VeeamObject { + + String uuid; + String name; + String href; + List links; + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getHref() { + return href; + } + + @Override + public VeeamObjectType getType() { + return VeeamObjectType.Backup; + } + + @Override + public List getLinks() { + return links; + } +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java new file mode 100644 index 00000000000..17775836963 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -0,0 +1,196 @@ +// 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 java.io.ByteArrayOutputStream; +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.List; + +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.utils.security.SSLUtils; +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.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.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.exception.CloudRuntimeException; +import com.cloud.utils.nio.TrustAllManager; + +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(); + + 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()); + } + } + + 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_NO_CONTENT) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to find the requested resource and 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 item) throws IOException { + final HttpPost request = new HttpPost(apiURI.toString() + path); + final HttpResponse response = httpClient.execute(request, httpContext); + checkAuthFailure(response); + return response; + } + + ////////////////////////////////////////////////////////// + //////////////// Public APIs: Backup ///////////////////// + ////////////////////////////////////////////////////////// + + public List listAllBackups() { + LOG.debug("Trying to list Veeam backups"); + try { + final HttpResponse response = get("/backups"); + checkResponseOK(response); + // FIXME: parse XML to object + ByteArrayOutputStream outstream = new ByteArrayOutputStream(); + response.getEntity().writeTo(outstream); + byte [] responseBody = outstream.toByteArray(); + + System.out.println(new String(responseBody)); + LOG.debug("Response received = " + response.getEntity().getContent()); + return null; + } catch (final IOException e) { + LOG.error("Failed to list Veeam backups due to:", e); + checkResponseTimeOut(e); + } + return new ArrayList<>(); + } + + public List listJobs() { + LOG.debug("Trying to list Veeam jobs"); + try { + final HttpResponse response = get("/jobs"); + checkResponseOK(response); + + // FIXME: parse XML to object + + return null; + } catch (final IOException e) { + LOG.error("Failed to list Veeam jobs due to:", e); + checkResponseTimeOut(e); + } + return new ArrayList<>(); + } + +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java new file mode 100644 index 00000000000..e0f83e23eaf --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObject.java @@ -0,0 +1,28 @@ +// 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 java.util.List; + +public interface VeeamObject { + String getUuid(); + String getName(); + String getHref(); + VeeamObjectType getType(); + List getLinks(); +} diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObjectType.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObjectType.java new file mode 100644 index 00000000000..c8b3c2f8013 --- /dev/null +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamObjectType.java @@ -0,0 +1,27 @@ +// 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; + +public enum VeeamObjectType { + Backup, + BackupReference, + BackupServerReference, + RestorePointReferenceList, + BackupFileReferenceList, + RepositoryReference, +} diff --git a/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java new file mode 100644 index 00000000000..d9583d71689 --- /dev/null +++ b/plugins/backup/veeam/src/test/java/org/apache/cloudstack/backup/veeam/VeeamClientTest.java @@ -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.backup.veeam; + +import org.junit.Before; +import org.junit.Test; + +public class VeeamClientTest { + + private VeeamClient client; + + @Before + public void setUp() throws Exception { + client = new VeeamClient("http://10.2.2.89:9399/api/", "administrator", "P@ssword123", true, 300); + } + + @Test + public void testBasicAuth() { + client.listAllBackups(); + } +} \ No newline at end of file