CLOUDSTACK-9700: Allow user to Register/Copy templates to multiple zones at the same time

This commit is contained in:
rashmidixit 2017-02-28 12:03:07 +05:30
parent ed2f573160
commit 49a862b223
26 changed files with 1060 additions and 229 deletions

View File

@ -652,6 +652,8 @@ public class ApiConstants {
public static final String OVM3_VIP = "ovm3vip";
public static final String CLEAN_UP_DETAILS = "cleanupdetails";
public static final String ZONE_ID_LIST = "zoneids";
public static final String DESTINATION_ZONE_ID_LIST = "destzoneids";
public static final String ADMIN = "admin";
public enum HostDetails {

View File

@ -307,7 +307,11 @@ public interface ResponseGenerator {
TemplateResponse createTemplateUpdateResponse(ResponseView view, VirtualMachineTemplate result);
List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result, Long zoneId, boolean readyOnly);
List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
Long zoneId, boolean readyOnly);
List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
List<Long> zoneIds, boolean readyOnly);
List<CapacityResponse> createCapacityResponse(List<? extends Capacity> result, DecimalFormat format);

View File

@ -40,11 +40,20 @@ public class CopyTemplateCmdByAdmin extends CopyTemplateCmd {
@Override
public void execute() throws ResourceAllocationException{
try {
if (destZoneId == null && (destZoneIds == null || destZoneIds.size() == 0))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Either destzoneid or destzoneids parameters have to be specified.");
if (destZoneId != null && destZoneIds != null && destZoneIds.size() != 0)
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Both destzoneid and destzoneids cannot be specified at the same time.");
CallContext.current().setEventDetails(getEventDescription());
VirtualMachineTemplate template = _templateService.copyTemplate(this);
if (template != null){
List<TemplateResponse> listResponse = _responseGenerator.createTemplateResponses(ResponseView.Full, template, getDestinationZoneId(), false);
List<TemplateResponse> listResponse = _responseGenerator.createTemplateResponses(ResponseView.Full, template,
getDestinationZoneIds(), false);
TemplateResponse response = new TemplateResponse();
if (listResponse != null && !listResponse.isEmpty()) {
response = listResponse.get(0);

View File

@ -40,10 +40,23 @@ public class RegisterTemplateCmdByAdmin extends RegisterTemplateCmd {
@Override
public void execute() throws ResourceAllocationException{
try {
if ((zoneId != null) && (zoneIds != null && !zoneIds.isEmpty()))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Both zoneid and zoneids cannot be specified at the same time");
if (zoneId == null && (zoneIds == null || zoneIds.isEmpty()))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Either zoneid or zoneids is required. Both cannot be null.");
if (zoneIds != null && zoneIds.size() > 1 && zoneIds.contains(-1L))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Parameter zoneids cannot combine all zones (-1) option with other zones");
VirtualMachineTemplate template = _templateService.registerTemplate(this);
if (template != null){
ListResponse<TemplateResponse> response = new ListResponse<TemplateResponse>();
List<TemplateResponse> templateResponses = _responseGenerator.createTemplateResponses(ResponseView.Full, template, zoneId, false);
List<TemplateResponse> templateResponses = _responseGenerator.createTemplateResponses(ResponseView.Full, template,
zoneIds, false);
response.setResponses(templateResponses);
response.setResponseName(getCommandName());
setResponseObject(response);

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.user.template;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
@ -51,25 +52,46 @@ public class CopyTemplateCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.DESTINATION_ZONE_ID,
type = CommandType.UUID,
entityType = ZoneResponse.class,
required = true,
required = false,
description = "ID of the zone the template is being copied to.")
private Long destZoneId;
protected Long destZoneId;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "Template ID.")
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = TemplateResponse.class, required = true, description = "Template ID.")
private Long id;
@Parameter(name = ApiConstants.SOURCE_ZONE_ID,
type = CommandType.UUID,
entityType = ZoneResponse.class,
description = "ID of the zone the template is currently hosted on. If not specified and template is cross-zone, then we will sync this template to region wide image store.")
description = "ID of the zone the template is currently hosted on. " +
"If not specified and template is cross-zone, " +
"then we will sync this template to region wide image store.")
private Long sourceZoneId;
@Parameter(name = ApiConstants.DESTINATION_ZONE_ID_LIST,
type=CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
required = false,
description = "A list of IDs of the zones that the template needs to be copied to." +
"Specify this list if the template needs to copied to multiple zones in one go. " +
"Do not specify destzoneid and destzoneids together, however one of them is required.")
protected List<Long> destZoneIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getDestinationZoneId() {
return destZoneId;
public List<Long> getDestinationZoneIds() {
if (destZoneIds != null && destZoneIds.size() != 0) {
return destZoneIds;
}
if (destZoneId != null) {
List < Long > destIds = new ArrayList<>();
destIds.add(destZoneId);
return destIds;
}
return null;
}
public Long getId() {
@ -111,7 +133,8 @@ public class CopyTemplateCmd extends BaseAsyncCmd {
@Override
public String getEventDescription() {
return "copying template: " + getId() + " from zone: " + getSourceZoneId() + " to zone: " + getDestinationZoneId();
return "copying template: " + getId() + " from zone: " + getSourceZoneId()
+ " to zone: " + getDestinationZoneIds();
}
@Override
@ -127,11 +150,20 @@ public class CopyTemplateCmd extends BaseAsyncCmd {
@Override
public void execute() throws ResourceAllocationException {
try {
if (destZoneId == null && (destZoneIds == null || destZoneIds.size() == 0))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Either destzoneid or destzoneids parameters have to be specified.");
if (destZoneId != null && destZoneIds != null && destZoneIds.size() != 0)
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Both destzoneid and destzoneids cannot be specified at the same time.");
CallContext.current().setEventDetails(getEventDescription());
VirtualMachineTemplate template = _templateService.copyTemplate(this);
if (template != null){
List<TemplateResponse> listResponse = _responseGenerator.createTemplateResponses(ResponseView.Restricted, template, getDestinationZoneId(), false);
List<TemplateResponse> listResponse = _responseGenerator.createTemplateResponses(ResponseView.Restricted,
template, getDestinationZoneIds(), false);
TemplateResponse response = new TemplateResponse();
if (listResponse != null && !listResponse.isEmpty()) {
response = listResponse.get(0);

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command.user.template;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -109,7 +110,7 @@ public class RegisterTemplateCmd extends BaseCmd {
private String url;
@Parameter(name=ApiConstants.ZONE_ID, type=CommandType.UUID, entityType = ZoneResponse.class,
required=true, description="the ID of the zone the template is to be hosted on")
required=false, description="the ID of the zone the template is to be hosted on")
protected Long zoneId;
@Parameter(name = ApiConstants.DOMAIN_ID,
@ -130,7 +131,8 @@ public class RegisterTemplateCmd extends BaseCmd {
@Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Register template for the project")
private Long projectId;
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Template details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].hypervisortoolsversion=xenserver61")
@Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP,
description = "Template details in key/value pairs using format details[i].keyname=keyvalue. Example: details[0].hypervisortoolsversion=xenserver61")
protected Map details;
@Parameter(name = ApiConstants.IS_DYNAMICALLY_SCALABLE,
@ -141,6 +143,19 @@ public class RegisterTemplateCmd extends BaseCmd {
@Parameter(name = ApiConstants.ROUTING, type = CommandType.BOOLEAN, description = "true if the template type is routing i.e., if template is used to deploy router")
protected Boolean isRoutingType;
@Parameter(name=ApiConstants.ZONE_ID_LIST,
type=CommandType.LIST,
collectionType = CommandType.UUID,
entityType = ZoneResponse.class,
required=false,
since="4.10.0.0",
description="A list of zone ids where the template will be hosted. Use this parameter if the template needs " +
"to be registered to multiple zones in one go. Use zoneid if the template " +
"needs to be registered to only one zone." +
"Passing only -1 to this will cause the template to be registered as a cross " +
"zone template and will be copied to all zones. ")
protected List<Long> zoneIds;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -197,8 +212,22 @@ public class RegisterTemplateCmd extends BaseCmd {
return url;
}
public Long getZoneId() {
return zoneId;
public List<Long> getZoneIds() {
// This function will return null when the zoneId
//is -1 which means all zones.
if (zoneIds != null && !(zoneIds.isEmpty())) {
if ((zoneIds.size() == 1) && (zoneIds.get(0) == -1L))
return null;
else
return zoneIds;
}
if (zoneId == null)
return null;
if (zoneId!= null && zoneId == -1)
return null;
List<Long> zones = new ArrayList<>();
zones.add(zoneId);
return zones;
}
public Long getDomainId() {
@ -261,10 +290,23 @@ public class RegisterTemplateCmd extends BaseCmd {
@Override
public void execute() throws ResourceAllocationException {
try {
if ((zoneId != null) && (zoneIds != null && !zoneIds.isEmpty()))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Both zoneid and zoneids cannot be specified at the same time");
if (zoneId == null && (zoneIds == null || zoneIds.isEmpty()))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Either zoneid or zoneids is required. Both cannot be null.");
if (zoneIds != null && zoneIds.size() > 1 && zoneIds.contains(-1L))
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Parameter zoneids cannot combine all zones (-1) option with other zones");
VirtualMachineTemplate template = _templateService.registerTemplate(this);
if (template != null) {
ListResponse<TemplateResponse> response = new ListResponse<TemplateResponse>();
List<TemplateResponse> templateResponses = _responseGenerator.createTemplateResponses(ResponseView.Restricted, template, zoneId, false);
List<TemplateResponse> templateResponses = _responseGenerator.createTemplateResponses(ResponseView.Restricted,
template, getZoneIds(), false);
response.setResponses(templateResponses);
response.setResponseName(getCommandName());
setResponseObject(response);

View File

@ -0,0 +1,77 @@
// 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.api.command.user.template;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.template.TemplateApiService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.template.CopyTemplateCmdByAdmin;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
/**
* Created by stack on 7/21/16.
*/
@RunWith(MockitoJUnitRunner.class)
public class CopyTemplateCmdByAdminTest{
@InjectMocks
private CopyTemplateCmdByAdmin copyTemplateCmdByAdmin;
@Mock
public TemplateApiService _templateService;
@Test
public void testZoneidAndZoneIdListEmpty() throws ResourceAllocationException {
try {
copyTemplateCmdByAdmin = new CopyTemplateCmdByAdmin();
copyTemplateCmdByAdmin.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("API should fail when no parameters are passed.");
}
}
}
@Test
public void testDestZoneidAndDestZoneIdListBothPresent() throws ResourceAllocationException {
try {
copyTemplateCmdByAdmin = new CopyTemplateCmdByAdmin();
copyTemplateCmdByAdmin.destZoneId = -1L;
copyTemplateCmdByAdmin.destZoneIds = new ArrayList<>();
copyTemplateCmdByAdmin.destZoneIds.add(-1L);
copyTemplateCmdByAdmin.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both destzoneid and destzoneids are passed");
}
}
}
}

View File

@ -0,0 +1,75 @@
// 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.api.command.user.template;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.template.TemplateApiService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
/**
* Created by stack on 7/21/16.
*/
@RunWith(MockitoJUnitRunner.class)
public class CopyTemplateCmdTest{
@InjectMocks
private CopyTemplateCmd copyTemplateCmd;
@Mock
public TemplateApiService _templateService;
@Test
public void testZoneidAndZoneIdListEmpty() throws ResourceAllocationException {
try {
copyTemplateCmd = new CopyTemplateCmd();
copyTemplateCmd.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("API should fail when no parameters are passed.");
}
}
}
@Test
public void testDestZoneidAndDestZoneIdListBothPresent() throws ResourceAllocationException {
try {
copyTemplateCmd = new CopyTemplateCmd();
copyTemplateCmd.destZoneId = -1L;
copyTemplateCmd.destZoneIds = new ArrayList<>();
copyTemplateCmd.destZoneIds.add(-1L);
copyTemplateCmd.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both destzoneid and destzoneids are passed");
}
}
}
}

View File

@ -0,0 +1,113 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.user.template;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.template.TemplateApiService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.admin.template.RegisterTemplateCmdByAdmin;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
@RunWith(MockitoJUnitRunner.class)
public class RegisterTemplateCmdByAdminTest{
@InjectMocks
private RegisterTemplateCmdByAdmin registerTemplateCmdByAdmin;
@Mock
public TemplateApiService _templateService;
@Test
public void testZoneidAndZoneIdListEmpty() throws ResourceAllocationException {
try {
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both zoneid and zoneids aren't passed");
}
}
}
@Test
public void testZoneidAndZoneIdListBothPresent() throws ResourceAllocationException {
try {
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.zoneId = -1L;
registerTemplateCmdByAdmin.zoneIds = new ArrayList<>();
registerTemplateCmdByAdmin.zoneIds.add(-1L);
registerTemplateCmdByAdmin.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both zoneid and zoneids are passed");
}
}
}
@Test
public void testZoneidMinusOne() throws ResourceAllocationException {
// If zoneId is passed as -1, then zone ids list should be null.
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.zoneId = -1L;
Assert.assertNull(registerTemplateCmdByAdmin.getZoneIds());
}
@Test
public void testZoneidListMinusOne() throws ResourceAllocationException {
// If zoneId List has only one parameter -1, then zone ids list should be null.
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.zoneIds = new ArrayList<>();
registerTemplateCmdByAdmin.zoneIds.add(-1L);
Assert.assertNull(registerTemplateCmdByAdmin.getZoneIds());
}
@Test
public void testZoneidListMoreThanMinusOne() throws ResourceAllocationException {
try {
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.zoneIds = new ArrayList<>();
registerTemplateCmdByAdmin.zoneIds.add(-1L);
registerTemplateCmdByAdmin.zoneIds.add(1L);
registerTemplateCmdByAdmin.execute();
} catch (ServerApiException e) {
if (e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Parameter zoneids cannot combine all zones (-1) option with other zones");
}
}
}
@Test
public void testZoneidPresentZoneidListAbsent() throws ResourceAllocationException {
registerTemplateCmdByAdmin = new RegisterTemplateCmdByAdmin();
registerTemplateCmdByAdmin.zoneIds = null;
registerTemplateCmdByAdmin.zoneId = 1L;
Assert.assertEquals((Long)1L,registerTemplateCmdByAdmin.getZoneIds().get(0));
}
}

View File

@ -0,0 +1,111 @@
// 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.api.command.user.template;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.template.TemplateApiService;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
@RunWith(MockitoJUnitRunner.class)
public class RegisterTemplateCmdTest{
@InjectMocks
private RegisterTemplateCmd registerTemplateCmd;
@Mock
public TemplateApiService _templateService;
@Test
public void testZoneidAndZoneIdListEmpty() throws ResourceAllocationException {
try {
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both zoneid and zoneids aren't passed");
}
}
}
@Test
public void testZoneidAndZoneIdListBothPresent() throws ResourceAllocationException {
try {
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.zoneId = -1L;
registerTemplateCmd.zoneIds = new ArrayList<>();
registerTemplateCmd.zoneIds.add(-1L);
registerTemplateCmd.execute();
} catch (ServerApiException e) {
if(e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Api should fail when both zoneid and zoneids are passed");
}
}
}
@Test
public void testZoneidMinusOne() throws ResourceAllocationException {
// If zoneId is passed as -1, then zone ids list should be null.
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.zoneId = -1L;
Assert.assertNull(registerTemplateCmd.getZoneIds());
}
@Test
public void testZoneidListMinusOne() throws ResourceAllocationException {
// If zoneId List has only one parameter -1, then zone ids list should be null.
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.zoneIds = new ArrayList<>();
registerTemplateCmd.zoneIds.add(-1L);
Assert.assertNull(registerTemplateCmd.getZoneIds());
}
@Test
public void testZoneidListMoreThanMinusOne() throws ResourceAllocationException {
try {
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.zoneIds = new ArrayList<>();
registerTemplateCmd.zoneIds.add(-1L);
registerTemplateCmd.zoneIds.add(1L);
registerTemplateCmd.execute();
} catch (ServerApiException e) {
if (e.getErrorCode() != ApiErrorCode.PARAM_ERROR) {
Assert.fail("Parameter zoneids cannot combine all zones (-1) option with other zones");
}
}
}
@Test
public void testZoneidPresentZoneidListAbsent() throws ResourceAllocationException {
registerTemplateCmd = new RegisterTemplateCmd();
registerTemplateCmd.zoneIds = null;
registerTemplateCmd.zoneId = 1L;
Assert.assertEquals((Long)1L,registerTemplateCmd.getZoneIds().get(0));
}
}

View File

@ -84,20 +84,22 @@ public class BareMetalTemplateAdapter extends TemplateAdapterBase implements Tem
@Override
public VMTemplateVO create(TemplateProfile profile) {
VMTemplateVO template = persistTemplate(profile, State.Active);
Long zoneId = profile.getZoneId();
List<Long> zones = profile.getZoneIdList();
// create an entry at template_store_ref with store_id = null to represent that this template is ready for use.
TemplateDataStoreVO vmTemplateHost =
new TemplateDataStoreVO(null, template.getId(), new Date(), 100, Status.DOWNLOADED, null, null, null, null, template.getUrl());
this._tmpltStoreDao.persist(vmTemplateHost);
if (zoneId == null || zoneId == -1) {
if (zones == null) {
List<DataCenterVO> dcs = _dcDao.listAllIncludingRemoved();
if (dcs != null && dcs.size() > 0) {
templateCreateUsage(template, dcs.get(0).getId());
}
} else {
templateCreateUsage(template, zoneId);
for (Long zoneId: zones) {
templateCreateUsage(template, zoneId);
}
}
_resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template);
@ -123,8 +125,12 @@ public class BareMetalTemplateAdapter extends TemplateAdapterBase implements Tem
boolean success = true;
String zoneName;
if (!template.isCrossZones() && profile.getZoneId() != null) {
zoneName = profile.getZoneId().toString();
if (profile.getZoneIdList() != null && profile.getZoneIdList().size() > 1)
throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time");
if (!template.isCrossZones() && profile.getZoneIdList() != null) {
//get the first element in the list
zoneName = profile.getZoneIdList().get(0).toString();
} else {
zoneName = "all zones";
}
@ -154,9 +160,16 @@ public class BareMetalTemplateAdapter extends TemplateAdapterBase implements Tem
}
}
if (profile.getZoneId() != null) {
UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), profile.getZoneId(), templateId, null);
if (profile.getZoneIdList() != null) {
UsageEventVO usageEvent = new UsageEventVO(eventType, account.getId(), profile.getZoneIdList().get(0),
templateId, null);
_usageEventDao.persist(usageEvent);
VMTemplateZoneVO templateZone = _tmpltZoneDao.findByZoneTemplate(profile.getZoneIdList().get(0), templateId);
if (templateZone != null) {
_tmpltZoneDao.remove(templateZone.getId());
}
} else {
List<DataCenterVO> dcs = _dcDao.listAllIncludingRemoved();
for (DataCenterVO dc : dcs) {
@ -165,12 +178,6 @@ public class BareMetalTemplateAdapter extends TemplateAdapterBase implements Tem
}
}
VMTemplateZoneVO templateZone = _tmpltZoneDao.findByZoneTemplate(profile.getZoneId(), templateId);
if (templateZone != null) {
_tmpltZoneDao.remove(templateZone.getId());
}
s_logger.debug("Successfully marked template host refs for template: " + template.getName() + " as destroyed in zone: " + zoneName);
// If there are no more non-destroyed template host entries for this template, delete it

View File

@ -1441,6 +1441,23 @@ public class ApiResponseHelper implements ResponseGenerator {
return ViewResponseHelper.createTemplateResponse(view, tvo.toArray(new TemplateJoinVO[tvo.size()]));
}
@Override
public List<TemplateResponse> createTemplateResponses(ResponseView view, VirtualMachineTemplate result,
List<Long> zoneIds, boolean readyOnly) {
List<TemplateJoinVO> tvo = null;
if (zoneIds == null) {
return createTemplateResponses(view, result, (Long)null, readyOnly);
} else {
for (Long zoneId: zoneIds){
if (tvo == null)
tvo = ApiDBUtils.newTemplateView(result, zoneId, readyOnly);
else
tvo.addAll(ApiDBUtils.newTemplateView(result, zoneId, readyOnly));
}
}
return ViewResponseHelper.createTemplateResponse(view, tvo.toArray(new TemplateJoinVO[tvo.size()]));
}
@Override
public List<TemplateResponse> createTemplateResponses(ResponseView view, long templateId, Long zoneId, boolean readyOnly) {
VirtualMachineTemplate template = findTemplateById(templateId);

View File

@ -16,6 +16,8 @@
// under the License.
package com.cloud.storage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
@ -36,7 +38,7 @@ public class TemplateProfile {
Boolean isExtractable;
ImageFormat format;
Long guestOsId;
Long zoneId;
List<Long> zoneIdList;
HypervisorType hypervisorType;
String accountName;
Long domainId;
@ -51,8 +53,8 @@ public class TemplateProfile {
TemplateType templateType;
public TemplateProfile(Long templateId, Long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHvm, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, ImageFormat format, Long guestOsId, Long zoneId, HypervisorType hypervisorType,
String accountName, Long domainId, Long accountId, String chksum, Boolean bootable, Map details, Boolean sshKeyEnabled) {
Boolean isPublic, Boolean featured, Boolean isExtractable, ImageFormat format, Long guestOsId, List<Long> zoneIdList, HypervisorType hypervisorType,
String accountName, Long domainId, Long accountId, String chksum, Boolean bootable, Map details, Boolean sshKeyEnabled) {
this.templateId = templateId;
this.userId = userId;
this.name = name;
@ -66,7 +68,7 @@ public class TemplateProfile {
this.isExtractable = isExtractable;
this.format = format;
this.guestOsId = guestOsId;
this.zoneId = zoneId;
this.zoneIdList = zoneIdList;
this.hypervisorType = hypervisorType;
this.accountName = accountName;
this.domainId = domainId;
@ -80,11 +82,15 @@ public class TemplateProfile {
public TemplateProfile(Long userId, VMTemplateVO template, Long zoneId) {
this.userId = userId;
this.template = template;
this.zoneId = zoneId;
if (zoneId != null) {
this.zoneIdList = new ArrayList<>();
this.zoneIdList.add(zoneId);
}
else this.zoneIdList = null;
}
public TemplateProfile(Long templateId, Long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHvm, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, ImageFormat format, Long guestOsId, Long zoneId,
Boolean isPublic, Boolean featured, Boolean isExtractable, ImageFormat format, Long guestOsId, List<Long> zoneId,
HypervisorType hypervisorType, String accountName, Long domainId, Long accountId, String chksum, Boolean bootable, String templateTag, Map details,
Boolean sshKeyEnabled, Long imageStoreId, Boolean isDynamicallyScalable, TemplateType templateType) {
@ -219,12 +225,8 @@ public class TemplateProfile {
this.guestOsId = id;
}
public Long getZoneId() {
return zoneId;
}
public void setZoneId(Long id) {
this.zoneId = id;
public List<Long> getZoneIdList() {
return zoneIdList;
}
public HypervisorType getHypervisorType() {

View File

@ -159,14 +159,31 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
throw new CloudRuntimeException("Unable to persist the template " + profile.getTemplate());
}
List<Long> zones = profile.getZoneIdList();
//zones is null when this template is to be registered to all zones
if (zones == null){
createTemplateWithinZone(null, profile, template);
}
else {
for (Long zId : zones) {
createTemplateWithinZone(zId, profile, template);
}
}
_resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template);
return template;
}
private void createTemplateWithinZone(Long zId, TemplateProfile profile, VMTemplateVO template) {
// find all eligible image stores for this zone scope
List<DataStore> imageStores = storeMgr.getImageStoresByScope(new ZoneScope(profile.getZoneId()));
List<DataStore> imageStores = storeMgr.getImageStoresByScope(new ZoneScope(zId));
if (imageStores == null || imageStores.size() == 0) {
throw new CloudRuntimeException("Unable to find image store to download template " + profile.getTemplate());
}
Set<Long> zoneSet = new HashSet<Long>();
Collections.shuffle(imageStores); // For private templates choose a random store. TODO - Have a better algorithm based on size, no. of objects, load etc.
Collections.shuffle(imageStores);
// For private templates choose a random store. TODO - Have a better algorithm based on size, no. of objects, load etc.
for (DataStore imageStore : imageStores) {
// skip data stores for a disabled zone
Long zoneId = imageStore.getScope().getScopeId();
@ -179,23 +196,22 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
// Check if zone is disabled
if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
s_logger.info("Zone " + zoneId + " is disabled, so skip downloading template to its image store " + imageStore.getId());
s_logger.info("Zone " + zoneId + " is disabled. Skip downloading template to its image store " + imageStore.getId());
continue;
}
// Check if image store has enough capacity for template
if (!_statsCollector.imageStoreHasEnoughCapacity(imageStore)) {
s_logger.info("Image store doesn't has enough capacity, so skip downloading template to this image store " + imageStore.getId());
s_logger.info("Image store doesn't have enough capacity. Skip downloading template to this image store " + imageStore.getId());
continue;
}
// We want to download private template to one of the image store in a zone
if(isPrivateTemplate(template) && zoneSet.contains(zoneId)){
if (isPrivateTemplate(template) && zoneSet.contains(zoneId)) {
continue;
}else {
} else {
zoneSet.add(zoneId);
}
}
TemplateInfo tmpl = imageFactory.getTemplate(template.getId(), imageStore);
CreateTemplateContext<TemplateApiResult> context = new CreateTemplateContext<TemplateApiResult>(null, tmpl);
AsyncCallbackDispatcher<HypervisorTemplateAdapter, TemplateApiResult> caller = AsyncCallbackDispatcher.create(this);
@ -203,9 +219,6 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
caller.setContext(context);
imageService.createTemplateAsync(tmpl, imageStore, caller);
}
_resourceLimitMgr.incrementResourceCount(profile.getAccountId(), ResourceType.template);
return template;
}
@Override
@ -222,8 +235,15 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
throw new CloudRuntimeException("Unable to persist the template " + profile.getTemplate());
}
if (profile.getZoneIdList() != null && profile.getZoneIdList().size() > 1)
throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time");
Long zoneId = null;
if (profile.getZoneIdList() != null)
zoneId = profile.getZoneIdList().get(0);
// find all eligible image stores for this zone scope
List<DataStore> imageStores = storeMgr.getImageStoresByScope(new ZoneScope(profile.getZoneId()));
List<DataStore> imageStores = storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
if (imageStores == null || imageStores.size() == 0) {
throw new CloudRuntimeException("Unable to find image store to download template " + profile.getTemplate());
}
@ -233,25 +253,27 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
Collections.shuffle(imageStores); // For private templates choose a random store. TODO - Have a better algorithm based on size, no. of objects, load etc.
for (DataStore imageStore : imageStores) {
// skip data stores for a disabled zone
Long zoneId = imageStore.getScope().getScopeId();
Long zoneId_is = imageStore.getScope().getScopeId();
if (zoneId != null) {
DataCenterVO zone = _dcDao.findById(zoneId);
DataCenterVO zone = _dcDao.findById(zoneId_is);
if (zone == null) {
s_logger.warn("Unable to find zone by id " + zoneId + ", so skip downloading template to its image store " + imageStore.getId());
s_logger.warn("Unable to find zone by id " + zoneId_is +
", so skip downloading template to its image store " + imageStore.getId());
continue;
}
// Check if zone is disabled
if (Grouping.AllocationState.Disabled == zone.getAllocationState()) {
s_logger.info("Zone " + zoneId + " is disabled, so skip downloading template to its image store " + imageStore.getId());
s_logger.info("Zone " + zoneId_is +
" is disabled, so skip downloading template to its image store " + imageStore.getId());
continue;
}
// We want to download private template to one of the image store in a zone
if (isPrivateTemplate(template) && zoneSet.contains(zoneId)) {
if (isPrivateTemplate(template) && zoneSet.contains(zoneId_is)) {
continue;
} else {
zoneSet.add(zoneId);
zoneSet.add(zoneId_is);
}
}
@ -363,8 +385,17 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
VMTemplateVO template = profile.getTemplate();
if (profile.getZoneIdList() != null && profile.getZoneIdList().size() > 1)
throw new CloudRuntimeException("Operation is not supported for more than one zone id at a time");
Long zoneId = null;
if (profile.getZoneIdList() != null)
zoneId = profile.getZoneIdList().get(0);
// find all eligible image stores for this template
List<DataStore> imageStores = templateMgr.getImageStoreByTemplate(template.getId(), profile.getZoneId());
List<DataStore> imageStores = templateMgr.getImageStoreByTemplate(template.getId(),
zoneId);
if (imageStores == null || imageStores.size() == 0) {
// already destroyed on image stores
s_logger.info("Unable to find image store still having template: " + template.getName() + ", so just mark the template removed");
@ -427,7 +458,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
}
}
if (success) {
if ((imageStores.size() > 1) && (profile.getZoneId() != null)) {
if ((imageStores.size() > 1) && (profile.getZoneIdList() != null)) {
//if template is stored in more than one image stores, and the zone id is not null, then don't delete other templates.
return success;
}
@ -467,13 +498,13 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
public TemplateProfile prepareDelete(DeleteTemplateCmd cmd) {
TemplateProfile profile = super.prepareDelete(cmd);
VMTemplateVO template = profile.getTemplate();
Long zoneId = profile.getZoneId();
List<Long> zoneIdList = profile.getZoneIdList();
if (template.getTemplateType() == TemplateType.SYSTEM) {
throw new InvalidParameterValueException("The DomR template cannot be deleted.");
}
if (zoneId != null && (storeMgr.getImageStore(zoneId) == null)) {
if (zoneIdList != null && (storeMgr.getImageStore(zoneIdList.get(0)) == null)) {
throw new InvalidParameterValueException("Failed to find a secondary storage in the specified zone.");
}
@ -483,9 +514,10 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
@Override
public TemplateProfile prepareDelete(DeleteIsoCmd cmd) {
TemplateProfile profile = super.prepareDelete(cmd);
Long zoneId = profile.getZoneId();
List<Long> zoneIdList = profile.getZoneIdList();
if (zoneId != null && (storeMgr.getImageStore(zoneId) == null)) {
if (zoneIdList != null &&
(storeMgr.getImageStore(zoneIdList.get(0)) == null)) {
throw new InvalidParameterValueException("Failed to find a secondary storage in the specified zone.");
}

View File

@ -70,11 +70,11 @@ public interface TemplateAdapter extends Adapter {
public boolean delete(TemplateProfile profile);
public TemplateProfile prepare(boolean isIso, Long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHVM, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, Long zoneId, HypervisorType hypervisorType, String accountName,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, List<Long> zoneId, HypervisorType hypervisorType, String accountName,
Long domainId, String chksum, Boolean bootable, Map details) throws ResourceAllocationException;
public TemplateProfile prepare(boolean isIso, long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHVM, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, Long zoneId, HypervisorType hypervisorType, String chksum,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, List<Long> zoneId, HypervisorType hypervisorType, String chksum,
Boolean bootable, String templateTag, Account templateOwner, Map details, Boolean sshKeyEnabled, String imageStoreUuid, Boolean isDynamicallyScalable,
TemplateType templateType) throws ResourceAllocationException;

View File

@ -16,6 +16,7 @@
// under the License.
package com.cloud.template;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -119,7 +120,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
@Override
public TemplateProfile prepare(boolean isIso, Long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHVM, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, Long zoneId, HypervisorType hypervisorType, String accountName,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, List<Long> zoneId, HypervisorType hypervisorType, String accountName,
Long domainId, String chksum, Boolean bootable, Map details) throws ResourceAllocationException {
return prepare(isIso, userId, name, displayText, bits, passwordEnabled, requiresHVM, url, isPublic, featured, isExtractable, format, guestOSId, zoneId,
hypervisorType, chksum, bootable, null, null, details, false, null, false, TemplateType.USER);
@ -127,7 +128,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
@Override
public TemplateProfile prepare(boolean isIso, long userId, String name, String displayText, Integer bits, Boolean passwordEnabled, Boolean requiresHVM, String url,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, Long zoneId, HypervisorType hypervisorType, String chksum,
Boolean isPublic, Boolean featured, Boolean isExtractable, String format, Long guestOSId, List<Long> zoneIdList, HypervisorType hypervisorType, String chksum,
Boolean bootable, String templateTag, Account templateOwner, Map details, Boolean sshkeyEnabled, String imageStoreUuid, Boolean isDynamicallyScalable,
TemplateType templateType) throws ResourceAllocationException {
//Long accountId = null;
@ -137,10 +138,6 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
isPublic = Boolean.FALSE;
}
if (zoneId.longValue() == -1) {
zoneId = null;
}
if (isIso) {
if (bootable == null) {
bootable = Boolean.TRUE;
@ -178,7 +175,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
isRegionStore = true;
}
if (!isAdmin && zoneId == null && !isRegionStore ) {
if (!isAdmin && zoneIdList == null && !isRegionStore ) {
// domain admin and user should also be able to register template on a region store
throw new InvalidParameterValueException("Please specify a valid zone Id. Only admins can create templates in all zones.");
}
@ -215,14 +212,16 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
_resourceLimitMgr.checkResourceLimit(templateOwner, ResourceType.template);
// If a zoneId is specified, make sure it is valid
if (zoneId != null) {
DataCenterVO zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new IllegalArgumentException("Please specify a valid zone.");
}
Account caller = CallContext.current().getCallingAccount();
if(Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())){
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: "+ zoneId );
if (zoneIdList != null) {
for (Long zoneId :zoneIdList) {
DataCenterVO zone = _dcDao.findById(zoneId);
if (zone == null) {
throw new IllegalArgumentException("Please specify a valid zone.");
}
Account caller = CallContext.current().getCallingAccount();
if (Grouping.AllocationState.Disabled == zone.getAllocationState() && !_accountMgr.isRootAdmin(caller.getId())) {
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
}
}
}
@ -248,7 +247,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
Long id = _tmpltDao.getNextInSequence(Long.class, "id");
CallContext.current().setEventDetails("Id: " + id + " name: " + name);
return new TemplateProfile(id, userId, name, displayText, bits, passwordEnabled, requiresHVM, url, isPublic, featured, isExtractable, imgfmt, guestOSId, zoneId,
return new TemplateProfile(id, userId, name, displayText, bits, passwordEnabled, requiresHVM, url, isPublic, featured, isExtractable, imgfmt, guestOSId, zoneIdList,
hypervisorType, templateOwner.getAccountName(), templateOwner.getDomainId(), templateOwner.getAccountId(), chksum, bootable, templateTag, details,
sshkeyEnabled, null, isDynamicallyScalable, templateType);
@ -263,11 +262,11 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
boolean isRouting = (cmd.isRoutingType() == null) ? false : cmd.isRoutingType();
Long zoneId = cmd.getZoneId();
List<Long> zoneId = cmd.getZoneIds();
// ignore passed zoneId if we are using region wide image store
List<ImageStoreVO> stores = _imgStoreDao.findRegionImageStores();
if (stores != null && stores.size() > 0) {
zoneId = -1L;
zoneId = null;
}
HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor());
@ -291,11 +290,13 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
boolean isRouting = (cmd.isRoutingType() == null) ? false : cmd.isRoutingType();
List<Long> zoneList = null;
Long zoneId = cmd.getZoneId();
// ignore passed zoneId if we are using region wide image store
List<ImageStoreVO> stores = _imgStoreDao.findRegionImageStores();
if (stores != null && stores.size() > 0) {
zoneId = -1L;
if (!(stores != null && stores.size() > 0)) {
zoneList = new ArrayList<>();
zoneList.add(zoneId);
}
HypervisorType hypervisorType = HypervisorType.getType(cmd.getHypervisor());
@ -305,7 +306,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
}
return prepare(false, CallContext.current().getCallingUserId(), cmd.getName(), cmd.getDisplayText(), cmd.getBits(), cmd.isPasswordEnabled(),
cmd.getRequiresHvm(), null, cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneId,
cmd.getRequiresHvm(), null, cmd.isPublic(), cmd.isFeatured(), cmd.isExtractable(), cmd.getFormat(), cmd.getOsTypeId(), zoneList,
hypervisorType, cmd.getChecksum(), true, cmd.getTemplateTag(), owner, cmd.getDetails(), cmd.isSshKeyEnabled(), null,
cmd.isDynamicallyScalable(), isRouting ? TemplateType.ROUTING : TemplateType.USER);
@ -318,20 +319,22 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId());
_accountMgr.checkAccess(caller, null, true, owner);
List<Long> zoneList = null;
Long zoneId = cmd.getZoneId();
// ignore passed zoneId if we are using region wide image store
List<ImageStoreVO> stores = _imgStoreDao.findRegionImageStores();
if (stores != null && stores.size() > 0) {
zoneId = -1L;
if (!(stores != null && stores.size() > 0)) {
zoneList = new ArrayList<>();
zoneList.add(zoneId);
}
return prepare(true, CallContext.current().getCallingUserId(), cmd.getIsoName(), cmd.getDisplayText(), 64, false, true, cmd.getUrl(), cmd.isPublic(),
cmd.isFeatured(), cmd.isExtractable(), ImageFormat.ISO.toString(), cmd.getOsTypeId(), zoneId, HypervisorType.None, cmd.getChecksum(), cmd.isBootable(), null,
cmd.isFeatured(), cmd.isExtractable(), ImageFormat.ISO.toString(), cmd.getOsTypeId(), zoneList, HypervisorType.None, cmd.getChecksum(), cmd.isBootable(), null,
owner, null, false, cmd.getImageStoreUuid(), cmd.isDynamicallyScalable(), TemplateType.USER);
}
protected VMTemplateVO persistTemplate(TemplateProfile profile, VirtualMachineTemplate.State initialState) {
Long zoneId = profile.getZoneId();
List<Long> zoneIdList = profile.getZoneIdList();
VMTemplateVO template =
new VMTemplateVO(profile.getTemplateId(), profile.getName(), profile.getFormat(), profile.getIsPublic(), profile.getFeatured(), profile.getIsExtractable(),
profile.getTemplateType(), profile.getUrl(), profile.getRequiresHVM(), profile.getBits(), profile.getAccountId(), profile.getCheckSum(),
@ -339,7 +342,7 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
profile.getTemplateTag(), profile.getDetails(), profile.getSshKeyEnabled(), profile.IsDynamicallyScalable());
template.setState(initialState);
if (zoneId == null || zoneId.longValue() == -1) {
if (zoneIdList == null) {
List<DataCenterVO> dcs = _dcDao.listAll();
if (dcs.isEmpty()) {
@ -352,7 +355,9 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
}
} else {
_tmpltDao.addTemplateToZone(template, zoneId);
for (Long zoneId: zoneIdList) {
_tmpltDao.addTemplateToZone(template, zoneId);
}
}
return _tmpltDao.findById(template.getId());
}

View File

@ -33,7 +33,11 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.storage.ImageStoreUploadMonitorImpl;
import com.cloud.utils.StringUtils;
import com.cloud.utils.EncryptionUtil;
import com.cloud.utils.DateUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.EnumUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@ -177,9 +181,6 @@ import com.cloud.user.AccountVO;
import com.cloud.user.ResourceLimitService;
import com.cloud.user.dao.AccountDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.DateUtil;
import com.cloud.utils.EnumUtils;
import com.cloud.utils.Pair;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory;
@ -780,7 +781,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
Long templateId = cmd.getId();
Long userId = CallContext.current().getCallingUserId();
Long sourceZoneId = cmd.getSourceZoneId();
Long destZoneId = cmd.getDestinationZoneId();
List<Long> destZoneIds = cmd.getDestinationZoneIds();
Account caller = CallContext.current().getCallingAccount();
// Verify parameters
@ -789,28 +790,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
throw new InvalidParameterValueException("Unable to find template with id");
}
DataStore srcSecStore = null;
if (sourceZoneId != null) {
// template is on zone-wide secondary storage
srcSecStore = getImageStore(sourceZoneId, templateId);
} else {
// template is on region store
srcSecStore = getImageStore(templateId);
}
if (srcSecStore == null) {
throw new InvalidParameterValueException("There is no template " + templateId + " ready on image store.");
}
if (template.isCrossZones()) {
// sync template from cache store to region store if it is not there, for cases where we are going to migrate existing NFS to S3.
_tmpltSvr.syncTemplateToRegionStore(templateId, srcSecStore);
s_logger.debug("Template " + templateId + " is cross-zone, don't need to copy");
return template;
}
if (sourceZoneId != null) {
if (sourceZoneId.equals(destZoneId)) {
if (destZoneIds!= null && destZoneIds.contains(sourceZoneId)) {
throw new InvalidParameterValueException("Please specify different source and destination zones.");
}
@ -820,26 +801,77 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
}
}
DataCenterVO dstZone = _dcDao.findById(destZoneId);
if (dstZone == null) {
throw new InvalidParameterValueException("Please specify a valid destination zone.");
}
Map<Long, DataCenterVO> dataCenterVOs = new HashMap();
DataStore dstSecStore = getImageStore(destZoneId, templateId);
if (dstSecStore != null) {
s_logger.debug("There is template " + templateId + " in secondary storage " + dstSecStore.getName() + " in zone " + destZoneId + " , don't need to copy");
return template;
for (Long destZoneId: destZoneIds) {
DataCenterVO dstZone = _dcDao.findById(destZoneId);
if (dstZone == null) {
throw new InvalidParameterValueException("Please specify a valid destination zone.");
}
dataCenterVOs.put(destZoneId, dstZone);
}
_accountMgr.checkAccess(caller, AccessType.OperateEntry, true, template);
boolean success = copy(userId, template, srcSecStore, dstZone);
List<String> failedZones = new ArrayList<>();
if (success) {
// increase resource count
long accountId = template.getAccountId();
if (template.getSize() != null) {
_resourceLimitMgr.incrementResourceCount(accountId, ResourceType.secondary_storage, template.getSize());
boolean success = false;
if (template.getHypervisorType() == HypervisorType.BareMetal) {
if (template.isCrossZones()) {
s_logger.debug("Template " + templateId + " is cross-zone, don't need to copy");
return template;
}
for (Long destZoneId: destZoneIds) {
if (!addTemplateToZone(template, destZoneId, sourceZoneId)) {
failedZones.add(dataCenterVOs.get(destZoneId).getName());
}
}
} else {
DataStore srcSecStore = null;
if (sourceZoneId != null) {
// template is on zone-wide secondary storage
srcSecStore = getImageStore(sourceZoneId, templateId);
} else {
// template is on region store
srcSecStore = getImageStore(templateId);
}
if (srcSecStore == null) {
throw new InvalidParameterValueException("There is no template " + templateId + " ready on image store.");
}
if (template.isCrossZones()) {
// sync template from cache store to region store if it is not there, for cases where we are going to migrate existing NFS to S3.
_tmpltSvr.syncTemplateToRegionStore(templateId, srcSecStore);
s_logger.debug("Template " + templateId + " is cross-zone, don't need to copy");
return template;
}
for (Long destZoneId : destZoneIds) {
DataStore dstSecStore = getImageStore(destZoneId, templateId);
if (dstSecStore != null) {
s_logger.debug("There is template " + templateId + " in secondary storage " + dstSecStore.getName() +
" in zone " + destZoneId + " , don't need to copy");
continue;
}
if (!copy(userId, template, srcSecStore, dataCenterVOs.get(destZoneId))) {
failedZones.add(dataCenterVOs.get(destZoneId).getName());
}
else{
if (template.getSize() != null) {
// increase resource count
long accountId = template.getAccountId();
_resourceLimitMgr.incrementResourceCount(accountId, ResourceType.secondary_storage, template.getSize());
}
}
}
}
if ((destZoneIds != null) && (destZoneIds.size() > failedZones.size())){
if (!failedZones.isEmpty()) {
s_logger.debug("There were failures when copying template to zones: " +
StringUtils.listToCsvTags(failedZones));
}
return template;
} else {
@ -847,6 +879,25 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
}
}
private boolean addTemplateToZone(VMTemplateVO template, long dstZoneId, long sourceZoneid) throws ResourceAllocationException{
long tmpltId = template.getId();
DataCenterVO dstZone = _dcDao.findById(dstZoneId);
DataCenterVO sourceZone = _dcDao.findById(sourceZoneid);
AccountVO account = _accountDao.findById(template.getAccountId());
_resourceLimitMgr.checkResourceLimit(account, ResourceType.template);
try {
_tmpltDao.addTemplateToZone(template, dstZoneId);
return true;
} catch (Exception ex) {
s_logger.debug("failed to copy template from Zone: " + sourceZone.getUuid() + " to Zone: " + dstZone.getUuid());
}
return false;
}
@Override
public boolean delete(long userId, long templateId, Long zoneId) {
VMTemplateVO template = _tmpltDao.findById(templateId);

View File

@ -241,7 +241,7 @@ public class HypervisorTemplateAdapterTest {
TemplateProfile profile = mock(TemplateProfile.class);
when(profile.getTemplate()).thenReturn(template);
when(profile.getZoneId()).thenReturn(1l);
when(profile.getZoneIdList()).thenReturn(null);
TemplateApiResult result = mock(TemplateApiResult.class);
when(result.isSuccess()).thenReturn(true);

View File

@ -13270,4 +13270,32 @@ ul.ui-autocomplete.ui-menu {
.list-view-select table th.availableHostSuitability,
.list-view-select table td.availableHostSuitability {
max-width: 250px;
}
}
.copy-template-destination-list div.text-search {
right: 5px;
}
.copy-template-destination-list div.ui-widget-content {
display: block !important;
}
div.panel.copy-template-destination-list div.list-view div.fixed-header{
width: 500px;
}
.copy-template-destination-list.panel div.list-view div.data-table table{
width: 595px;
}
.copy-template-destination-list .list-view .toolbar {
width: 654px;
}
.multi-edit-add-list .ui-button.copytemplateok{
left: 330px;
}
.multi-edit-add-list .ui-button.copytemplatecancel {
left: 310px;
}

View File

@ -1853,6 +1853,7 @@
<script type="text/javascript" src="scripts/affinity.js"></script>
<script type="text/javascript" src="scripts/ui-custom/affinity.js"></script>
<script type="text/javascript" src="scripts/ui-custom/migrate.js"></script>
<script type="text/javascript" src="scripts/ui-custom/copyTemplate.js"></script>
<script type="text/javascript" src="scripts/instances.js"></script>
<script type="text/javascript" src="scripts/events.js"></script>
<script type="text/javascript" src="scripts/regions.js"></script>

View File

@ -2207,6 +2207,7 @@ var dictionary = {"ICMP.code":"ICMP Code",
"message.suspend.project":"Are you sure you want to suspend this project?",
"message.systems.vms.ready":"System VMs ready.",
"message.template.copying":"Template is being copied.",
"message.template.copy.select.zone":"Please select a zone to copy template.",
"message.template.desc":"OS image that can be used to boot VMs",
"message.tier.required":"Tier is required",
"message.tooltip.dns.1":"Name of a DNS server for use by VMs in the zone. The public IP addresses for the zone must have a route to this server.",

View File

@ -1197,7 +1197,7 @@ cloudStack.docs = {
externalLink: ''
},
helpRegisterTemplateZone: {
desc: 'Choose the zone where you want the template to be available, or All Zones to make it available throughout the cloud',
desc: 'Choose one or more zones where you want the template to be available, or All Zones to make it available throughout the cloud. (Tip: Use Ctrl to choose multiple zones)',
externalLink: ''
},
helpRegisterTemplateHypervisor: {
@ -1333,4 +1333,4 @@ cloudStack.docs = {
helpLdapLinkDomainAdmin: {
desc: 'domain admin of the linked domain. Specify a username in GROUP/OU of LDAP'
}
};
};

View File

@ -2354,3 +2354,13 @@ $.validator.addMethod("ipv46cidr", function(value, element) {
return false;
}, "The specified IPv4/IPv6 CIDR is invalid.");
$.validator.addMethod("allzonesonly", function(value, element){
if ((value.indexOf("-1") != -1) &&(value.length > 1))
return false;
return true;
},
"All Zones cannot be combined with any other zone");

View File

@ -137,6 +137,10 @@
zone: {
label: 'label.zone',
docID: 'helpRegisterTemplateZone',
isMultiple: true,
validation: {
allzonesonly: true
},
select: function(args) {
if(g_regionsecondaryenabled == true) {
args.response.success({
@ -182,35 +186,50 @@
select: function(args) {
if (args.zone == null)
return;
// We want only distinct Hypervisor entries to be visible to the user
var items = [];
var distinctHVNames = [];
var length = 1;
// When only one zone is selected, args.zone is NOT an array.
if (Object.prototype.toString.call( args.zone ) === '[object Array]')
length = args.zone.length;
for (var index = 0; index < length; index++)
{
var zoneId;
if (length == 1)
zoneId = args.zone;
else
zoneId = args.zone[index];
var apiCmd;
if (args.zone == -1) { //All Zones
//apiCmd = "listHypervisors&zoneid=-1"; //"listHypervisors&zoneid=-1" has been changed to return only hypervisors available in all zones (bug 8809)
apiCmd = "listHypervisors";
}
else {
apiCmd = "listHypervisors&zoneid=" + args.zone;
}
$.ajax({
url: createURL(apiCmd),
dataType: "json",
async: false,
success: function(json) {
var hypervisorObjs = json.listhypervisorsresponse.hypervisor;
var items = [];
$(hypervisorObjs).each(function() {
items.push({
id: this.name,
description: this.name
});
});
args.response.success({
data: items
});
var apiCmd;
if (zoneId == -1) { //All Zones
apiCmd = "listHypervisors";
}
else {
apiCmd = "listHypervisors&zoneid=" + zoneId;
}
});
$.ajax({
url: createURL(apiCmd),
dataType: "json",
async: false,
success: function(json) {
var hypervisorObjs = json.listhypervisorsresponse.hypervisor;
$(hypervisorObjs).each(function() {
// Only if this hypervisor isn't already part of this
// list, then add to the drop down
if (distinctHVNames.indexOf(this.name) < 0 ){
distinctHVNames.push(this.name);
items.push({
id: this.name,
description: this.name
});
}
});
}
});
}
args.$select.change(function() {
var $form = $(this).closest('form');
if ($(this).val() == "VMware") {
@ -244,7 +263,15 @@
}
});
items.push({
id: "Any",
description: "Any"
});
args.response.success({
data: items
});
args.$select.trigger('change');
}
},
@ -545,11 +572,17 @@
},
action: function(args) {
var zones = "";
if (Object.prototype.toString.call( args.data.zone ) === '[object Array]'){
zones = args.data.zone.join(",");
}
else
zones = args.data.zone;
var data = {
name: args.data.name,
displayText: args.data.description,
url: args.data.url,
zoneid: args.data.zone,
zoneids: zones,
format: args.data.format,
isextractable: (args.data.isExtractable == "on"),
passwordEnabled: (args.data.isPasswordEnabled == "on"),
@ -1532,9 +1565,6 @@
copyTemplate: {
label: 'label.action.copy.template',
messages: {
confirm: function(args) {
return 'message.copy.template.confirm';
},
success: function(args) {
return 'message.template.copying';
},
@ -1542,76 +1572,102 @@
return 'label.action.copy.template';
}
},
createForm: {
title: 'label.action.copy.template',
desc: '',
fields: {
destinationZoneId: {
label: 'label.destination.zone',
docID: 'helpCopyTemplateDestination',
validation: {
required: true
},
select: function(args) {
$.ajax({
url: createURL("listZones&available=true"),
dataType: "json",
async: true,
success: function(json) {
var zoneObjs = [];
var items = json.listzonesresponse.zone;
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (args.context.zones[0].zoneid != items[i].id) {
zoneObjs.push({
id: items[i].id,
description: items[i].name
action: {
custom: cloudStack.uiCustom.copyTemplate({
listView: {
listView: {
id: 'destinationZones',
fields: {
destinationZoneName: {
label: 'label.name'
}
},
dataProvider: function(args) {
var data = {
page: args.page,
pagesize: pageSize
};
if (args.filterBy.search.value) {
data.keyword = args.filterBy.search.value;
}
$.ajax({
url: createURL("listZones&available=true"),
dataType: "json",
data: data,
async: true,
success: function(json) {
var zoneObjs = [];
var items = json.listzonesresponse.zone;
if (items != null) {
for (var i = 0; i < items.length; i++) {
if (args.context.zones[0].zoneid != items[i].id) {
zoneObjs.push({
id: items[i].id,
destinationZoneName: items[i].name
});
}
}
args.response.success({
data: zoneObjs
});
}
}else if(args.page == 1) {
args.response.success({
data: []
});
} else {
args.response.success({
data: []
});
}
}
});
}
}
},
action: function(args) {
var zoneids = "";
if (args.context.selectedZone != null &&
args.context.selectedZone.length > 0) {
for (var i = 0; i < args.context.selectedZone.length; i++){
if (i != 0 )
zoneids += ",";
zoneids += args.context.selectedZone[i].id;
}
}
if (zoneids == "")
return;
var data = {
id: args.context.templates[0].id,
destzoneids: zoneids,
sourcezoneid: args.context.zones[0].zoneid
};
$.ajax({
url: createURL('copyTemplate'),
data: data,
success: function(json) {
var jid = json.copytemplateresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
return {}; //nothing in this template needs to be updated
},
getActionFilter: function() {
return templateActionfilter;
}
}
args.response.success({
data: zoneObjs
});
}
});
}
}
}
},
action: function(args) {
var data = {
id: args.context.templates[0].id,
destzoneid: args.data.destinationZoneId
};
$.extend(data, {
sourcezoneid: args.context.zones[0].zoneid
});
$.ajax({
url: createURL('copyTemplate'),
data: data,
success: function(json) {
var jid = json.copytemplateresponse.jobid;
args.response.success({
_custom: {
jobId: jid,
getUpdatedItem: function(json) {
return {}; //nothing in this template needs to be updated
},
getActionFilter: function() {
return templateActionfilter;
}
});
}
});
}
});
})
},
notification: {
poll: pollAsyncJobResult
}
}
},
}
},
tabs: {
details: {

View File

@ -0,0 +1,130 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
(function(cloudStack, $) {
cloudStack.uiCustom.copyTemplate = function(args) {
var listView = args.listView;
var action = args.action;
return function(args) {
var context = args.context;
var destZoneList = function(args) {
var $listView;
var destZones = $.extend(true, {}, args.listView, {
context: context,
uiCustom: true
});
destZones.listView.actions = {
select: {
label: _l('label.select.zone'),
type: 'checkbox',
action: {
uiCustom: function(args) {
var $item = args.$item;
var $input = $item.find('td.actions input:visible');
if ($input.attr('type') == 'checkbox') {
if ($input.is(':checked'))
$item.addClass('multi-edit-selected');
else
$item.removeClass('multi-edit-selected');
} else {
$item.siblings().removeClass('multi-edit-selected');
$item.addClass('multi-edit-selected');
}
}
}
}
};
$listView = $('<div>').listView(destZones);
// Change action label
$listView.find('th.actions').html(_l('label.select'));
return $listView;
};
var $dataList = destZoneList({
listView: listView
}).dialog({
dialogClass: 'multi-edit-add-list panel copy-template-destination-list',
width: 625,
draggable: false,
title: _l('label.action.copy.template'),
buttons: [{
text: _l('label.ok'),
'class': 'ok copytemplateok',
click: function() {
var complete = args.complete;
var selectedZoneObj = [];
$dataList.find('tr.multi-edit-selected').map(function(index, elem) {
var itemData = $(elem).data('json-obj');
selectedZoneObj.push(itemData) ;
});
if(selectedZoneObj != undefined) {
$dataList.fadeOut(function() {
action({
context: $.extend(true, {}, context, {
selectedZone: selectedZoneObj
}),
response: {
success: function(args) {
complete({
_custom: args._custom,
$item: $('<div>'),
});
},
error: function(args) {
cloudStack.dialog.notice({
message: args
});
}
}
});
});
$('div.overlay').fadeOut(function() {
$('div.overlay').remove();
});
}
else {
cloudStack.dialog.notice({
message: _l('message.template.copy.select.zone')
});
}
}
}, {
text: _l('label.cancel'),
'class': 'cancel copytemplatecancel',
click: function() {
$dataList.fadeOut(function() {
$dataList.remove();
});
$('div.overlay').fadeOut(function() {
$('div.overlay').remove();
$(':ui-dialog').dialog('destroy');
});
}
}]
}).parent('.ui-dialog').overlay();
};
};
}(cloudStack, jQuery));

View File

@ -276,6 +276,11 @@
// Determine field type of input
if (field.select) {
var multiple = false;
if (field.isMultiple != null){
if (typeof(field.isMultiple) == 'boolean' && field.isMultiple == true)
multiple = true;
}
isAsync = true;
selectArgs = {
context: args.context,
@ -321,10 +326,18 @@
};
selectFn = field.select;
$input = $('<select>')
.attr({
var attrib = {};
if (multiple)
attrib = {
name: key,
multiple: 'multiple'
};
else
attrib = {
name: key
})
}
$input = $('<select>')
.attr(attrib)
.data('dialog-select-fn', function(args) {
selectFn(args ? $.extend(true, {}, selectArgs, args) : selectArgs);
})