mirror of https://github.com/apache/cloudstack.git
460 lines
21 KiB
Java
460 lines
21 KiB
Java
// 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;
|
|
}
|
|
}
|