blob: c003bd05a5189487ccb3408e73ecdfdbacd6365f [file] [log] [blame]
// 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 com.cloud.bgp;
import com.cloud.dc.ASNumberRangeVO;
import com.cloud.dc.ASNumberVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.ASNumberDao;
import com.cloud.dc.dao.ASNumberRangeDao;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.domain.Domain;
import com.cloud.domain.dao.DomainDao;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.element.BgpServiceProvider;
import com.cloud.network.element.NetworkElement;
import com.cloud.network.vpc.Vpc;
import com.cloud.network.vpc.VpcOfferingVO;
import com.cloud.network.vpc.VpcVO;
import com.cloud.network.vpc.dao.VpcDao;
import com.cloud.network.vpc.dao.VpcOfferingDao;
import com.cloud.network.vpc.dao.VpcServiceMapDao;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.offerings.dao.NetworkOfferingDao;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import com.cloud.user.dao.AccountDao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionCallbackNoReturn;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.api.command.user.bgp.ListASNumbersCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.network.BgpPeer;
import org.apache.cloudstack.network.BgpPeerVO;
import org.apache.cloudstack.network.RoutedIpv4Manager;
import org.apache.cloudstack.network.dao.BgpPeerDao;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.inject.Inject;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class BGPServiceImpl implements BGPService {
public static final Logger LOGGER = LogManager.getLogger(BGPServiceImpl.class);
@Inject
private DataCenterDao dataCenterDao;
@Inject
private ASNumberRangeDao asNumberRangeDao;
@Inject
private ASNumberDao asNumberDao;
@Inject
private NetworkDao networkDao;
@Inject
private VpcDao vpcDao;
@Inject
private VpcOfferingDao vpcOfferingDao;
@Inject
private NetworkOfferingDao networkOfferingDao;
@Inject
private AccountDao accountDao;
@Inject
private DomainDao domainDao;
@Inject
NetworkServiceMapDao ntwkSrvcDao;
@Inject
NetworkModel networkModel;
@Inject
BgpPeerDao bgpPeerDao;
@Inject
RoutedIpv4Manager routedIpv4Manager;
@Inject
VpcServiceMapDao vpcServiceMapDao;
public BGPServiceImpl() {
}
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_AS_RANGE_CREATE, eventDescription = "AS Range creation")
public ASNumberRange createASNumberRange(long zoneId, long startASNumber, long endASNumber) {
DataCenterVO zone = dataCenterDao.findById(zoneId);
if (zone == null) {
String msg = String.format("Cannot find a zone with ID %s", zoneId);
LOGGER.error(msg);
throw new InvalidParameterException(msg);
}
if (!routedIpv4Manager.isRoutedNetworkVpcEnabled(zoneId)) {
throw new InvalidParameterValueException("Cannot create ASN range as Routed networks and VPCs are not enabled for the zone.");
}
if (startASNumber > endASNumber) {
String msg = "Please specify a valid AS Number range";
LOGGER.error(msg);
throw new InvalidParameterException(msg);
}
List<ASNumberRangeVO> asNumberRanges = asNumberRangeDao.listByZoneId(zoneId);
for (ASNumberRangeVO asNumberRange : asNumberRanges) {
if (isASNumbersOverlap(asNumberRange.getStartASNumber(), asNumberRange.getEndASNumber(), startASNumber, endASNumber)) {
throw new InvalidParameterException(String.format("New AS number range (%s-%s) has conflict with an existing AS number range (%s-%s)",
startASNumber, endASNumber, asNumberRange.getStartASNumber(), asNumberRange.getEndASNumber()));
}
}
try {
return Transaction.execute((TransactionCallback<ASNumberRange>) status -> {
LOGGER.debug("Persisting AS Number Range {}-{} for the zone {}", startASNumber, endASNumber, zone);
ASNumberRangeVO asNumberRangeVO = new ASNumberRangeVO(zoneId, startASNumber, endASNumber);
asNumberRangeDao.persist(asNumberRangeVO);
for (long asn = startASNumber; asn <= endASNumber; asn++) {
LOGGER.debug("Persisting AS Number {} for zone {}", asn, zone);
ASNumberVO asNumber = new ASNumberVO(asn, asNumberRangeVO.getId(), zoneId);
asNumberDao.persist(asNumber);
}
return asNumberRangeVO;
});
} catch (Exception e) {
String err = String.format("Error creating AS Number range %s-%s for zone %s: %s", startASNumber, endASNumber, zone, e.getMessage());
LOGGER.error(err, e);
throw new CloudRuntimeException(err);
}
}
protected boolean isASNumbersOverlap(long startNumber1, long endNumber1, long startNumber2, long endNumber2) {
if (startNumber1 > endNumber2 || startNumber2 > endNumber1) {
return false;
}
return true;
}
@Override
public List<ASNumberRange> listASNumberRanges(Long zoneId) {
List<ASNumberRangeVO> rangeVOList = zoneId != null ? asNumberRangeDao.listByZoneId(zoneId) : asNumberRangeDao.listAll();
return new ArrayList<>(rangeVOList);
}
@Override
public Pair<List<ASNumber>, Integer> listASNumbers(ListASNumbersCmd cmd) {
Long zoneId = cmd.getZoneId();
Long asNumberRangeId = cmd.getAsNumberRangeId();
Integer asNumber = cmd.getAsNumber();
Boolean allocated = cmd.getAllocated();
Long networkId = cmd.getNetworkId();
Long vpcId = cmd.getVpcId();
String accountName = cmd.getAccount();
Long domainId = cmd.getDomainId();
Long startIndex = cmd.getStartIndex();
Long pageSizeVal = cmd.getPageSizeVal();
String keyword = cmd.getKeyword();
Account userAccount = null;
Domain domain = null;
final Account caller = CallContext.current().getCallingAccount();
if (Objects.nonNull(accountName)) {
if (domainId != null) {
userAccount = accountDao.findActiveAccount(accountName, domainId);
domain = domainDao.findById(domainId);
} else {
userAccount = accountDao.findActiveAccount(accountName, caller.getDomainId());
domain = domainDao.findById(caller.getDomainId());
}
}
if (Objects.nonNull(accountName) && Objects.isNull(userAccount)) {
throw new InvalidParameterException(String.format("Failed to find user Account: %s", accountName));
}
Long networkSearchId = networkId;
Long vpcSerchId = vpcId;
if (networkId != null) {
NetworkVO network = networkDao.findById(networkId);
if (network == null) {
throw new InvalidParameterException(String.format("Failed to find network with ID: %s", networkId));
}
if (network.getVpcId() != null) {
LOGGER.debug("The network {} is a VPC tier, searching for the AS number on the VPC {}",
network::toString, () -> vpcDao.findById(network.getVpcId()));
networkSearchId = null;
vpcSerchId = network.getVpcId();
}
}
Pair<List<ASNumberVO>, Integer> pair = asNumberDao.searchAndCountByZoneOrRangeOrAllocated(zoneId, asNumberRangeId,
asNumber, networkSearchId, vpcSerchId, allocated, Objects.nonNull(userAccount) ? userAccount.getId() : null,
Objects.nonNull(domain) ? domain.getId() : null, keyword, caller, startIndex, pageSizeVal);
return new Pair<>(new ArrayList<>(pair.first()), pair.second());
}
@Override
public boolean allocateASNumber(long zoneId, Long asNumber, Long networkId, Long vpcId) {
ASNumberVO asNumberVO = isOfferingSpecifyAsNumber(networkId, vpcId) ?
asNumberDao.findByAsNumber(asNumber) :
asNumberDao.findOneByAllocationStateAndZone(zoneId, false);
if (asNumberVO == null || asNumberVO.getDataCenterId() != zoneId) {
if (asNumber != null) {
LOGGER.error("Cannot find AS number {} in zone {} with id {}", asNumber, dataCenterDao.findById(zoneId), zoneId);
return false;
}
throw new CloudRuntimeException(String.format("Cannot allocate AS number in zone with ID %s", zoneId));
}
long accountId, domainId;
String netName;
VpcVO vpc = null;
NetworkVO network = null;
if (Objects.nonNull(vpcId)) {
vpc = vpcDao.findById(vpcId);
if (vpc == null) {
LOGGER.error(String.format("Cannot find VPC with ID %s", vpcId));
return false;
}
accountId = vpc.getAccountId();
domainId = vpc.getDomainId();
netName = vpc.getName();
} else {
network = networkDao.findById(networkId);
if (network == null) {
LOGGER.error(String.format("Cannot find network with ID %s", networkId));
return false;
}
accountId = network.getAccountId();
domainId = network.getDomainId();
netName = network.getName();
}
String networkName = Objects.nonNull(vpcId) ? ("VPC " + vpc) : ("network " + network);
LOGGER.debug("Allocating the AS Number {} to {} on zone {}", asNumberVO::toString,
networkName::toString, () -> dataCenterDao.findById(zoneId));
asNumberVO.setAllocated(true);
asNumberVO.setAllocatedTime(new Date());
if (Objects.nonNull(vpcId)) {
asNumberVO.setVpcId(vpcId);
} else {
asNumberVO.setNetworkId(networkId);
}
asNumberVO.setAccountId(accountId);
asNumberVO.setDomainId(domainId);
return asNumberDao.update(asNumberVO.getId(), asNumberVO);
}
private boolean isOfferingSpecifyAsNumber(Long networkId, Long vpcId) {
if (Objects.nonNull(vpcId)) {
VpcVO vpc = vpcDao.findById(vpcId);
if (vpc == null) {
throw new CloudRuntimeException(String.format("Cannot find VPC with ID %s", vpcId));
}
VpcOfferingVO vpcOffering = vpcOfferingDao.findById(vpc.getVpcOfferingId());
return NetworkOffering.RoutingMode.Dynamic == vpcOffering.getRoutingMode() && BooleanUtils.toBoolean(vpcOffering.isSpecifyAsNumber());
} else {
NetworkVO network = networkDao.findById(networkId);
NetworkOfferingVO networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId());
return NetworkOffering.RoutingMode.Dynamic == networkOffering.getRoutingMode() && BooleanUtils.toBoolean(networkOffering.isSpecifyAsNumber());
}
}
private Pair<Boolean, String> logAndReturnErrorMessage(String msg) {
LOGGER.error(msg);
return new Pair<>(false, msg);
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_AS_NUMBER_RELEASE, eventDescription = "Releasing AS Number")
public Pair<Boolean, String> releaseASNumber(long zoneId, long asNumber, boolean isDestroyNetworkOperation) {
ASNumberVO asNumberVO = asNumberDao.findByAsNumber(asNumber);
DataCenterVO zone = dataCenterDao.findById(zoneId);
if (asNumberVO == null) {
return logAndReturnErrorMessage(String.format("Cannot find AS Number %s on zone %s", asNumber, zone));
}
if (!asNumberVO.isAllocated()) {
LOGGER.debug("The AS Number {} is not allocated to any network on zone {}, ignoring release", asNumber, zone);
return new Pair<>(true, "");
}
Long networkId = asNumberVO.getNetworkId();
Long vpcId = asNumberVO.getVpcId();
if (!isDestroyNetworkOperation) {
Pair<Boolean, String> checksResult = performReleaseASNumberChecks(networkId, vpcId, asNumber);
if (checksResult != null) {
return checksResult;
}
}
LOGGER.debug("Releasing AS Number {} on zone {} from previous allocation", asNumber, zone);
asNumberVO.setAllocated(false);
asNumberVO.setAllocatedTime(null);
asNumberVO.setDomainId(null);
asNumberVO.setAccountId(null);
if (vpcId != null) {
asNumberVO.setVpcId(null);
} else {
asNumberVO.setNetworkId(null);
}
boolean update = asNumberDao.update(asNumberVO.getId(), asNumberVO);
String msg = update ? "OK" : "Could not update database record for AS number";
return new Pair<>(update, msg);
}
private Pair<Boolean, String> performReleaseASNumberChecks(Long networkId, Long vpcId, long asNumber) {
if (networkId != null) {
NetworkVO network = networkDao.findById(networkId);
if (network == null) {
return logAndReturnErrorMessage(String.format("Cannot find a network with ID %s which acquired the AS number %s", networkId, asNumber));
}
NetworkOfferingVO offering = networkOfferingDao.findById(network.getNetworkOfferingId());
if (offering == null) {
return logAndReturnErrorMessage(String.format("Cannot find a network offering with ID %s", network.getNetworkOfferingId()));
}
if (offering.isSpecifyAsNumber()) {
return logAndReturnErrorMessage(String.format("Cannot release the AS number %s as it is acquired by a network that requires AS number", asNumber));
}
} else if (vpcId != null) {
VpcVO vpc = vpcDao.findById(vpcId);
if (vpc == null) {
return logAndReturnErrorMessage(String.format("Cannot find a VPC with ID %s which acquired the AS number %s", vpcId, asNumber));
}
VpcOfferingVO vpcOffering = vpcOfferingDao.findById(vpc.getVpcOfferingId());
if (vpcOffering == null) {
return logAndReturnErrorMessage(String.format("Cannot find a VPC offering with ID %s", vpc.getVpcOfferingId()));
}
if (vpcOffering.isSpecifyAsNumber()) {
return logAndReturnErrorMessage(String.format("Cannot release the AS number %s as it is acquired by a VPC that requires AS number", asNumber));
}
}
return null;
}
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_AS_RANGE_DELETE, eventDescription = "Deleting AS Range")
public boolean deleteASRange(long id) {
final ASNumberRange asRange = asNumberRangeDao.findById(id);
if (asRange == null) {
throw new CloudRuntimeException(String.format("Could not find a AS range with id: %s", id));
}
long startASNumber = asRange.getStartASNumber();
long endASNumber = asRange.getEndASNumber();
long zoneId = asRange.getDataCenterId();
DataCenterVO zone = dataCenterDao.findById(zoneId);
List<ASNumberVO> allocatedAsNumbers = asNumberDao.listAllocatedByASRange(asRange.getId());
if (Objects.nonNull(allocatedAsNumbers) && !allocatedAsNumbers.isEmpty()) {
throw new CloudRuntimeException(String.format("There are %s AS numbers in use from the range %s-%s, cannot remove the range",
allocatedAsNumbers.size(), startASNumber, endASNumber));
}
try {
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
int removedASNumbers = asNumberDao.removeASRangeNumbers(asRange.getId());
LOGGER.debug(String.format("Removed %s AS numbers from the range %s-%s", removedASNumbers,
startASNumber, endASNumber));
asNumberRangeDao.remove(id);
LOGGER.debug("Removing the AS Number Range {}-{} for the zone {}", startASNumber, endASNumber, zone);
}
});
} catch (Exception e) {
String err = String.format("Error removing AS Number range %s-%s for zone %s: %s",
startASNumber, endASNumber, zone, e.getMessage());
LOGGER.error(err, e);
throw new CloudRuntimeException(err);
}
return true;
}
@Override
public boolean applyBgpPeers(Network network, boolean continueOnError) throws ResourceUnavailableException {
if (!routedIpv4Manager.isDynamicRoutedNetwork(network)) {
return true;
}
final String gatewayProviderStr = ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Network.Service.Gateway);
if (gatewayProviderStr != null) {
NetworkElement provider = networkModel.getElementImplementingProvider(gatewayProviderStr);
if (provider != null && provider instanceof BgpServiceProvider) {
List<? extends BgpPeer> bgpPeers = getBgpPeersForNetwork(network);
LOGGER.debug(String.format("Applying BPG Peers for network [%s]: [%s]", network, bgpPeers));
return ((BgpServiceProvider) provider).applyBgpPeers(null, network, bgpPeers);
}
}
return true;
}
@Override
public boolean applyBgpPeers(Vpc vpc, boolean continueOnError) throws ResourceUnavailableException {
if (!routedIpv4Manager.isDynamicRoutedVpc(vpc)) {
return true;
}
final String gatewayProviderStr = vpcServiceMapDao.getProviderForServiceInVpc(vpc.getId(), Network.Service.Gateway);
if (gatewayProviderStr != null) {
NetworkElement provider = networkModel.getElementImplementingProvider(gatewayProviderStr);
if (provider != null && provider instanceof BgpServiceProvider) {
List<? extends BgpPeer> bgpPeers = getBgpPeersForVpc(vpc);
LOGGER.debug(String.format("Applying BPG Peers for VPC [%s]: [%s]", vpc, bgpPeers));
return ((BgpServiceProvider) provider).applyBgpPeers(vpc, null, bgpPeers);
}
}
return true;
}
@Override
public List<? extends BgpPeer> getBgpPeersForNetwork(Network network) {
List<BgpPeerVO> bgpPeers;
if (network.getVpcId() != null) {
bgpPeers = bgpPeerDao.listNonRevokeByVpcId(network.getVpcId());
} else {
bgpPeers = bgpPeerDao.listNonRevokeByNetworkId(network.getId());
}
if (CollectionUtils.isEmpty(bgpPeers)) {
Account owner = accountDao.findByIdIncludingRemoved(network.getAccountId());
List<Long> bgpPeerIds = routedIpv4Manager.getBgpPeerIdsForAccount(owner, network.getDataCenterId());
bgpPeers = bgpPeerIds.stream()
.map(bgpPeerId -> bgpPeerDao.findById(bgpPeerId))
.collect(Collectors.toList());
}
return bgpPeers;
}
@Override
public List<? extends BgpPeer> getBgpPeersForVpc(Vpc vpc) {
List<BgpPeerVO> bgpPeers = bgpPeerDao.listNonRevokeByVpcId(vpc.getId());
if (CollectionUtils.isEmpty(bgpPeers)) {
Account owner = accountDao.findByIdIncludingRemoved(vpc.getAccountId());
List<Long> bgpPeerIds = routedIpv4Manager.getBgpPeerIdsForAccount(owner, vpc.getZoneId());
bgpPeers = bgpPeerIds.stream()
.map(bgpPeerId -> bgpPeerDao.findById(bgpPeerId))
.collect(Collectors.toList());
}
return bgpPeers;
}
}