Merge branch 'bckuprecframework' of github.com:shapeblue/cloudstack into bckuprecframework

This commit is contained in:
nvazquez 2018-06-01 14:02:20 -03:00
commit cd327b165f
10 changed files with 389 additions and 50 deletions

View File

@ -22,15 +22,15 @@ import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import com.cloud.agent.api.to.VolumeTO;
import com.cloud.vm.VirtualMachine;
import org.apache.cloudstack.backup.veeam.VeeamClient;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.log4j.Logger;
import com.cloud.agent.api.to.VolumeTO;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.VirtualMachine;
public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable {
private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class);
@ -73,7 +73,7 @@ public class VeeamBackupProvider extends AdapterBase implements BackupProvider,
public boolean addVMToBackupPolicy(Long zoneId, String policyId, VirtualMachine vm) {
String instanceName = vm.getInstanceName();
//TODO: Get vcenter ip
return getClient(zoneId).assignBackupPolicyToVM(policyId, instanceName, "");
return getClient(zoneId).addVMToVeeamJob(policyId, instanceName, "");
}
@Override

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.backup.veeam;
import static org.apache.cloudstack.backup.veeam.api.VeeamObjectType.HierarchyRootReference;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
@ -35,6 +37,10 @@ import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.backup.BackupPolicy;
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.ObjectInJob;
import org.apache.cloudstack.backup.veeam.api.ObjectsInJob;
import org.apache.cloudstack.backup.veeam.api.Ref;
import org.apache.cloudstack.backup.veeam.api.Task;
import org.apache.cloudstack.utils.security.SSLUtils;
@ -67,6 +73,7 @@ import org.apache.log4j.Logger;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.nio.TrustAllManager;
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;
@ -138,7 +145,9 @@ public class VeeamClient {
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) {
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.");
}
}
@ -184,9 +193,86 @@ public class VeeamClient {
return response;
}
//////////////////////////////////////////////////////////
//////////////// Public APIs: Backup /////////////////////
//////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
//////////////// 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 boolean checkTaskStatus(final HttpResponse response) throws IOException {
final Task task = parseTaskResponse(response);
for (int i = 0; i < 20; i++) {
final HttpResponse taskResponse = get("/tasks/" + task.getTaskId());
final Task polledTask = parseTaskResponse(taskResponse);
if (polledTask.getState().equals("Finished")) {
final HttpResponse taskDeleteResponse = delete("/tasks/" + task.getTaskId());
if (taskDeleteResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
LOG.warn("Operation failed for veeam task id=" + task.getTaskId());
}
if (polledTask.getResult().getSuccess().equals("true")) {
return true;
}
throw new CloudRuntimeException("Failed to assign VM to backup policy due to: " + polledTask.getResult().getMessage());
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
LOG.debug("Failed to sleep while polling for Veeam task status due to: ", e);
}
}
return false;
}
////////////////////////////////////////////////////////
//////////////// Public Veeam APIs /////////////////////
////////////////////////////////////////////////////////
public List<VeeamBackup> listAllBackups() {
LOG.debug("Trying to list Veeam backups");
@ -208,7 +294,7 @@ public class VeeamClient {
}
public List<BackupPolicy> listBackupPolicies() {
LOG.debug("Trying to list Veeam jobs that are backup policies");
LOG.debug("Trying to list backup policies that are Veeam jobs");
try {
final HttpResponse response = get("/jobs");
checkResponseOK(response);
@ -226,47 +312,57 @@ public class VeeamClient {
return new ArrayList<>();
}
private Task parseTaskResponse(HttpResponse response) throws IOException {
checkResponseOK(response);
final ObjectMapper objectMapper = new XmlMapper();
return objectMapper.readValue(response.getEntity().getContent(), Task.class);
}
// FIXME: pass an object that implement a common interface across backup plugins
public boolean assignBackupPolicyToVM(final String jobId, final String vmwareInstanceName, final String vcIpOrName) {
LOG.debug("Trying to assign VM to backup policy that is a veeam job");
public boolean startAdhocBackupJob(final String jobId) {
LOG.debug("Trying to start ad-hoc backup for Veeam job: " + jobId);
try {
//FIXME: add logic to find hierarical root based on vCenter details this is useful in env with multiple VCs
final String heirarchyId = "dec37163-39df-4c4b-9690-899cf5543bf6";
final CreateObjectInJobSpec vmToBackupJob = new CreateObjectInJobSpec();
vmToBackupJob.setObjName(vmwareInstanceName);
vmToBackupJob.setObjRef(String.format("urn:VMware:VM:%s.%s", heirarchyId, vmwareInstanceName));
final HttpResponse response = post(String.format("/jobs/%s/includes", jobId), vmToBackupJob);
final Task task = parseTaskResponse(response);
for (int i = 0; i < 5; i++) {
final HttpResponse taskResponse = get("/tasks/" + task.getTaskId());
final Task polledTask = parseTaskResponse(taskResponse);
if (polledTask.getState().equals("Finished")) {
final HttpResponse taskDeleteResponse = delete("/tasks/" + task.getTaskId());
if (taskDeleteResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
LOG.warn("Failed to cleanup VM assign task on veeam job for task id=" + task.getTaskId());
}
if (polledTask.getResult().getSuccess().equals("true")) {
return true;
}
throw new CloudRuntimeException("Failed to assign VM to backup policy due to: " + polledTask.getResult().getMessage());
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
LOG.debug("Failed to sleep while polling for task status due to: ", e);
}
}
final HttpResponse response = post(String.format("/jobs/%s?action=start", jobId), null);
return checkTaskStatus(response);
} catch (final IOException e) {
LOG.error("Failed to list Veeam jobs due to:", e);
checkResponseTimeOut(e);
}
throw new CloudRuntimeException("Failed to assign VM to backup policy likely due to timeout, please check veeam tasks");
return false;
}
public boolean addVMToVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) {
LOG.debug("Trying to add VM to backup policy that is a veeam job: " + jobId);
try {
final String heirarchyId = findDCHierarchy(vmwareDcName);
final String veeamVmRefId = lookupVM(heirarchyId, vmwareInstanceName);
final CreateObjectInJobSpec vmToBackupJob = new CreateObjectInJobSpec();
vmToBackupJob.setObjName(vmwareInstanceName);
vmToBackupJob.setObjRef(veeamVmRefId);
final HttpResponse response = post(String.format("/jobs/%s/includes", jobId), vmToBackupJob);
return checkTaskStatus(response);
} catch (final IOException e) {
LOG.error("Failed to add VM to Veeam job due to:", e);
checkResponseTimeOut(e);
}
throw new CloudRuntimeException("Failed to add VM to backup policy likely due to timeout, please check veeam tasks");
}
public boolean removeVMFromVeeamJob(final String jobId, final String vmwareInstanceName, final String vmwareDcName) {
LOG.debug("Trying to remove VM from backup policy that is a veeam job: " + jobId);
try {
final String heirarchyId = findDCHierarchy(vmwareDcName);
final String veeamVmRefId = lookupVM(heirarchyId, vmwareInstanceName);
final HttpResponse response = get(String.format("/jobs/%s/includes", jobId));
checkResponseOK(response);
final ObjectMapper objectMapper = new XmlMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final ObjectsInJob jobObjects = objectMapper.readValue(response.getEntity().getContent(), ObjectsInJob.class);
for (final ObjectInJob jobObject : jobObjects.getObjects()) {
if (jobObject.getName().equals(vmwareInstanceName) && jobObject.getHierarchyObjRef().equals(veeamVmRefId)) {
final HttpResponse deleteResponse = delete(String.format("/jobs/%s/includes/%s", jobId, jobObject.getObjectInJobId()));
return checkTaskStatus(deleteResponse);
}
}
return checkTaskStatus(response);
} catch (final IOException e) {
LOG.error("Failed to list Veeam jobs due to:", e);
checkResponseTimeOut(e);
}
throw new CloudRuntimeException("Failed to remove VM from backup policy, please check veeam tasks");
}
}

View File

@ -0,0 +1,68 @@
// 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.api;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "HierarchyItem")
public class HierarchyItem {
@JacksonXmlProperty(localName = "Type", isAttribute = true)
private String type;
@JacksonXmlProperty(localName = "ObjectRef")
private String objectRef;
@JacksonXmlProperty(localName = "ObjectType")
private String objectType;
@JacksonXmlProperty(localName = "ObjectName")
private String objectName;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getObjectRef() {
return objectRef;
}
public void setObjectRef(String objectRef) {
this.objectRef = objectRef;
}
public String getObjectType() {
return objectType;
}
public void setObjectType(String objectType) {
this.objectType = objectType;
}
public String getObjectName() {
return objectName;
}
public void setObjectName(String objectName) {
this.objectName = objectName;
}
}

View File

@ -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.backup.veeam.api;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "HierarchyItems")
public class HierarchyItems {
@JacksonXmlProperty(localName = "HierarchyItem")
@JacksonXmlElementWrapper(localName = "HierarchyItem", useWrapping = false)
private List<HierarchyItem> items;
public List<HierarchyItem> getItems() {
return items;
}
public void setItems(List<HierarchyItem> items) {
this.items = items;
}
}

View File

@ -0,0 +1,94 @@
// 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.api;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "ObjectInJob")
public class ObjectInJob {
@JacksonXmlProperty(localName = "Href", isAttribute = true)
private String href;
@JacksonXmlProperty(localName = "Type", isAttribute = true)
private String type;
@JacksonXmlProperty(localName = "Link")
@JacksonXmlElementWrapper(localName = "Links")
private List<Link> link;
@JacksonXmlProperty(localName = "ObjectInJobId", isAttribute = true)
private String objectInJobId;
@JacksonXmlProperty(localName = "HierarchyObjRef", isAttribute = true)
private String hierarchyObjRef;
@JacksonXmlProperty(localName = "Name", isAttribute = true)
private String name;
public String getHref() {
return href;
}
public void setHref(String href) {
this.href = href;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<Link> getLink() {
return link;
}
public void setLink(List<Link> link) {
this.link = link;
}
public String getObjectInJobId() {
return objectInJobId;
}
public void setObjectInJobId(String objectInJobId) {
this.objectInJobId = objectInJobId;
}
public String getHierarchyObjRef() {
return hierarchyObjRef;
}
public void setHierarchyObjRef(String hierarchyObjRef) {
this.hierarchyObjRef = hierarchyObjRef;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -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.backup.veeam.api;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JacksonXmlRootElement(localName = "ObjectsInJob")
public class ObjectsInJob {
@JacksonXmlProperty(localName = "ObjectInJob")
@JacksonXmlElementWrapper(localName = "ObjectInJob", useWrapping = false)
private List<ObjectInJob> objects;
public List<ObjectInJob> getObjects() {
return objects;
}
public void setObjects(List<ObjectInJob> objects) {
this.objects = objects;
}
}

View File

@ -50,8 +50,7 @@ public class Ref {
}
public String getUid() {
String[] fields = uid.split(":");
return fields[fields.length - 1];
return uid;
}
public void setUid(String uid) {

View File

@ -18,6 +18,9 @@
package org.apache.cloudstack.backup.veeam.api;
public enum VeeamObjectType {
HierarchyRoot,
HierarchyRootReference,
Job,
JobReference,

View File

@ -40,8 +40,9 @@ public class VeeamClientTest {
}
@Test
public void testAssignVMToPolicy() {
client.assignBackupPolicyToVM("8acac50d-3711-4c99-bf7b-76fe9c7e39c3", "i-2-9-VM", "10.2.2.52");
public void testBackupLifecycle() {
client.addVMToVeeamJob("8acac50d-3711-4c99-bf7b-76fe9c7e39c3", "i-2-9-VM", "10.2.2.52");
client.startAdhocBackupJob("8acac50d-3711-4c99-bf7b-76fe9c7e39c3");
client.removeVMFromVeeamJob("8acac50d-3711-4c99-bf7b-76fe9c7e39c3", "i-2-9-VM", "10.2.2.52");
}
}

View File

@ -92,7 +92,7 @@
<cs.mockito.version>1.10.19</cs.mockito.version>
<cs.powermock.version>1.6.4</cs.powermock.version>
<cs.aws.sdk.version>1.11.213</cs.aws.sdk.version>
<cs.jackson.version>2.9.2</cs.jackson.version>
<cs.jackson.version>2.9.5</cs.jackson.version>
<cs.lang.version>2.6</cs.lang.version>
<cs.commons-lang3.version>3.6</cs.commons-lang3.version>
<cs.commons-io.version>2.6</cs.commons-io.version>