infra: edge zones (#6840)

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
Co-authored-by: dahn <daan@onecht.net>
This commit is contained in:
Abhishek Kumar 2023-01-31 14:06:45 +05:30 committed by GitHub
parent 26eaae7872
commit 3b6ce97097
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1231 additions and 381 deletions

View File

@ -32,6 +32,10 @@ public interface DataCenter extends InfrastructureEntity, Grouping, Partition {
Basic, Advanced,
}
public enum Type {
Core, Edge,
}
String getDns1();
String getDns2();
@ -83,4 +87,6 @@ public interface DataCenter extends InfrastructureEntity, Grouping, Partition {
boolean isLocalStorageEnabled();
int getSortKey();
Type getType();
}

View File

@ -243,6 +243,7 @@ public class ApiConstants {
public static final String IP_TOTAL = "iptotal";
public static final String IS_CLEANUP_REQUIRED = "iscleanuprequired";
public static final String IS_DYNAMIC = "isdynamic";
public static final String IS_EDGE = "isedge";
public static final String IS_EXTRACTABLE = "isextractable";
public static final String IS_FEATURED = "isfeatured";
public static final String IS_PORTABLE = "isportable";

View File

@ -50,16 +50,16 @@ public class CreatePodCmd extends BaseCmd {
description = "the Zone ID in which the Pod will be created")
private Long zoneId;
@Parameter(name = ApiConstants.START_IP, type = CommandType.STRING, required = true, description = "the starting IP address for the Pod")
@Parameter(name = ApiConstants.START_IP, type = CommandType.STRING, description = "the starting IP address for the Pod")
private String startIp;
@Parameter(name = ApiConstants.END_IP, type = CommandType.STRING, description = "the ending IP address for the Pod")
private String endIp;
@Parameter(name = ApiConstants.NETMASK, type = CommandType.STRING, required = true, description = "the netmask for the Pod")
@Parameter(name = ApiConstants.NETMASK, type = CommandType.STRING, description = "the netmask for the Pod")
private String netmask;
@Parameter(name = ApiConstants.GATEWAY, type = CommandType.STRING, required = true, description = "the gateway for the Pod")
@Parameter(name = ApiConstants.GATEWAY, type = CommandType.STRING, description = "the gateway for the Pod")
private String gateway;
@Parameter(name = ApiConstants.ALLOCATION_STATE, type = CommandType.STRING, description = "Allocation state of this Pod for allocation of new resources")

View File

@ -87,6 +87,10 @@ public class CreateZoneCmd extends BaseCmd {
@Parameter(name = ApiConstants.LOCAL_STORAGE_ENABLED, type = CommandType.BOOLEAN, description = "true if local storage offering enabled, false otherwise")
private Boolean localStorageEnabled;
@Parameter(name = ApiConstants.IS_EDGE, type = CommandType.BOOLEAN, description = "true if the zone is an edge zone, false otherwise", since = "4.18.0")
private Boolean isEdge;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
@ -153,6 +157,13 @@ public class CreateZoneCmd extends BaseCmd {
return localStorageEnabled;
}
public boolean isEdge() {
if (isEdge == null) {
return false;
}
return isEdge;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
@Override

View File

@ -141,6 +141,10 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
@Param(description = "The maximum value the MTU can have on the VR's public interfaces", since = "4.18.0")
private Integer routerPublicInterfaceMaxMtu;
@SerializedName(ApiConstants.TYPE)
@Param(description = "the type of the zone - core or edge", since = "4.18.0")
String type;
public ZoneResponse() {
tags = new LinkedHashSet<ResourceTagResponse>();
}
@ -352,4 +356,12 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso
public void setRouterPublicInterfaceMaxMtu(Integer routerPublicInterfaceMaxMtu) {
this.routerPublicInterfaceMaxMtu = routerPublicInterfaceMaxMtu;
}
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
}

View File

@ -0,0 +1,35 @@
// 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.admin.zone;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class CreateZoneCmdTest {
@Test
public void isEdge() {
CreateZoneCmd createZoneCmd = new CreateZoneCmd();
ReflectionTestUtils.setField(createZoneCmd, "isEdge", null);
Assert.assertFalse("Null or no isedge param value for API should return false", createZoneCmd.isEdge());
ReflectionTestUtils.setField(createZoneCmd, "isEdge", false);
Assert.assertFalse("false value for isedge param value for API should return false", createZoneCmd.isEdge());
ReflectionTestUtils.setField(createZoneCmd, "isEdge", true);
Assert.assertTrue("true value for isedge param value for API should return true", createZoneCmd.isEdge());
}
}

View File

@ -127,7 +127,7 @@ public interface ConfigurationManager {
*
* @param userId
* @param podName
* @param zoneId
* @param zone
* @param gateway
* @param cidr
* @param startIp
@ -137,7 +137,7 @@ public interface ConfigurationManager {
* (true if it is ok to not validate that gateway IP address overlap with Start/End IP of the POD)
* @return Pod
*/
HostPodVO createPod(long userId, String podName, long zoneId, String gateway, String cidr, String startIp, String endIp, String allocationState,
HostPodVO createPod(long userId, String podName, DataCenter zone, String gateway, String cidr, String startIp, String endIp, String allocationState,
boolean skipGatewayOverlapCheck);
/**
@ -164,7 +164,7 @@ public interface ConfigurationManager {
*/
DataCenterVO createZone(long userId, String zoneName, String dns1, String dns2, String internalDns1, String internalDns2, String guestCidr, String domain,
Long domainId, NetworkType zoneType, String allocationState, String networkDomain, boolean isSecurityGroupEnabled, boolean isLocalStorageEnabled, String ip6Dns1,
String ip6Dns2);
String ip6Dns2, boolean isEdge);
/**
* Deletes a VLAN from the database, along with all of its IP addresses. Will not delete VLANs that have allocated

View File

@ -37,6 +37,7 @@ import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.engine.datacenter.entity.api.DataCenterResourceEntity.State;
import org.apache.cloudstack.engine.datacenter.entity.api.DataCenterResourceEntity.State.Event;
import com.cloud.dc.DataCenter;
import com.cloud.network.Network.Provider;
import com.cloud.org.Grouping;
import com.cloud.utils.NumbersUtil;
@ -156,6 +157,10 @@ public class EngineDataCenterVO implements EngineDataCenter, Identity {
@Temporal(value = TemporalType.TIMESTAMP)
protected Date lastUpdated;
@Column(name = "type")
@Enumerated(value = EnumType.STRING)
private DataCenter.Type type;
/**
* Note that state is intentionally missing the setter. Any updates to
* the state machine needs to go through the DAO object because someone
@ -513,4 +518,9 @@ public class EngineDataCenterVO implements EngineDataCenter, Identity {
public PartitionType partitionType() {
return PartitionType.Zone;
}
@Override
public DataCenter.Type getType() {
return type;
}
}

View File

@ -138,6 +138,10 @@ public class DataCenterVO implements DataCenter {
@Column(name = "sort_key")
int sortKey;
@Column(name = "type")
@Enumerated(value = EnumType.STRING)
private DataCenter.Type type;
@Override
public String getDnsProvider() {
return dnsProvider;
@ -472,6 +476,15 @@ public class DataCenterVO implements DataCenter {
return PartitionType.Zone;
}
@Override
public DataCenter.Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
@Override
public String toString() {
return String.format("Zone {\"id\": \"%s\", \"name\": \"%s\", \"uuid\": \"%s\"}", id, name, uuid);

View File

@ -104,6 +104,8 @@ public interface DataCenterDao extends GenericDao<DataCenterVO, Long> {
List<DataCenterVO> listEnabledZones();
List<Long> listEnabledNonEdgeZoneIds();
DataCenterVO findByToken(String zoneToken);
DataCenterVO findByTokenOrIdOrName(String tokenIdOrName);

View File

@ -20,14 +20,17 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import javax.persistence.TableGenerator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterDetailVO;
import com.cloud.dc.DataCenterIpAddressVO;
import com.cloud.dc.DataCenterLinkLocalIpAddressVO;
@ -63,6 +66,7 @@ public class DataCenterDaoImpl extends GenericDaoBase<DataCenterVO, Long> implem
protected SearchBuilder<DataCenterVO> ChildZonesSearch;
protected SearchBuilder<DataCenterVO> DisabledZonesSearch;
protected SearchBuilder<DataCenterVO> TokenSearch;
protected SearchBuilder<DataCenterVO> ZoneAllocationAndNotTypeSearch;
@Inject
protected DataCenterIpAddressDao _ipAllocDao = null;
@ -336,6 +340,11 @@ public class DataCenterDaoImpl extends GenericDaoBase<DataCenterVO, Long> implem
DisabledZonesSearch.and("allocationState", DisabledZonesSearch.entity().getAllocationState(), SearchCriteria.Op.EQ);
DisabledZonesSearch.done();
ZoneAllocationAndNotTypeSearch = createSearchBuilder();
ZoneAllocationAndNotTypeSearch.and("allocationState", ZoneAllocationAndNotTypeSearch.entity().getAllocationState(), SearchCriteria.Op.EQ);
ZoneAllocationAndNotTypeSearch.and("type", ZoneAllocationAndNotTypeSearch.entity().getType(), SearchCriteria.Op.NLIKE);
ZoneAllocationAndNotTypeSearch.done();
TokenSearch = createSearchBuilder();
TokenSearch.and("zoneToken", TokenSearch.entity().getZoneToken(), SearchCriteria.Op.EQ);
TokenSearch.done();
@ -399,6 +408,18 @@ public class DataCenterDaoImpl extends GenericDaoBase<DataCenterVO, Long> implem
return dcs;
}
@Override
public List<Long> listEnabledNonEdgeZoneIds() {
SearchCriteria<DataCenterVO> sc = ZoneAllocationAndNotTypeSearch.create();
sc.setParameters("allocationState", Grouping.AllocationState.Enabled);
sc.setParameters("type", DataCenter.Type.Edge);
List<DataCenterVO> zones = listBy(sc);
if (CollectionUtils.isEmpty(zones)) {
return new ArrayList<>();
}
return zones.stream().map(DataCenterVO::getId).collect(Collectors.toList());
}
@Override
public DataCenterVO findByTokenOrIdOrName(String tokenOrIdOrName) {
DataCenterVO result = findByToken(tokenOrIdOrName);

View File

@ -39,6 +39,50 @@ UPDATE `cloud`.`networks` SET public_mtu = 1500, private_mtu = 1500 WHERE remove
UPDATE `cloud`.`vpc` SET public_mtu = 1500 WHERE removed IS NULL;
UPDATE `cloud`.`nics` SET mtu = 1500 WHERE vm_type='DomainRouter' AND removed IS NULL AND reserver_name IN ('PublicNetworkGuru', 'ExternalGuestNetworkGuru');
-- Add type column to data_center table
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.data_center', 'type', 'varchar(32) DEFAULT ''Core'' COMMENT ''the type of the zone'' ');
-- Recreate data_center_view
DROP VIEW IF EXISTS `cloud`.`data_center_view`;
CREATE VIEW `cloud`.`data_center_view` AS
select
data_center.id,
data_center.uuid,
data_center.name,
data_center.is_security_group_enabled,
data_center.is_local_storage_enabled,
data_center.description,
data_center.dns1,
data_center.dns2,
data_center.ip6_dns1,
data_center.ip6_dns2,
data_center.internal_dns1,
data_center.internal_dns2,
data_center.guest_network_cidr,
data_center.domain,
data_center.networktype,
data_center.allocation_state,
data_center.zone_token,
data_center.dhcp_provider,
data_center.type,
data_center.removed,
data_center.sort_key,
domain.id domain_id,
domain.uuid domain_uuid,
domain.name domain_name,
domain.path domain_path,
dedicated_resources.affinity_group_id,
dedicated_resources.account_id,
affinity_group.uuid affinity_group_uuid
from
`cloud`.`data_center`
left join
`cloud`.`domain` ON data_center.domain_id = domain.id
left join
`cloud`.`dedicated_resources` ON data_center.id = dedicated_resources.data_center_id
left join
`cloud`.`affinity_group` ON dedicated_resources.affinity_group_id = affinity_group.id;
DROP VIEW IF EXISTS `cloud`.`domain_router_view`;
CREATE VIEW `cloud`.`domain_router_view` AS
select
@ -65,7 +109,7 @@ CREATE VIEW `cloud`.`domain_router_view` AS
data_center.id data_center_id,
data_center.uuid data_center_uuid,
data_center.name data_center_name,
data_center.networktype data_center_type,
data_center.networktype data_center_network_type,
data_center.dns1 dns1,
data_center.dns2 dns2,
data_center.ip6_dns1 ip6_dns1,
@ -751,7 +795,7 @@ SELECT
`data_center`.`uuid` AS `data_center_uuid`,
`data_center`.`name` AS `data_center_name`,
`data_center`.`is_security_group_enabled` AS `security_group_enabled`,
`data_center`.`networktype` AS `data_center_type`,
`data_center`.`networktype` AS `data_center_network_type`,
`host`.`id` AS `host_id`,
`host`.`uuid` AS `host_uuid`,
`host`.`name` AS `host_name`,

View File

@ -28,6 +28,9 @@ import com.cloud.agent.api.MaintainAnswer;
import com.cloud.agent.api.PingTestCommand;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.api.commands.SimulatorAddSecondaryAgent;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.exception.DiscoveryException;
import com.cloud.host.HostVO;
@ -79,6 +82,8 @@ import java.util.regex.PatternSyntaxException;
public class MockAgentManagerImpl extends ManagerBase implements MockAgentManager {
private static final Logger s_logger = Logger.getLogger(MockAgentManagerImpl.class);
@Inject
DataCenterDao dcDao;
@Inject
HostPodDao _podDao = null;
@Inject
MockHostDao _mockHostDao = null;
@ -106,7 +111,12 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage
private Pair<String, Long> getPodCidr(long podId, long dcId) {
try {
DataCenterVO zone = dcDao.findById(dcId);
if (DataCenter.Type.Edge.equals(zone.getType())) {
String subnet = String.format("172.%d.%d.0", random.nextInt(15) + 16, random.nextInt(6) + 1);
s_logger.info(String.format("Pod belongs to an edge zone hence CIDR cannot be found, returning %s/24", subnet));
return new Pair<>(subnet, 24L);
}
HashMap<Long, List<Object>> podMap = _podDao.getCurrentPodCidrSubnets(dcId, 0);
List<Object> cidrPair = podMap.get(podId);
String cidrAddress = (String)cidrPair.get(0);

View File

@ -387,7 +387,7 @@ public class ManagementServerMock {
ConfigurationManager mgr = (ConfigurationManager)_configService;
_zone =
mgr.createZone(User.UID_SYSTEM, "default", "8.8.8.8", null, "8.8.4.4", null, null /* cidr */, "ROOT", Domain.ROOT_DOMAIN, NetworkType.Advanced, null,
null /* networkDomain */, false, false, null, null);
null /* networkDomain */, false, false, null, null, false);
}
}

View File

@ -37,11 +37,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.host.ControlState;
import com.cloud.utils.security.CertificateHelper;
import com.cloud.user.UserData;
import com.cloud.api.query.dao.UserVmJoinDao;
import com.cloud.network.vpc.VpcVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
@ -205,6 +200,7 @@ import org.apache.log4j.Logger;
import com.cloud.agent.api.VgpuTypesInfo;
import com.cloud.api.query.ViewResponseHelper;
import com.cloud.api.query.dao.UserVmJoinDao;
import com.cloud.api.query.vo.AccountJoinVO;
import com.cloud.api.query.vo.AsyncJobJoinVO;
import com.cloud.api.query.vo.ControlledViewEntity;
@ -254,6 +250,7 @@ import com.cloud.event.Event;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.gpu.GPU;
import com.cloud.host.ControlState;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.hypervisor.HypervisorCapabilities;
@ -318,6 +315,7 @@ import com.cloud.network.vpc.PrivateGateway;
import com.cloud.network.vpc.StaticRoute;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcOffering;
import com.cloud.network.vpc.VpcVO;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.NetworkOffering.Detail;
@ -361,6 +359,7 @@ import com.cloud.user.AccountManager;
import com.cloud.user.SSHKeyPair;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.UserData;
import com.cloud.user.UserStatisticsVO;
import com.cloud.user.dao.UserStatisticsDao;
import com.cloud.uservm.UserVm;
@ -374,6 +373,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.Dhcp;
import com.cloud.utils.net.Ip;
import com.cloud.utils.net.NetUtils;
import com.cloud.utils.security.CertificateHelper;
import com.cloud.vm.ConsoleProxyVO;
import com.cloud.vm.InstanceGroup;
import com.cloud.vm.Nic;

View File

@ -183,6 +183,7 @@ import com.cloud.api.query.vo.TemplateJoinVO;
import com.cloud.api.query.vo.UserAccountJoinVO;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.api.query.vo.VolumeJoinVO;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DedicatedResourceVO;
import com.cloud.dc.dao.DedicatedResourceDao;
import com.cloud.domain.Domain;
@ -3034,6 +3035,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<DiskOfferingJoinVO> zoneSC = sb.create();
zoneSC.setParameters("zoneId", String.valueOf(zoneId));
sc.addAnd("zoneId", SearchCriteria.Op.SC, zoneSC);
DataCenterJoinVO zone = _dcJoinDao.findById(zoneId);
if (DataCenter.Type.Edge.equals(zone.getType())) {
sc.addAnd("useLocalStorage", Op.EQ, true);
}
}
DiskOffering currentDiskOffering = null;
@ -3281,6 +3286,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<ServiceOfferingJoinVO> zoneSC = sb.create();
zoneSC.setParameters("zoneId", String.valueOf(zoneId));
sc.addAnd("zoneId", SearchCriteria.Op.SC, zoneSC);
DataCenterJoinVO zone = _dcJoinDao.findById(zoneId);
if (DataCenter.Type.Edge.equals(zone.getType())) {
sc.addAnd("useLocalStorage", Op.EQ, true);
}
}
if (cpuNumber != null) {

View File

@ -27,6 +27,7 @@ import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -69,6 +70,7 @@ public class DataCenterJoinDaoImpl extends GenericDaoBase<DataCenterJoinVO, Long
zoneResponse.setName(dataCenter.getName());
zoneResponse.setSecurityGroupsEnabled(ApiDBUtils.isSecurityGroupEnabledInZone(dataCenter.getId()));
zoneResponse.setLocalStorageEnabled(dataCenter.isLocalStorageEnabled());
zoneResponse.setType(ObjectUtils.defaultIfNull(dataCenter.getType(), DataCenter.Type.Core).toString());
if ((dataCenter.getDescription() != null) && !dataCenter.getDescription().equalsIgnoreCase("null")) {
zoneResponse.setDescription(dataCenter.getDescription());

View File

@ -27,9 +27,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.network.vpc.VpcVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.storage.DiskOfferingVO;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -53,7 +50,10 @@ import com.cloud.api.ApiResponseHelper;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.gpu.GPU;
import com.cloud.host.ControlState;
import com.cloud.network.vpc.VpcVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.service.ServiceOfferingDetailsVO;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.GuestOS;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;

View File

@ -28,6 +28,7 @@ import javax.persistence.Table;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.org.Grouping.AllocationState;
import com.cloud.utils.db.GenericDao;
@ -120,6 +121,10 @@ public class DataCenterJoinVO extends BaseViewVO implements InternalIdentity, Id
@Column(name = "sort_key")
private int sortKey;
@Column(name = "type")
@Enumerated(value = EnumType.STRING)
private DataCenter.Type type;
public DataCenterJoinVO() {
}
@ -228,4 +233,8 @@ public class DataCenterJoinVO extends BaseViewVO implements InternalIdentity, Id
public int getSortKey() {
return sortKey;
}
public DataCenter.Type getType() {
return type;
}
}

View File

@ -1402,14 +1402,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
}
private void checkPodAttributes(final long podId, final String podName, final long zoneId, final String gateway, final String cidr, final String startIp, final String endIp, final String allocationStateStr,
final boolean checkForDuplicates, final boolean skipGatewayOverlapCheck) {
if (checkForDuplicates) {
// Check if the pod already exists
if (validPod(podName, zoneId)) {
throw new InvalidParameterValueException("A pod with name: " + podName + " already exists in zone " + zoneId + ". Please specify a different pod name. ");
}
}
private void checkPodAttributesForNonEdgeZone(final long podId, final String podName, final DataCenter zone, final String gateway,
final String cidr, final String startIp, final String endIp, final boolean skipGatewayOverlapCheck) {
String cidrAddress;
long cidrSize;
@ -1427,7 +1421,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
// Check if the IP range overlaps with the public ip
if (StringUtils.isNotEmpty(startIp)) {
checkOverlapPublicIpRange(zoneId, startIp, endIp);
checkOverlapPublicIpRange(zone.getId(), startIp, endIp);
}
// Check if the gateway is a valid IP address
@ -1449,7 +1443,21 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
final String checkPodCIDRs = _configDao.getValue("check.pod.cidrs");
if (checkPodCIDRs == null || checkPodCIDRs.trim().isEmpty() || Boolean.parseBoolean(checkPodCIDRs)) {
checkPodCidrSubnets(zoneId, podId, cidr);
checkPodCidrSubnets(zone.getId(), podId, cidr);
}
}
private void checkPodAttributes(final long podId, final String podName, final DataCenter zone, final String gateway, final String cidr, final String startIp, final String endIp, final String allocationStateStr,
final boolean checkForDuplicates, final boolean skipGatewayOverlapCheck) {
if (checkForDuplicates) {
// Check if the pod already exists
if (validPod(podName, zone.getId())) {
throw new InvalidParameterValueException("A pod with name: " + podName + " already exists in zone " + zone.getId() + ". Please specify a different pod name. ");
}
}
if (!DataCenter.Type.Edge.equals(zone.getType())) {
checkPodAttributesForNonEdgeZone(podId, podName, zone, gateway, cidr, startIp, endIp, skipGatewayOverlapCheck);
}
if (allocationStateStr != null && !allocationStateStr.isEmpty()) {
@ -2098,7 +2106,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
// Verify pod's attributes
final String cidr = NetUtils.ipAndNetMaskToCidr(gateway, netmask);
final boolean checkForDuplicates = !oldPodName.equals(name);
checkPodAttributes(id, name, pod.getDataCenterId(), gateway, cidr, startIp, endIp, allocationStateStr, checkForDuplicates, true);
final DataCenterVO zone = _zoneDao.findById(pod.getDataCenterId());
checkPodAttributes(id, name, zone, gateway, cidr, startIp, endIp, allocationStateStr, checkForDuplicates, true);
// Valid check is already done in checkPodAttributes method.
final String cidrAddress = getCidrAddress(cidr);
@ -2161,48 +2170,62 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
return pod;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_POD_CREATE, eventDescription = "creating pod", async = false)
public Pod createPod(final long zoneId, final String name, final String startIp, final String endIp, final String gateway, final String netmask, String allocationState) {
// Check if the gateway is a valid IP address
private void checkPodRangeParametersBasicsForNonEdgeZone(final String startIp, final String endIp, final String gateway, final String netmask) {
if (!NetUtils.isValidIp4(startIp)) {
throw new InvalidParameterValueException("The start IP is invalid");
}
if (endIp != null && !NetUtils.isValidIp4(endIp)) {
throw new InvalidParameterValueException("The end IP is invalid");
}
if (!NetUtils.isValidIp4(gateway)) {
throw new InvalidParameterValueException("The gateway is invalid");
}
if (!NetUtils.isValidIp4Netmask(netmask)) {
throw new InvalidParameterValueException("The netmask is invalid");
}
final String cidr = NetUtils.ipAndNetMaskToCidr(gateway, netmask);
final Long userId = CallContext.current().getCallingUserId();
if (allocationState == null) {
allocationState = Grouping.AllocationState.Enabled.toString();
}
return createPod(userId.longValue(), name, zoneId, gateway, cidr, startIp, endIp, allocationState, false);
}
@Override
@DB
public HostPodVO createPod(final long userId, final String podName, final long zoneId, final String gateway, final String cidr, final String startIp, String endIp, final String allocationStateStr,
final boolean skipGatewayOverlapCheck) {
// Check if the zone is valid
if (!validZone(zoneId)) {
@ActionEvent(eventType = EventTypes.EVENT_POD_CREATE, eventDescription = "creating pod", async = false)
public Pod createPod(final long zoneId, final String name, final String startIp, final String endIp, final String gateway, final String netmask, String allocationState) {
final DataCenterVO zone = _zoneDao.findById(zoneId);
if (zone == null) {
throw new InvalidParameterValueException("Please specify a valid zone.");
}
// Check if zone is disabled
final DataCenterVO zone = _zoneDao.findById(zoneId);
final Account account = CallContext.current().getCallingAccount();
if (Grouping.AllocationState.Disabled == zone.getAllocationState()
&& !_accountMgr.isRootAdmin(account.getId())) {
throw new PermissionDeniedException("Cannot perform this operation, Zone is currently disabled: " + zoneId);
}
final String cidrAddress = getCidrAddress(cidr);
final int cidrSize = getCidrSize(cidr);
String cidr = null;
if (!DataCenter.Type.Edge.equals(zone.getType())) {
checkPodRangeParametersBasicsForNonEdgeZone(startIp, endIp, gateway, netmask);
cidr = NetUtils.ipAndNetMaskToCidr(gateway, netmask);
} else {
if (ObjectUtils.anyNotNull(startIp, endIp, gateway, netmask)) {
throw new InvalidParameterValueException("IP range parameters can not be specified for a pod in an edge zone");
}
}
final Long userId = CallContext.current().getCallingUserId();
if (allocationState == null) {
allocationState = Grouping.AllocationState.Enabled.toString();
}
return createPod(userId.longValue(), name, zone, gateway, cidr, startIp, endIp, allocationState, false);
}
@Override
@DB
public HostPodVO createPod(final long userId, final String podName, final DataCenter zone, final String gateway, final String cidr, String startIp, String endIp, final String allocationStateStr,
final boolean skipGatewayOverlapCheck) {
final String cidrAddress = DataCenter.Type.Edge.equals(zone.getType()) ? "" : getCidrAddress(cidr);
final int cidrSize = DataCenter.Type.Edge.equals(zone.getType()) ? 0 : getCidrSize(cidr);
if (DataCenter.Type.Edge.equals(zone.getType())) {
startIp = null;
endIp = null;
}
// endIp is an optional parameter; if not specified - default it to the
// end ip of the pod's cidr
@ -2213,18 +2236,15 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
// Validate new pod settings
checkPodAttributes(-1, podName, zoneId, gateway, cidr, startIp, endIp, allocationStateStr, true, skipGatewayOverlapCheck);
checkPodAttributes(-1, podName, zone, gateway, cidr, startIp, endIp, allocationStateStr, true, skipGatewayOverlapCheck);
// Create the new pod in the database
String ipRange;
String ipRange = null;
if (StringUtils.isNotEmpty(startIp)) {
ipRange = startIp + "-" + endIp + "-" + DefaultForSystemVmsForPodIpRange + "-" + DefaultVlanForPodIpRange;
} else {
throw new InvalidParameterValueException("Start ip is required parameter");
}
final HostPodVO podFinal = new HostPodVO(podName, zoneId, gateway, cidrAddress, cidrSize, ipRange);
final HostPodVO podFinal = new HostPodVO(podName, zone.getId(), StringUtils.defaultIfEmpty(gateway, "") , cidrAddress, cidrSize, ipRange);
Grouping.AllocationState allocationState = null;
if (allocationStateStr != null && !allocationStateStr.isEmpty()) {
@ -2232,26 +2252,24 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
podFinal.setAllocationState(allocationState);
}
final String startIpFinal = startIp;
final String endIpFinal = endIp;
return Transaction.execute(new TransactionCallback<HostPodVO>() {
@Override
public HostPodVO doInTransaction(final TransactionStatus status) {
return Transaction.execute((TransactionCallback<HostPodVO>) status -> {
final HostPodVO pod = _podDao.persist(podFinal);
if (StringUtils.isNotEmpty(startIp)) {
_zoneDao.addPrivateIpAddress(zoneId, pod.getId(), startIp, endIpFinal, false, null);
if (StringUtils.isNotEmpty(startIpFinal)) {
_zoneDao.addPrivateIpAddress(zone.getId(), pod.getId(), startIpFinal, endIpFinal, false, null);
}
final String[] linkLocalIpRanges = NetUtils.getLinkLocalIPRange(_configDao.getValue(Config.ControlCidr.key()));
if (linkLocalIpRanges != null) {
_zoneDao.addLinkLocalIpAddress(zoneId, pod.getId(), linkLocalIpRanges[0], linkLocalIpRanges[1]);
if (linkLocalIpRanges.length > 1) {
_zoneDao.addLinkLocalIpAddress(zone.getId(), pod.getId(), linkLocalIpRanges[0], linkLocalIpRanges[1]);
}
CallContext.current().putContextParameter(Pod.class, pod.getUuid());
return pod;
}
});
}
@ -2624,7 +2642,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
if (allocationStateStr != null && !allocationStateStr.isEmpty()) {
final Grouping.AllocationState allocationState = Grouping.AllocationState.valueOf(allocationStateStr);
if (allocationState == Grouping.AllocationState.Enabled) {
if (allocationState == Grouping.AllocationState.Enabled && !DataCenter.Type.Edge.equals(zone.getType())) {
// check if zone has necessary trafficTypes before enabling
try {
PhysicalNetwork mgmtPhyNetwork;
@ -2694,7 +2712,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
@DB
public DataCenterVO createZone(final long userId, final String zoneName, final String dns1, final String dns2, final String internalDns1, final String internalDns2, final String guestCidr, final String domain,
final Long domainId, final NetworkType zoneType, final String allocationStateStr, final String networkDomain, final boolean isSecurityGroupEnabled, final boolean isLocalStorageEnabled,
final String ip6Dns1, final String ip6Dns2) {
final String ip6Dns1, final String ip6Dns2, final boolean isEdge) {
// checking the following params outside checkzoneparams method as we do
// not use these params for updatezone
@ -2728,6 +2746,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
// physical network and providers setup.
zoneFinal.setAllocationState(Grouping.AllocationState.Disabled);
}
zoneFinal.setType(isEdge ? DataCenter.Type.Edge : DataCenter.Type.Core);
return Transaction.execute(new TransactionCallback<DataCenterVO>() {
@Override
@ -2838,6 +2857,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
final String networkDomain = cmd.getDomain();
boolean isSecurityGroupEnabled = cmd.getSecuritygroupenabled();
final boolean isLocalStorageEnabled = cmd.getLocalStorageEnabled();
final boolean isEdge = cmd.isEdge();
if (allocationState == null) {
allocationState = Grouping.AllocationState.Disabled.toString();
@ -2856,6 +2876,10 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
throw new InvalidParameterValueException("guestCidrAddress parameter is not supported for Basic zone");
}
if (!NetworkType.Advanced.equals(zoneType) && isEdge) {
throw new InvalidParameterValueException("Only advanced network type zones can be edge zones");
}
DomainVO domainVO = null;
if (domainId != null) {
@ -2867,7 +2891,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
return createZone(userId, zoneName, dns1, dns2, internalDns1, internalDns2, guestCidr, domainVO != null ? domainVO.getName() : null, domainId, zoneType, allocationState,
networkDomain, isSecurityGroupEnabled, isLocalStorageEnabled, ip6Dns1, ip6Dns2);
networkDomain, isSecurityGroupEnabled, isLocalStorageEnabled, ip6Dns1, ip6Dns2, isEdge);
}
@Override
@ -5711,10 +5735,6 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
}
private boolean validPod(final String podName, final long zoneId) {
if (!validZone(zoneId)) {
return false;
}
return _podDao.findByName(podName, zoneId) != null;
}

View File

@ -21,15 +21,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.utils.PasswordGenerator;
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.consoleproxy.ConsoleAccessManager;
@ -48,6 +50,7 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
@ -117,6 +120,7 @@ import com.cloud.user.AccountManager;
import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.PasswordGenerator;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB;
@ -148,10 +152,6 @@ import com.cloud.vm.dao.VMInstanceDao;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
/**
* Class to manage console proxys. <br><br>
@ -1491,15 +1491,11 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
@Override
public Long[] getScannablePools() {
List<DataCenterVO> zones = dataCenterDao.listEnabledZones();
Long[] dcIdList = new Long[zones.size()];
int i = 0;
for (DataCenterVO dc : zones) {
dcIdList[i++] = dc.getId();
List<Long> zoneIds = dataCenterDao.listEnabledNonEdgeZoneIds();
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("Enabled non-edge zones available for scan: %s", org.apache.commons.lang3.StringUtils.join(zoneIds, ",")));
}
return dcIdList;
return zoneIds.toArray(Long[]::new);
}
@Override

View File

@ -3320,6 +3320,11 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {
RouterTemplateKvm,
RouterTemplateVmware,
RouterTemplateHyperV,
RouterTemplateLxc,
RouterTemplateOvm3,
UseExternalDnsServers,
RouterVersionCheckEnabled,
SetServiceMonitor,

View File

@ -78,7 +78,10 @@ import com.cloud.api.query.MutualExclusiveIdsManagerBase;
import com.cloud.configuration.Config;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.ClusterDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.domain.dao.DomainDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.ActionEventUtils;
@ -216,6 +219,8 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
@Inject
protected SnapshotHelper snapshotHelper;
@Inject
DataCenterDao dataCenterDao;
private int _totalRetries;
private int _pauseInterval;
@ -223,6 +228,14 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
private ScheduledExecutorService backupSnapshotExecutor;
protected boolean isBackupSnapshotToSecondaryForZone(long zoneId) {
if (Boolean.FALSE.equals(SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value())) {
return false;
}
DataCenterVO zone = dataCenterDao.findById(zoneId);
return !DataCenter.Type.Edge.equals(zone.getType());
}
@Override
public String getConfigComponentName() {
return SnapshotManager.class.getSimpleName();
@ -1250,7 +1263,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
}
SnapshotInfo snapshotOnPrimary = snapshotStrategy.takeSnapshot(snapshot);
boolean backupSnapToSecondary = SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value() == null || SnapshotInfo.BackupSnapshotAfterTakingSnapshot.value();
boolean backupSnapToSecondary = isBackupSnapshotToSecondaryForZone(snapshot.getDataCenterId());
if (backupSnapToSecondary) {
backupSnapshotToSecondary(payload.getAsyncBackup(), snapshotStrategy, snapshotOnPrimary);
@ -1262,7 +1275,7 @@ public class SnapshotManagerImpl extends MutualExclusiveIdsManagerBase implement
try {
postCreateSnapshot(volume.getId(), snapshotId, payload.getSnapshotPolicyId());
DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot);
DataStoreRole dataStoreRole = backupSnapToSecondary ? snapshotHelper.getDataStoreRole(snapshot) : DataStoreRole.Primary;
SnapshotDataStoreVO snapshotStoreRef = _snapshotStoreDao.findBySnapshot(snapshotId, dataStoreRole);
if (snapshotStoreRef == null) {

View File

@ -16,11 +16,33 @@
// under the License.
package org.apache.cloudstack.consoleproxy;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.GetVmVncTicketAnswer;
import com.cloud.agent.api.GetVmVncTicketCommand;
import com.cloud.consoleproxy.ConsoleProxyManager;
import com.cloud.dc.DataCenter;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException;
@ -48,25 +70,6 @@ import com.cloud.vm.dao.ConsoleSessionDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAccessManager {
@ -87,6 +90,8 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
@Inject
private ConsoleProxyManager consoleProxyManager;
@Inject
DataCenterDao dataCenterDao;
@Inject
private ConsoleSessionDao consoleSessionDao;
private static KeysManager secretKeysManager;
@ -135,6 +140,13 @@ public class ConsoleAccessManagerImpl extends ManagerBase implements ConsoleAcce
return new ConsoleEndpoint(false, null, "Permission denied");
}
DataCenter zone = dataCenterDao.findById(vm.getDataCenterId());
if (zone != null && DataCenter.Type.Edge.equals(zone.getType())) {
String errorMsg = "Console access is not supported for Edge zones";
s_logger.error(errorMsg);
return new ConsoleEndpoint(false, null, errorMsg);
}
String sessionUuid = UUID.randomUUID().toString();
return generateAccessEndpoint(vmId, sessionUuid, extraSecurityToken, clientAddress);
} catch (Exception e) {

View File

@ -46,6 +46,8 @@ import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6Prefi
import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.ReleasePublicIpRangeCmd;
import org.apache.cloudstack.api.command.admin.zone.CreateZoneCmd;
import org.apache.cloudstack.api.command.admin.zone.UpdateZoneCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
@ -66,12 +68,14 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.stubbing.Answer;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.api.query.dao.NetworkOfferingJoinDao;
import com.cloud.api.query.vo.NetworkOfferingJoinVO;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.AccountVlanMapVO;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.dc.DataCenterGuestIpv6Prefix;
import com.cloud.dc.DataCenterGuestIpv6PrefixVO;
@ -106,10 +110,10 @@ import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.projects.ProjectManager;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.storage.DiskOfferingVO;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.DiskOfferingDao;
import com.cloud.storage.dao.StoragePoolTagsDao;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
@ -1230,4 +1234,95 @@ public class ConfigurationManagerTest {
throw new RuntimeException(e);
}
}
private void mockPersistDatacenterForCreateZone() {
Mockito.when(_zoneDao.persist(Mockito.any(DataCenterVO.class))).thenAnswer((Answer<DataCenterVO>) invocation -> {
DataCenterVO zone = (DataCenterVO)invocation.getArguments()[0];
ReflectionTestUtils.setField(zone, "uuid", UUID.randomUUID().toString());
ReflectionTestUtils.setField(zone, "id", 1L);
return zone;
});
}
@Test
public void testCreateEdgeZone() {
CreateZoneCmd cmd = Mockito.mock(CreateZoneCmd.class);
Mockito.when(cmd.isEdge()).thenReturn(true);
Mockito.when(cmd.getNetworkType()).thenReturn(NetworkType.Advanced.toString());
Mockito.when(cmd.getDomainId()).thenReturn(null);
mockPersistDatacenterForCreateZone();
DataCenter zone = configurationMgr.createZone(cmd);
Assert.assertNotNull(zone);
Assert.assertEquals(NetworkType.Advanced, zone.getNetworkType());
Assert.assertEquals(DataCenter.Type.Edge, zone.getType());
}
@Test
public void testCreateCoreZone() {
CreateZoneCmd cmd = Mockito.mock(CreateZoneCmd.class);
Mockito.when(cmd.isEdge()).thenReturn(false);
Mockito.when(cmd.getNetworkType()).thenReturn(NetworkType.Advanced.toString());
Mockito.when(cmd.getDomainId()).thenReturn(null);
mockPersistDatacenterForCreateZone();
DataCenter zone = configurationMgr.createZone(cmd);
Assert.assertNotNull(zone);
Assert.assertEquals(NetworkType.Advanced, zone.getNetworkType());
Assert.assertEquals(DataCenter.Type.Core, zone.getType());
}
@Test
public void testCreateBasicZone() {
CreateZoneCmd cmd = Mockito.mock(CreateZoneCmd.class);
Mockito.when(cmd.isEdge()).thenReturn(false);
Mockito.when(cmd.getNetworkType()).thenReturn(NetworkType.Basic.toString());
Mockito.when(cmd.getDomainId()).thenReturn(null);
mockPersistDatacenterForCreateZone();
DataCenter zone = configurationMgr.createZone(cmd);
Assert.assertNotNull(zone);
Assert.assertEquals(NetworkType.Basic, zone.getNetworkType());
}
@Test(expected = InvalidParameterValueException.class)
public void testCreateBasicEdgeZoneFailure() {
CreateZoneCmd cmd = Mockito.mock(CreateZoneCmd.class);
Mockito.when(cmd.isEdge()).thenReturn(true);
Mockito.when(cmd.getNetworkType()).thenReturn(NetworkType.Basic.toString());
Mockito.when(cmd.getDomainId()).thenReturn(null);
configurationMgr.createZone(cmd);
}
@Test
public void testEditEdgeZone() {
// editZone should be successful despite no Public network
final Long zoneId = 1L;
UpdateZoneCmd cmd = Mockito.mock(UpdateZoneCmd.class);
Mockito.when(cmd.getId()).thenReturn(zoneId);
Mockito.when(cmd.getZoneName()).thenReturn("NewName");
DataCenterVO zone = Mockito.mock(DataCenterVO.class);
Mockito.when(zone.getNetworkType()).thenReturn(NetworkType.Advanced);
Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Edge);
Mockito.when(zone.getId()).thenReturn(zoneId);
Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(zone);
Mockito.when(_networkModel.getDefaultPhysicalNetworkByZoneAndTrafficType(zoneId, Networks.TrafficType.Public)).thenReturn(null);
Mockito.when(_zoneDao.update(Mockito.anyLong(), Mockito.any(DataCenterVO.class))).thenReturn(true);
configurationMgr.editZone(cmd);
}
@Test
public void testEdgeZoneCreatePod() {
final long zoneId = 1L;
DataCenterVO zone = Mockito.mock(DataCenterVO.class);
Mockito.when(zone.getNetworkType()).thenReturn(NetworkType.Advanced);
Mockito.when(zone.getType()).thenReturn(DataCenter.Type.Edge);
Mockito.when(zone.getId()).thenReturn(1L);
Mockito.when(_zoneDao.findById(Mockito.anyLong())).thenReturn(zone);
Mockito.when(_configDao.getValue(Config.ControlCidr.key())).thenReturn(Config.ControlCidr.getDefaultValue());
Mockito.when(_podDao.persist(Mockito.any(HostPodVO.class))).thenAnswer((Answer<HostPodVO>) invocation -> {
HostPodVO pod = (HostPodVO)invocation.getArguments()[0];
ReflectionTestUtils.setField(pod, "uuid", UUID.randomUUID().toString());
ReflectionTestUtils.setField(pod, "id", 1L);
return pod;
});
configurationMgr.createPod(zoneId, "TestPod", null, null, null, null, null);
}
}

View File

@ -24,7 +24,10 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;
import org.junit.Assert;
@ -275,4 +278,24 @@ public class ConsoleProxyManagerTest {
ConsoleProxyStatus result = new ConsoleProxyManagerImpl().parseJsonToConsoleProxyStatus(null);
Assert.assertEquals(expectedResult, result);
}
private void verifyScannablePoolsZoneIds(List<Long> expected, Long[] result) {
Assert.assertNotNull(result);
Assert.assertEquals(expected.size(), result.length);
for (int i = 0; i < expected.size(); ++i) {
Assert.assertEquals(expected.get(i), result[i]);
}
}
@Test
public void testGetScannablePools() {
List<Long> dbZoneIds = new ArrayList<>();
Mockito.when(dataCenterDaoMock.listEnabledNonEdgeZoneIds()).thenReturn(dbZoneIds);
ConsoleProxyManagerImpl consoleProxyManager = new ConsoleProxyManagerImpl();
ReflectionTestUtils.setField(consoleProxyManager, "dataCenterDao", dataCenterDaoMock);
verifyScannablePoolsZoneIds(dbZoneIds, consoleProxyManager.getScannablePools());
dbZoneIds = Arrays.asList(2L, 3L);
Mockito.when(dataCenterDaoMock.listEnabledNonEdgeZoneIds()).thenReturn(dbZoneIds);
verifyScannablePoolsZoneIds(dbZoneIds, consoleProxyManager.getScannablePools());
}
}

View File

@ -24,7 +24,11 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.cloudstack.acl.ControlledEntity;
@ -37,10 +41,11 @@ import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotStrategy.SnapshotOperation;
import org.apache.cloudstack.snapshot.SnapshotHelper;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageStrategyFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.snapshot.SnapshotHelper;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@ -49,12 +54,21 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.verification.VerificationMode;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.hypervisor.Hypervisor;
@ -88,15 +102,6 @@ import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.verification.VerificationMode;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(GlobalLock.class)
@ -171,6 +176,8 @@ public class SnapshotManagerTest {
@Mock
TaggedResourceService taggedResourceServiceMock;
@Mock
DataCenterDao dataCenterDao;
SnapshotPolicyVO snapshotPolicyVoInstance;
@ -207,6 +214,7 @@ public class SnapshotManagerTest {
_snapshotMgr._snapshotPolicyDao = snapshotPolicyDaoMock;
_snapshotMgr._snapSchedMgr = snapshotSchedulerMock;
_snapshotMgr.taggedResourceService = taggedResourceServiceMock;
_snapshotMgr.dataCenterDao = dataCenterDao;
when(_snapshotDao.findById(anyLong())).thenReturn(snapshotMock);
when(snapshotMock.getVolumeId()).thenReturn(TEST_VOLUME_ID);
@ -533,4 +541,38 @@ public class SnapshotManagerTest {
Mockito.any(DateUtil.IntervalType.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyBoolean());
Mockito.verify(_snapshotMgr, timesVerification).createTagsForSnapshotPolicy(Mockito.any(), Mockito.any());
}
private void mockForBackupSnapshotToSecondaryZoneTest(final Boolean configValue, final DataCenter.Type dcType) {
try {
Field f = ConfigKey.class.getDeclaredField("_value");
f.setAccessible(true);
f.set(SnapshotInfo.BackupSnapshotAfterTakingSnapshot, configValue);
} catch (NoSuchFieldException | IllegalAccessException e) {
Assert.fail(String.format("Failed to override config: %s", e.getMessage()));
}
DataCenterVO dc = Mockito.mock(DataCenterVO.class);
Mockito.when(dc.getType()).thenReturn(dcType);
Mockito.when(dataCenterDao.findById(1L)).thenReturn(dc);
}
@Test
public void testIsBackupSnapshotToSecondaryForCoreZoneNullConfig() {
mockForBackupSnapshotToSecondaryZoneTest(null, DataCenter.Type.Core);
Assert.assertTrue(_snapshotMgr.isBackupSnapshotToSecondaryForZone(1L));
}
@Test
public void testIsBackupSnapshotToSecondaryForCoreZoneEnabledConfig() {
mockForBackupSnapshotToSecondaryZoneTest(true, DataCenter.Type.Core);
Assert.assertTrue(_snapshotMgr.isBackupSnapshotToSecondaryForZone(1L));
}
@Test
public void testIsBackupSnapshotToSecondaryForCoreZoneDisabledConfig() {
mockForBackupSnapshotToSecondaryZoneTest(false, DataCenter.Type.Core);
Assert.assertFalse(_snapshotMgr.isBackupSnapshotToSecondaryForZone(1L));
}
@Test
public void testIsBackupSnapshotToSecondaryForEdgeZone() {
mockForBackupSnapshotToSecondaryZoneTest(true, DataCenter.Type.Edge);
Assert.assertFalse(_snapshotMgr.isBackupSnapshotToSecondaryForZone(1L));
}
}

View File

@ -506,7 +506,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
* @see com.cloud.configuration.ConfigurationManager#createPod(long, java.lang.String, long, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, boolean)
*/
@Override
public HostPodVO createPod(long userId, String podName, long zoneId, String gateway, String cidr, String startIp, String endIp, String allocationState,
public HostPodVO createPod(long userId, String podName, DataCenter zone, String gateway, String cidr, String startIp, String endIp, String allocationState,
boolean skipGatewayOverlapCheck) {
// TODO Auto-generated method stub
return null;
@ -632,7 +632,7 @@ public class MockConfigurationManagerImpl extends ManagerBase implements Configu
@Override
public DataCenterVO createZone(long userId, String zoneName, String dns1, String dns2, String internalDns1, String internalDns2, String guestCidr, String domain,
Long domainId, NetworkType zoneType, String allocationState, String networkDomain, boolean isSecurityGroupEnabled, boolean isLocalStorageEnabled, String ip6Dns1,
String ip6Dns2) {
String ip6Dns2, boolean isEdge) {
// TODO Auto-generated method stub
return null;
}

View File

@ -30,8 +30,6 @@ import java.util.Map;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.storage.VolumeApiService;
import com.cloud.utils.PasswordGenerator;
import org.apache.cloudstack.agent.lb.IndirectAgentLB;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.context.CallContext;
@ -50,6 +48,8 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.AgentManager;
@ -112,6 +112,7 @@ import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.ImageStoreDetailsUtil;
import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.dao.SnapshotDao;
import com.cloud.storage.dao.StoragePoolHostDao;
import com.cloud.storage.dao.UploadDao;
@ -127,6 +128,7 @@ import com.cloud.user.AccountService;
import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.PasswordGenerator;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.QueryBuilder;
@ -150,8 +152,6 @@ import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.dao.SecondaryStorageVmDao;
import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
/**
* Class to manage secondary storages. <br><br>
@ -1307,15 +1307,11 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
@Override
public Long[] getScannablePools() {
List<DataCenterVO> zones = _dcDao.listEnabledZones();
Long[] dcIdList = new Long[zones.size()];
int i = 0;
for (DataCenterVO dc : zones) {
dcIdList[i++] = dc.getId();
List<Long> zoneIds = _dcDao.listEnabledNonEdgeZoneIds();
if (s_logger.isDebugEnabled()) {
s_logger.debug(String.format("Enabled non-edge zones available for scan: %s", StringUtils.join(zoneIds, ",")));
}
return dcIdList;
return zoneIds.toArray(Long[]::new);
}
@Override

View File

@ -17,7 +17,17 @@
package org.apache.cloudstack.secondarystorage;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
@ -27,8 +37,8 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.network.Networks.TrafficType;
@ -37,16 +47,6 @@ import com.cloud.network.dao.NetworkVO;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicProfile;
import com.cloud.vm.VirtualMachineProfile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.when;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.Mockito.eq;
public class SecondaryStorageManagerTest {
@Mock
@ -218,4 +218,22 @@ public class SecondaryStorageManagerTest {
Assert.assertEquals(expectedResult, result);
}
}
private void verifyScannablePoolsZoneIds(List<Long> expected, Long[] result) {
Assert.assertNotNull(result);
Assert.assertEquals(expected.size(), result.length);
for (int i = 0; i < expected.size(); ++i) {
Assert.assertEquals(expected.get(i), result[i]);
}
}
@Test
public void testGetScannablePools() {
List<Long> dbZoneIds = new ArrayList<>();
Mockito.when(_dcDao.listEnabledNonEdgeZoneIds()).thenReturn(dbZoneIds);
verifyScannablePoolsZoneIds(dbZoneIds, _ssMgr.getScannablePools());
dbZoneIds = Arrays.asList(1L, 2L, 3L);
Mockito.when(_dcDao.listEnabledNonEdgeZoneIds()).thenReturn(dbZoneIds);
verifyScannablePoolsZoneIds(dbZoneIds, _ssMgr.getScannablePools());
}
}

View File

@ -0,0 +1,133 @@
# 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.
""" BVT tests for supported VM and network operations on an Edge zone
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.cloudstackAPI import (attachVolume,
detachVolume,
deleteVolume,
attachIso,
detachIso,
deleteIso,
startVirtualMachine,
stopVirtualMachine,
migrateVirtualMachineWithVolume)
from marvin.lib.utils import (cleanup_resources)
from marvin.lib.base import (Account,
Host,
Pod,
StoragePool,
ServiceOffering,
DiskOffering,
VirtualMachine,
Iso,
Volume)
from marvin.lib.common import (get_domain,
get_zone,
get_template)
from marvin.lib.decoratorGenerators import skipTestIf
from marvin.codes import FAILED, PASS
from nose.plugins.attrib import attr
# Import System modules
import time
_multiprocess_shared_ = True
class TestEdgeZoneSupportedOperations(cloudstackTestCase):
@classmethod
def setUpClass(cls):
testClient = super(TestVMMigration, cls).getClsTestClient()
cls.apiclient = testClient.getApiClient()
cls.services = testClient.getParsedTestDataConfig()
cls.domain = get_domain(cls.apiclient)
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
cls.services['mode'] = cls.zone.networktype
cls.hypervisor = cls.testClient.getHypervisorInfo()
cls.cleanup = []
cls.testsNotSupported = False
if cls.zone.type != 'Edge':
cls.testsNotSupported = True
return
cls.services["test_templates"]["kvm"]["directdownload"] = "true"
cls.template = Template.register(cls.apiclient, cls.services["test_templates"]["kvm"],
zoneid=cls.zone.id, hypervisor=cls.hypervisor)
cls.cleanup.append(cls.template)
cls.services["virtual_machine"]["zoneid"] = cls.zone.id
cls.services["virtual_machine"]["template"] = cls.template.id
cls.services["virtual_machine"]["hypervisor"] = cls.hypervisor
cls.services["service_offerings"]["tiny"]["storagetype"] = "local"
cls.service_offering = ServiceOffering.create(
cls.apiclient,
cls.services["service_offerings"]["tiny"]
)
cls.cleanup.append(cls.service_offering)
cls.l2_network_offering = NetworkOffering.create(
cls.apiclient,
cls.services["l2-l2_network_offering"],
)
cls.cleanup.append(cls.l2_network_offering)
cls.l2_network_offering.update(cls.apiclient, state='Enabled')
cls.domain = get_domain(cls.apiclient)
cls.account = Account.create(
cls.apiclient,
cls.services["account"],
admin=False,
domainid=cls.domain.id
)
cls.cleanup.append(cls.account)
@classmethod
def tearDownClass(cls):
super(TestMetrics, cls).tearDownClass()
def setUp(self):
self.userapiclient = self.testClient.getUserApiClient(
UserName=self.account.name,
DomainName=self.account.domain
)
self.cleanup = []
def tearDown(self):
super(TestMetrics, self).tearDown()
@skipTestIf("testsNotSupported")
@attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false")
def test_01_deploy_vm_l2network(self):
"""Test to deploy VM in a L2 network
"""
network = Network.create(
self.apiclient,
self.services["l2-network"],
zoneid=self.zone.id,
networkofferingid=self.l2_network_offering.id
)
self.cleanup.append(network)
vm = VirtualMachine.create(
self.apiclient,
self.services["virtual_machine"],
serviceofferingid=self.service_offering.id
networkids=network.id
)
self.cleanup.append(vm)
self.assertEqual(
vm.state,
"Running",
"Check VM deployed in edge zone with a L2 network is running"
)

View File

@ -14,7 +14,7 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
""" BVT tests for Virtual Machine Life Cycle
""" BVT tests for Virtual Machine migration across pods
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase

View File

@ -451,6 +451,8 @@
"label.copy": "Copy",
"label.copy.clipboard": "Copy to clipboard",
"label.copyid": "Copy ID",
"label.core": "Core",
"label.core.zone.type": "Core zone type",
"label.counter": "Counter",
"label.counter.name": "Name of the counter for which the policy will be evaluated",
"label.cpu": "CPU",
@ -666,6 +668,15 @@
"label.duration.7days": "7 days",
"label.dynamicscalingenabled": "Dynamic scaling enabled",
"label.dynamicscalingenabled.tooltip": "VM can dynamically scale only when dynamic scaling is enabled on template, service offering and global setting.",
<<<<<<< HEAD
"label.diskofferingstrictness": "Disk offering strictness",
"label.disksizestrictness": "Disk size strictness",
"label.computeonly.offering": "Compute only disk offering",
"label.computeonly.offering.tooltip": "Option to specify root disk related information in the compute offering or to directly link a disk offering to the compute offering",
"label.edge": "Edge",
"label.edge.zone": "Edge Zone",
=======
>>>>>>> main
"label.edit": "Edit",
"label.edit.acl.list": "Edit ACL list",
"label.edit.acl.rule": "Edit ACL rule",
@ -2260,6 +2271,8 @@
"message.deployasis": "Selected template is Deploy As-Is i.e., the VM is deployed by importing an OVA with vApps directly into vCenter. Root disk(s) resize is allowed only on stopped VMs for such templates.",
"message.desc.advanced.zone": "This is recommended and allows more sophisticated network topologies. This network model provides the most flexibility in defining guest networks and providing custom network offerings such as firewall, VPN, or load balancer support.",
"message.desc.basic.zone": "Provide a single network where each VM instance is assigned an IP directly from the network. Guest isolation can be provided through layer-3 means such as security groups (IP address source filtering).",
"message.desc.core.zone": "Core Zones are intended for Datacenter based deployments and allow the full range of networking and other functionality in Apache CloudStack. Core zones have a number of prerequisites and rely on the presence of shared storage and helper VMs.",
"message.desc.edge.zone": "Edge Zones are lightweight zones, designed for deploying in edge computing scenarios. They are limited in functionality but have far fewer prerequisites than core zones.<br><br>Please refer to the Apache CloudStack documentation for more information on Zone Types<br><a href='http://docs.cloudstack.apache.org/en/latest/installguide/configuration.html#adding-a-zone'>http://docs.cloudstack.apache.org/en/latest/installguide/configuration.html#adding-a-zone</a>",
"message.desc.cluster": "Each pod must contain one or more clusters. We will add the first cluster now. A cluster provides a way to group hosts. The hosts in a cluster all have identical hardware, run the same hypervisor, are on the same subnet, and access the same shared storage. Each cluster consists of one or more hosts and one or more primary storage servers.",
"message.desc.create.ssh.key.pair": "Please fill in the following data to create or register a ssh key pair.<br><br>(1) If public key is set, CloudStack will register the public key. You can use it through your private key.<br><br>(2) If public key is not set, CloudStack will create a new SSH key pair. In this case, please copy and save the private key. CloudStack will not keep it.<br>",
"message.desc.created.ssh.key.pair": "Created a SSH key pair.",
@ -2271,6 +2284,8 @@
"message.desc.register.user.data": "Please fill in the following data to register a user data.",
"message.desc.registered.user.data": "Registered a User Data.",
"message.desc.zone": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.",
"message.desc.zone.edge": "A zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. An edge zone consists of one or more hosts (each of which provides local storage as primary storage servers). Only shared and L2 networks can be deployed in such zones and functionalities that require secondary storages are not supported.",
"message.zone.edge.local.storage": "Local storage will be used by default for user VMs and virtual routers",
"message.detach.disk": "Are you sure you want to detach this disk?",
"message.detach.iso.confirm": "Please confirm that you want to detach the ISO from this virtual instance.",
"message.disable.account": "Please confirm that you want to disable this account. By disabling the account, all users for this account will no longer have access to their cloud resources. All running virtual machines will be immediately shut down.",

View File

@ -631,7 +631,7 @@
<a-divider/>
<div v-for="item in $route.meta.related" :key="item.path">
<router-link
v-if="$router.resolve('/' + item.name).matched[0].redirect !== '/exception/404'"
v-if="(item.show === undefined || item.show(resource)) && $router.resolve('/' + item.name).matched[0].redirect !== '/exception/404'"
:to="{ name: item.name, query: getRouterQuery(item) }">
<a-button style="margin-right: 10px">
<template #icon>

View File

@ -24,7 +24,7 @@ export default {
icon: 'global-outlined',
permission: ['listZonesMetrics'],
columns: () => {
const fields = ['name', 'allocationstate', 'networktype', 'clusters']
const fields = ['name', 'allocationstate', 'type', 'networktype', 'clusters']
const metricsFields = ['cpuused', 'cpumaxdeviation', 'cpuallocated', 'cputotal', 'memoryused', 'memorymaxdeviation', 'memoryallocated', 'memorytotal']
if (store.getters.metrics) {
fields.push(...metricsFields)
@ -32,15 +32,21 @@ export default {
fields.push('order')
return fields
},
details: ['name', 'id', 'allocationstate', 'networktype', 'guestcidraddress', 'localstorageenabled', 'securitygroupsenabled', 'dns1', 'dns2', 'internaldns1', 'internaldns2'],
details: ['name', 'id', 'allocationstate', 'type', 'networktype', 'guestcidraddress', 'localstorageenabled', 'securitygroupsenabled', 'dns1', 'dns2', 'internaldns1', 'internaldns2'],
related: [{
name: 'pod',
title: 'label.pods',
param: 'zoneid'
param: 'zoneid',
show: (record) => {
return record.type !== 'Edge'
}
}, {
name: 'cluster',
title: 'label.clusters',
param: 'zoneid'
param: 'zoneid',
show: (record) => {
return record.type !== 'Edge'
}
}, {
name: 'host',
title: 'label.hosts',
@ -52,7 +58,10 @@ export default {
}, {
name: 'imagestore',
title: 'label.secondary.storage',
param: 'zoneid'
param: 'zoneid',
show: (record) => {
return record.type !== 'Edge'
}
}],
resourceType: 'Zone',
tabs: [{
@ -63,7 +72,8 @@ export default {
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/PhysicalNetworksTab.vue')))
}, {
name: 'system.vms',
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/SystemVmsTab.vue')))
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/zone/SystemVmsTab.vue'))),
show: (record) => { return record.isEdge !== true }
}, {
name: 'resources',
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/Resources.vue')))

View File

@ -343,6 +343,7 @@ export default {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
}
}).finally(() => {
this.zoneLoading = false

View File

@ -195,6 +195,7 @@ export default {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
}
}).finally(() => {
this.zoneLoading = false

View File

@ -283,6 +283,7 @@ export default {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = this.zones.concat(listZones)
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
}
}).finally(() => {
this.zoneLoading = false

View File

@ -107,7 +107,7 @@
@change="handlerSelectZone"
:placeholder="apiParams.zoneid.description"
:loading="zones.loading">
<a-select-option :value="zone.id" v-for="zone in zones.opts" :key="zone.id" :label="zone.name || zone.description">
<a-select-option :value="zone.id" v-for="zone in filteredZones" :key="zone.id" :label="zone.name || zone.description">
<span>
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
@ -421,6 +421,13 @@ export default {
computed: {
isAdminRole () {
return this.$store.getters.userInfo.roletype === 'Admin'
},
filteredZones () {
let zoneList = this.zones.opts
if (zoneList && zoneList.length > 0 && this.currentForm === 'Upload') {
zoneList = zoneList.filter(zone => zone.type !== 'Edge')
}
return zoneList
}
},
methods: {
@ -532,9 +539,14 @@ export default {
listZones = listZones.concat(listZonesResponse)
this.zones.opts = listZones
}).finally(() => {
this.form.zoneid = (this.zones.opts && this.zones.opts[1]) ? this.zones.opts[1].id : ''
this.form.zoneid = (this.filteredZones && this.filteredZones[1]) ? this.filteredZones[1].id : ''
if (!this.form.zoneid) {
this.form.zoneid = (this.filteredZones && this.filteredZones[0] && this.filteredZones[0].id !== this.$t('label.all.zone')) ? this.filteredZones[0].id : ''
}
this.zones.loading = false
if (this.form.zoneid) {
this.fetchHyperVisor({ zoneid: this.form.zoneid })
}
})
},
fetchHyperVisor (params) {
@ -548,7 +560,7 @@ export default {
}
if (this.currentForm !== 'Upload') {
listhyperVisors.push({
name: 'Any'
name: 'Simulator'
})
}
this.hyperVisor.opts = listhyperVisors

View File

@ -23,7 +23,7 @@
size="small"
:current="currentStep">
<a-step
v-for="(item, index) in steps"
v-for="(item, index) in zoneSteps"
:key="item.title"
:title="$t(item.title)"
:ref="`step${index}`">
@ -31,13 +31,21 @@
</a-steps>
<div>
<zone-wizard-zone-type-step
v-if="currentStep === 0"
v-if="zoneSteps[currentStep].name === 'type'"
@nextPressed="nextPressed"
@fieldsChanged="onFieldsChanged"
:prefillContent="zoneConfig"
/>
<zone-wizard-core-zone-type-step
v-else-if="zoneSteps[currentStep].name === 'coreType'"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
:isFixError="stepFixError"
:prefillContent="zoneConfig"
/>
<zone-wizard-zone-details-step
v-else-if="currentStep === 1"
v-else-if="zoneSteps[currentStep].name === 'details'"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@ -46,7 +54,7 @@
:prefillContent="zoneConfig"
/>
<zone-wizard-network-setup-step
v-else-if="currentStep === 2"
v-else-if="zoneSteps[currentStep].name === 'network'"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@ -56,7 +64,7 @@
:prefillContent="zoneConfig"
/>
<zone-wizard-add-resources
v-else-if="currentStep === 3"
v-else-if="zoneSteps[currentStep].name === 'resources'"
@nextPressed="nextPressed"
@backPressed="backPressed"
@fieldsChanged="onFieldsChanged"
@ -83,6 +91,7 @@
<script>
import { mixinDevice } from '@/utils/mixin.js'
import ZoneWizardZoneTypeStep from '@views/infra/zone/ZoneWizardZoneTypeStep'
import ZoneWizardCoreZoneTypeStep from '@views/infra/zone/ZoneWizardCoreZoneTypeStep'
import ZoneWizardZoneDetailsStep from '@views/infra/zone/ZoneWizardZoneDetailsStep'
import ZoneWizardNetworkSetupStep from '@views/infra/zone/ZoneWizardNetworkSetupStep'
import ZoneWizardAddResources from '@views/infra/zone/ZoneWizardAddResources'
@ -91,6 +100,7 @@ import ZoneWizardLaunchZone from '@views/infra/zone/ZoneWizardLaunchZone'
export default {
components: {
ZoneWizardZoneTypeStep,
ZoneWizardCoreZoneTypeStep,
ZoneWizardZoneDetailsStep,
ZoneWizardNetworkSetupStep,
ZoneWizardAddResources,
@ -104,32 +114,44 @@ export default {
launchZone: false,
launchData: {},
stepChild: '',
coreZoneTypeStep: {
name: 'coreType',
title: 'label.core.zone.type',
step: [],
description: this.$t('message.select.zone.description'),
hint: this.$t('message.select.zone.hint')
},
steps: [
{
name: 'type',
title: 'label.zone.type',
step: [],
description: this.$t('message.select.zone.description'),
hint: this.$t('message.select.zone.hint')
},
{
name: 'details',
title: 'label.zone.details',
step: ['stepAddZone', 'dedicateZone'],
description: this.$t('message.zone.detail.description'),
hint: this.$t('message.zone.detail.hint')
},
{
name: 'network',
title: 'label.network',
step: ['physicalNetwork', 'netscaler', 'pod', 'guestTraffic', 'storageTraffic', 'publicTraffic'],
description: this.$t('message.network.description'),
hint: this.$t('message.network.hint')
},
{
name: 'resources',
title: 'label.add.resources',
step: ['clusterResource', 'hostResource', 'primaryResource', 'secondaryResource'],
description: this.$t('message.add.resource.description'),
hint: this.$t('message.add.resource.hint')
},
{
name: 'launch',
title: 'label.launch',
step: ['launchZone'],
description: this.$t('message.launch.zone.description'),
@ -139,6 +161,15 @@ export default {
zoneConfig: {}
}
},
computed: {
zoneSteps () {
var steps = [...this.steps]
if (this.zoneConfig.zoneSuperType !== 'Edge') {
steps.splice(1, 0, this.coreZoneTypeStep)
}
return steps
}
},
methods: {
nextPressed () {
this.currentStep++

View File

@ -18,6 +18,7 @@
<template>
<div style="width: auto;">
<a-steps
v-if="steps.length > 1"
ref="resourceStep"
progressDot
:current="currentStep"
@ -54,7 +55,7 @@
:isFixError="isFixError"
/>
<static-inputs-form
v-if="(!localstorageenabled || !localstorageenabledforsystemvm) && checkVisibleResource('primaryResource')"
v-if="!isEdgeZone && (!localstorageenabled || !localstorageenabledforsystemvm) && checkVisibleResource('primaryResource')"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@ -65,7 +66,7 @@
:isFixError="isFixError"
/>
<static-inputs-form
v-if="checkVisibleResource('secondaryResource')"
v-if="!isEdgeZone && checkVisibleResource('secondaryResource')"
@nextPressed="nextPressed"
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@ -76,7 +77,7 @@
:isFixError="isFixError"
/>
</div>
<div v-else>
<div v-else-if="!isEdgeZone">
<static-inputs-form
v-if="checkVisibleResource('primaryResource')"
@nextPressed="nextPressed"
@ -133,6 +134,9 @@ export default {
zoneType () {
return this.prefillContent?.zoneType || null
},
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
hypervisor () {
return this.prefillContent?.hypervisor || null
},
@ -142,14 +146,19 @@ export default {
localstorageenabledforsystemvm () {
return this.prefillContent?.localstorageenabledforsystemvm || false
},
isEdgeZone () {
return this.prefillContent?.zoneSuperType === 'Edge' || false
},
steps () {
const steps = []
const hypervisor = this.prefillContent.hypervisor ? this.prefillContent.hypervisor : null
if (!this.isEdgeZone) {
steps.push({
title: 'label.cluster',
fromKey: 'clusterResource',
description: 'message.desc.cluster'
})
}
if (hypervisor !== 'VMware') {
steps.push({
title: 'label.host',
@ -157,6 +166,7 @@ export default {
description: 'message.desc.host'
})
}
if (!this.isEdgeZone) {
if (!this.localstorageenabled || !this.localstorageenabledforsystemvm) {
steps.push({
title: 'label.primary.storage',
@ -169,6 +179,7 @@ export default {
fromKey: 'secondaryResource',
description: 'message.desc.secondary.storage'
})
}
return steps
},

View File

@ -0,0 +1,194 @@
// 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.
<template>
<div>
<a-form
class="form-content"
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
v-ctrl-enter="handleSubmit"
>
<a-form-item name="zoneType" ref="zoneType">
<a-radio-group v-model:value="form.zoneType">
<a-card class="card-item">
<a-row :gutter="12">
<a-col :md="6" :lg="6">
<a-radio class="card-form-item" value="Advanced" v-if="$config.basicZoneEnabled">{{ $t('label.advanced') }}</a-radio>
<span style="margin-top: 20px;" class="card-form-item" v-else>
<setting-outlined style="margin-right: 10px" />
{{ $t('label.advanced') }}
</span>
</a-col>
<a-col :md="18" :lg="18">
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Advanced) }}</a-card>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="6" :lg="6" style="margin-top: 15px">
<a-form-item
name="securityGroupsEnabled"
ref="securityGroupsEnabled"
class="card-form-item"
v-bind="formItemLayout">
<a-switch
class="card-form-item"
v-model:checked="form.securityGroupsEnabled"
:disabled="!isAdvancedZone"
v-focus="true"
/>
</a-form-item>
<span>{{ $t('label.menu.security.groups') }}</span>
</a-col>
<a-col :md="18" :lg="18" style="margin-top: 15px;">
<a-card class="zone-support">{{ $t(zoneDescription.SecurityGroups) }}</a-card>
</a-col>
</a-row>
</a-card>
<a-card class="card-item" v-if="$config.basicZoneEnabled">
<a-row :gutter="12">
<a-col :md="6" :lg="6">
<a-radio class="card-form-item" value="Basic">{{ $t('label.basic') }}</a-radio>
</a-col>
<a-col :md="18" :lg="18">
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Basic) }}</a-card>
</a-col>
</a-row>
</a-card>
</a-radio-group>
</a-form-item>
</a-form>
<div class="form-action">
<a-button
@click="handleBack"
class="button-back"
v-if="!isFixError">
{{ $t('label.previous') }}
</a-button>
<a-button ref="submit" type="primary" @click="handleSubmit" class="button-next">
{{ $t('label.next') }}
</a-button>
</div>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
export default {
props: {
prefillContent: {
type: Object,
default: function () {
return {}
}
},
isFixError: {
type: Boolean,
default: false
}
},
data: () => ({
formItemLayout: {
labelCol: { span: 6 },
wrapperCol: { span: 14 }
},
zoneDescription: {
Basic: 'message.desc.basic.zone',
Advanced: 'message.desc.advanced.zone',
SecurityGroups: 'message.advanced.security.group'
},
formModel: {}
}),
created () {
this.initForm()
},
watch: {
formModel: {
deep: true,
handler (changedFields) {
const fieldsChanged = toRaw(changedFields)
this.$emit('fieldsChanged', fieldsChanged)
}
}
},
computed: {
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType : 'Advanced'
},
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent?.securityGroupsEnabled || false)
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
zoneType: this.zoneType,
securityGroupsEnabled: this.securityGroupsEnabled
})
this.rules = reactive({
zoneType: [{ required: true, message: this.$t('message.error.zone.type') }]
})
this.formModel = toRaw(this.form)
},
handleBack () {
this.$emit('backPressed')
},
handleSubmit () {
this.formRef.value.validate().then(() => {
this.$emit('nextPressed')
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
}
}
}
</script>
<style scoped lang="less">
.form-content {
border: 1px dashed #e9e9e9;
border-radius: 6px;
min-height: 200px;
text-align: center;
vertical-align: center;
padding: 8px;
padding-top: 16px;
margin-top: 8px;
}
.card-item {
margin-top: 10px;
.card-form-item {
float: left;
}
.checkbox-advance {
margin-top: 10px;
}
.zone-support {
text-align: justify;
}
}
</style>

View File

@ -161,6 +161,9 @@ export default {
sgEnabled () {
return this.prefillContent?.securityGroupsEnabled || false
},
isEdgeZone () {
return this.prefillContent?.zoneSuperType === 'Edge' || false
},
havingNetscaler () {
return this.prefillContent?.networkOfferingSelected?.havingNetscaler || false
},
@ -189,7 +192,8 @@ export default {
this.stepData.tasks = []
this.stepData.stepMove = this.stepData.stepMove.filter(item => item.indexOf('createStorageNetworkIpRange') === -1)
}
this.handleSubmit()
console.log('step-data', this.stepData)
// this.handleSubmit()
}
},
methods: {
@ -333,6 +337,7 @@ export default {
params.internaldns1 = this.prefillContent?.internalDns1 || null
params.internaldns2 = this.prefillContent?.internalDns2 || null
params.domain = this.prefillContent?.networkDomain || null
params.isedge = this.prefillContent?.zoneSuperType === 'Edge' || false
try {
if (!this.stepData.stepMove.includes('createZone')) {
@ -617,11 +622,11 @@ export default {
try {
// Advanced SG-disabled zone
if (!this.sgEnabled) {
if (!this.sgEnabled && !this.isEdgeZone) {
// ***** VPC Virtual Router ***** (begin) *****
await this.configVpcVirtualRouter(physicalNetwork)
// ***** VPC Virtual Router ***** (end) *****
} else {
} else if (this.sgEnabled && !this.isEdgeZone) {
this.stepData.physicalNetworkReturned = physicalNetwork
await this.stepEnableSecurityGroupProvider()
}
@ -643,7 +648,7 @@ export default {
}
},
async configOvs (physicalNetwork) {
if (this.stepData.stepMove.includes('configOvs' + physicalNetwork.id)) {
if (this.isEdgeZone || this.stepData.stepMove.includes('configOvs' + physicalNetwork.id)) {
return
}
@ -663,7 +668,7 @@ export default {
this.stepData.stepMove.push('configOvs' + physicalNetwork.id)
},
async configInternalLBVM (physicalNetwork) {
if (this.stepData.stepMove.includes('configInternalLBVM' + physicalNetwork.id)) {
if (this.isEdgeZone || this.stepData.stepMove.includes('configInternalLBVM' + physicalNetwork.id)) {
return
}
@ -816,7 +821,7 @@ export default {
const params = {}
params.zoneId = this.stepData.zoneReturned.id
params.name = this.prefillContent?.podName || null
params.name = this.prefillContent?.podName || this.stepData.zoneReturned.type === 'Edge' ? 'Pod-' + this.stepData.zoneReturned.name : null
params.gateway = this.prefillContent?.podReservedGateway || null
params.netmask = this.prefillContent?.podReservedNetmask || null
params.startIp = this.prefillContent?.podReservedStartIp || null
@ -869,7 +874,7 @@ export default {
if (
(this.isBasicZone &&
(this.havingSG && this.havingEIP && this.havingELB)) ||
(this.isAdvancedZone && !this.sgEnabled)) {
(this.isAdvancedZone && !this.sgEnabled && !this.isEdgeZone)) {
this.setStepStatus(STATUS_FINISH)
this.currentStep++
this.addStep('message.configuring.public.traffic', 'publicTraffic')
@ -1141,7 +1146,7 @@ export default {
}
params.clustertype = clusterType
params.podId = this.stepData.podReturned.id
let clusterName = this.prefillContent.clusterName
let clusterName = this.prefillContent.clusterName || this.stepData.zoneReturned.type === 'Edge' ? 'Cluster-' + this.stepData.zoneReturned.name : null
if (hypervisor === 'VMware') {
params.username = this.prefillContent?.vCenterUsername || null

View File

@ -68,7 +68,7 @@
@backPressed="handleBack"
@fieldsChanged="fieldsChanged"
@submitLaunchZone="submitLaunchZone"
:fields="podFields"
:fields="filteredPodFields"
:prefillContent="prefillContent"
:description="podSetupDescription"
:isFixError="isFixError"
@ -151,12 +151,18 @@ export default {
zoneType () {
return this.prefillContent?.zoneType || null
},
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
sgEnabled () {
return this.prefillContent?.securityGroupsEnabled || false
},
havingNetscaler () {
return this.prefillContent?.networkOfferingSelected?.havingNetscaler || false
},
isEdgeZone () {
return this.prefillContent?.zoneSuperType === 'Edge' || false
},
guestTrafficRangeMode () {
return this.zoneType === 'Basic' ||
(this.zoneType === 'Advanced' && this.sgEnabled)
@ -311,6 +317,14 @@ export default {
})
}
return fields
},
filteredPodFields () {
var fields = [...this.podFields]
if (this.isEdgeZone) {
fields = fields.filter(x => !['podReservedGateway', 'podReservedNetmask', 'podReservedStartIp', 'podReservedStopIp'].includes(x.key))
return fields
}
return fields
}
},
@ -363,7 +377,7 @@ export default {
title: 'label.end.reserved.system.ip',
key: 'podReservedStopIp',
placeHolder: 'message.installwizard.tooltip.addpod.reservedsystemendip',
required: false,
required: true,
ipV4: true,
message: 'message.error.ipv4.address'
}
@ -449,6 +463,7 @@ export default {
},
filteredSteps () {
return this.allSteps.filter(step => {
if (step.formKey === 'pod' && this.isEdgeZone) return false
if (!step.trafficType) return true
if (this.physicalNetworks) {
let neededTraffic = false

View File

@ -305,6 +305,9 @@ export default {
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent?.securityGroupsEnabled || false)
},
isEdgeZone () {
return this.prefillContent?.zoneSuperType === 'Edge' || false
},
networkOfferingSelected () {
return this.prefillContent.networkOfferingSelected
},
@ -312,11 +315,17 @@ export default {
if (!this.isAdvancedZone) { // Basic zone
return (this.networkOfferingSelected && (this.networkOfferingSelected.havingEIP || this.networkOfferingSelected.havingELB))
} else {
return !this.securityGroupsEnabled
return !this.securityGroupsEnabled && !this.isEdgeZone
}
},
needsManagementTraffic () {
return !this.isEdgeZone
},
requiredTrafficTypes () {
const traffics = ['management', 'guest']
const traffics = ['guest']
if (this.needsManagementTraffic) {
traffics.push('management')
}
if (this.needsPublicTraffic) {
traffics.push('public')
}

View File

@ -39,6 +39,7 @@
v-focus="true"
/>
</a-form-item>
<div v-if="!this.isEdgeZone">
<a-form-item
name="ipv4Dns1"
ref="ipv4Dns1"
@ -107,6 +108,7 @@
has-feedback>
<a-input v-model:value="form.internalDns2" />
</a-form-item>
</div>
<a-form-item
name="hypervisor"
ref="hypervisor"
@ -117,6 +119,7 @@
v-model:value="form.hypervisor"
:placeholder="$t('message.error.hypervisor.type')"
:loading="hypervisors === null"
:disabled="this.isEdgeZone"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
@ -127,6 +130,7 @@
</a-select-option>
</a-select>
</a-form-item>
<div v-if="!this.isEdgeZone">
<a-form-item
name="networkOfferingId"
ref="networkOfferingId"
@ -167,6 +171,7 @@
has-feedback>
<a-input v-model:value="form.guestcidraddress" />
</a-form-item>
</div>
<a-form-item
name="isDedicated"
ref="isDedicated"
@ -195,6 +200,12 @@
</a-select-option>
</a-select>
</a-form-item>
<a-alert style="margin-top: 5px" type="warning" v-if="this.isEdgeZone">
<template #message>
<span v-html="$t('message.zone.edge.local.storage')" />
</template>
</a-alert>
<div v-else>
<a-form-item
name="account"
ref="account"
@ -217,6 +228,7 @@
v-bind="formItemLayout">
<a-switch v-model:checked="form.localstorageenabledforsystemvm" />
</a-form-item>
</div>
</a-form>
<div class="form-action">
<a-button
@ -251,7 +263,6 @@ export default {
}
},
data: () => ({
description: 'message.desc.zone',
formItemLayout: {
labelCol: { span: 8 },
wrapperCol: { span: 12 }
@ -328,10 +339,19 @@ export default {
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent?.securityGroupsEnabled || false)
},
isEdgeZone () {
return this.prefillContent?.zoneSuperType === 'Edge' || false
},
description () {
return this.isEdgeZone ? 'message.desc.zone.edge' : 'message.desc.zone'
},
name () {
return this.prefillContent?.name || null
},
ipv4Dns1 () {
if (this.isEdgeZone) {
return '8.8.8.8'
}
return this.prefillContent?.ipv4Dns1 || null
},
ipv4Dns2 () {
@ -344,6 +364,9 @@ export default {
return this.prefillContent?.ipv6Dns2 || null
},
internalDns1 () {
if (this.isEdgeZone) {
return '8.8.8.8'
}
return this.prefillContent?.internalDns1 || null
},
internalDns2 () {
@ -356,6 +379,9 @@ export default {
return this.prefillContent?.ip6gateway || null
},
currentHypervisor () {
if (this.isEdgeZone) {
return 'KVM'
}
if (this.prefillContent.hypervisor) {
return this.prefillContent.hypervisor
} else if (this.hypervisors && this.hypervisors.length > 0) {
@ -393,9 +419,15 @@ export default {
return this.prefillContent?.account || null
},
localstorageenabled () {
if (this.isEdgeZone) {
return true
}
return this.prefillContent?.localstorageenabled || false
},
localstorageenabledforsystemvm () {
if (this.isEdgeZone) {
return true
}
return this.prefillContent?.localstorageenabledforsystemvm || false
}
},

View File

@ -25,49 +25,25 @@
@finish="handleSubmit"
v-ctrl-enter="handleSubmit"
>
<a-form-item name="zoneType" ref="zoneType">
<a-radio-group v-model:value="form.zoneType">
<a-form-item name="zoneSuperType" ref="zoneSuperType">
<a-radio-group v-model:value="form.zoneSuperType">
<a-card class="card-item">
<a-row :gutter="12">
<a-col :md="6" :lg="6">
<a-radio class="card-form-item" value="Advanced" v-if="$config.basicZoneEnabled">{{ $t('label.advanced') }}</a-radio>
<span style="margin-top: 20px;" class="card-form-item" v-else>
<setting-outlined style="margin-right: 10px" />
{{ $t('label.advanced') }}
</span>
<a-radio class="card-form-item" value="Core">{{ $t('label.core') }}</a-radio>
</a-col>
<a-col :md="18" :lg="18">
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Advanced) }}</a-card>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :md="6" :lg="6" style="margin-top: 15px">
<a-form-item
name="securityGroupsEnabled"
ref="securityGroupsEnabled"
class="card-form-item"
v-bind="formItemLayout">
<a-switch
class="card-form-item"
v-model:checked="form.securityGroupsEnabled"
:disabled="!isAdvancedZone"
v-focus="true"
/>
</a-form-item>
<span>{{ $t('label.menu.security.groups') }}</span>
</a-col>
<a-col :md="18" :lg="18" style="margin-top: 15px;">
<a-card class="zone-support">{{ $t(zoneDescription.SecurityGroups) }}</a-card>
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Core) }}</a-card>
</a-col>
</a-row>
</a-card>
<a-card class="card-item" v-if="$config.basicZoneEnabled">
<a-card class="card-item">
<a-row :gutter="12">
<a-col :md="6" :lg="6">
<a-radio class="card-form-item" value="Basic">{{ $t('label.basic') }}</a-radio>
<a-radio class="card-form-item" value="Edge">{{ $t('label.edge') }}</a-radio>
</a-col>
<a-col :md="18" :lg="18">
<a-card class="ant-form-text zone-support">{{ $t(zoneDescription.Basic) }}</a-card>
<a-card class="ant-form-text zone-support"><span v-html="$t(zoneDescription.Edge)"></span></a-card>
</a-col>
</a-row>
</a-card>
@ -100,9 +76,8 @@ export default {
wrapperCol: { span: 14 }
},
zoneDescription: {
Basic: 'message.desc.basic.zone',
Advanced: 'message.desc.advanced.zone',
SecurityGroups: 'message.advanced.security.group'
Core: 'message.desc.core.zone',
Edge: 'message.desc.edge.zone'
},
formModel: {}
}),
@ -119,29 +94,25 @@ export default {
}
},
computed: {
isAdvancedZone () {
return this.zoneType === 'Advanced'
},
zoneType () {
return this.prefillContent.zoneType ? this.prefillContent.zoneType : 'Advanced'
},
securityGroupsEnabled () {
return this.isAdvancedZone && (this.prefillContent?.securityGroupsEnabled || false)
zoneSuperType () {
return this.prefillContent.zoneSuperType ? this.prefillContent.zoneSuperType : 'Core'
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
zoneType: this.zoneType,
securityGroupsEnabled: this.securityGroupsEnabled
zoneSuperType: this.zoneSuperType
})
this.rules = reactive({
zoneType: [{ required: true, message: this.$t('message.error.zone.type') }]
zoneSuperType: [{ required: true, message: this.$t('message.error.zone.type') }]
})
this.formModel = toRaw(this.form)
},
handleSubmit () {
if (this.form.zoneSuperType === 'Edge') {
this.form.zoneType = 'Advanced'
}
this.formRef.value.validate().then(() => {
this.$emit('nextPressed')
}).catch(error => {
@ -168,6 +139,8 @@ export default {
.card-form-item {
float: left;
font-weight: bold;
font-size: 15px;
}
.checkbox-advance {

View File

@ -430,7 +430,7 @@ export default {
api('listZones', params).then(json => {
for (const i in json.listzonesresponse.zone) {
const zone = json.listzonesresponse.zone[i]
if (zone.networktype === 'Advanced' && zone.securitygroupsenabled !== true) {
if (zone.networktype === 'Advanced' && zone.securitygroupsenabled !== true && zone.type !== 'Edge') {
this.zones.push(zone)
}
}

View File

@ -221,6 +221,7 @@ export default {
api('listZones', { showicon: true }).then(json => {
if (json && json.listzonesresponse && json.listzonesresponse.zone) {
this.zones = json.listzonesresponse.zone
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
if (this.zones.length > 0) {
this.onZoneChange(this.zones[0].id)
}

View File

@ -210,8 +210,9 @@ export default {
this.loading = true
api('listZones', { showicon: true }).then(json => {
this.zones = json.listzonesresponse.zone || []
this.selectedZoneId = this.zones[0].id || ''
this.fetchDiskOfferings(this.selectedZoneId)
this.zones = this.zones.filter(zone => zone.type !== 'Edge')
this.form.zoneId = this.zones[0].id || ''
this.fetchDiskOfferings(this.form.zoneId)
}).finally(() => {
this.loading = false
})