mirror of
https://github.com/apache/cloudstack.git
synced 2025-11-02 11:52:28 +01:00
CLOUDSTACK-9700: Allow user to Register/Copy templates to multiple zones at the same time
This commit is contained in:
parent
ed2f573160
commit
49a862b223
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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.");
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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.",
|
||||
|
||||
@ -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'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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: {
|
||||
|
||||
130
ui/scripts/ui-custom/copyTemplate.js
Normal file
130
ui/scripts/ui-custom/copyTemplate.js
Normal 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));
|
||||
@ -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);
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user