// // 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.network.bigswitch; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.commons.net.util.SubnetUtils; import org.apache.log4j.Logger; import org.bouncycastle.util.IPAddress; import com.cloud.agent.AgentManager; import com.cloud.agent.api.BcfAnswer; import com.cloud.agent.api.BcfCommand; import com.cloud.agent.api.CacheBcfTopologyCommand; import com.cloud.agent.api.GetControllerDataAnswer; import com.cloud.agent.api.GetControllerDataCommand; import com.cloud.agent.api.SyncBcfTopologyCommand; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.VlanDao; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.BigSwitchBcfDeviceVO; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.bigswitch.TopologyData.Port; import com.cloud.network.dao.BigSwitchBcfDao; import com.cloud.network.dao.FirewallRulesCidrsDao; import com.cloud.network.dao.FirewallRulesCidrsVO; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.NetworkACLItemCidrsDao; import com.cloud.network.vpc.NetworkACLItemCidrsVO; import com.cloud.network.vpc.NetworkACLItemDao; import com.cloud.network.vpc.NetworkACLItemVO; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.utils.db.DB; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.NicVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.VMInstanceDao; public class BigSwitchBcfUtils { private static final Logger s_logger = Logger.getLogger(BigSwitchBcfUtils.class); private final NetworkDao _networkDao; private final NicDao _nicDao; private final VMInstanceDao _vmDao; private final HostDao _hostDao; private final VpcDao _vpcDao; private final BigSwitchBcfDao _bigswitchBcfDao; private final AgentManager _agentMgr; private final VlanDao _vlanDao; private final IPAddressDao _ipAddressDao; private final FirewallRulesDao _fwRulesDao; private final FirewallRulesCidrsDao _fwCidrsDao; private final NetworkACLItemDao _aclItemDao; private final NetworkACLItemCidrsDao _aclItemCidrsDao; private final NetworkModel _networkModel; public BigSwitchBcfUtils(NetworkDao networkDao, NicDao nicDao, VMInstanceDao vmDao, HostDao hostDao, VpcDao vpcDao, BigSwitchBcfDao bigswitchBcfDao, AgentManager agentMgr, VlanDao vlanDao, IPAddressDao ipAddressDao, FirewallRulesDao fwRulesDao, FirewallRulesCidrsDao fwCidrsDao, NetworkACLItemDao aclItemDao, NetworkACLItemCidrsDao aclItemCidrsDao, NetworkModel networkModel){ _networkDao = networkDao; _nicDao = nicDao; _vmDao = vmDao; _hostDao = hostDao; _vpcDao = vpcDao; _bigswitchBcfDao = bigswitchBcfDao; _agentMgr = agentMgr; _vlanDao = vlanDao; _ipAddressDao = ipAddressDao; _fwRulesDao = fwRulesDao; _fwCidrsDao = fwCidrsDao; _aclItemDao = aclItemDao; _aclItemCidrsDao = aclItemCidrsDao; _networkModel = networkModel; } public ControlClusterData getControlClusterData(long physicalNetworkId){ ControlClusterData cluster = new ControlClusterData(); // reusable command to query all devices GetControllerDataCommand cmd = new GetControllerDataCommand(); // retrieve all registered BCF devices List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listByPhysicalNetwork(physicalNetworkId); for (BigSwitchBcfDeviceVO d: devices){ HostVO bigswitchBcfHost = _hostDao.findById(d.getHostId()); if (bigswitchBcfHost == null){ continue; } _hostDao.loadDetails(bigswitchBcfHost); GetControllerDataAnswer answer = (GetControllerDataAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), cmd); if (answer != null){ if (answer.isMaster()) { cluster.setMaster(bigswitchBcfHost); } else { cluster.setSlave(bigswitchBcfHost); } } } return cluster; } public TopologyData getTopology(){ long physicalNetworkId; List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listAll(); if(!devices.isEmpty()){ physicalNetworkId = devices.get(0).getPhysicalNetworkId(); return getTopology(physicalNetworkId); } else { return null; } } public NetworkVO getPublicNetwork(long physicalNetworkId){ List<NetworkVO> pubNets = _networkDao.listByPhysicalNetworkTrafficType(physicalNetworkId, TrafficType.Public); return pubNets.get(0); } public TopologyData getTopology(long physicalNetworkId){ List<NetworkVO> networks; List<NicVO> nics; networks = _networkDao.listByPhysicalNetworkTrafficType(physicalNetworkId, TrafficType.Guest); TopologyData topo = new TopologyData(); // networks: // - all user created networks (VPC or non-VPC) // - one external network // routers: // - per VPC (handle in network loop) // - per stand-alone network (handle in network loop) // - one external router // handle external network first, only if NAT service is enabled if(networks != null) { if(!(networks.isEmpty()) && isNatEnabled()){ // get public net info - needed to set up source nat gateway NetworkVO pubNet = getPublicNetwork(physicalNetworkId); // locate subnet info SearchCriteria<VlanVO> sc = _vlanDao.createSearchCriteria(); sc.setParameters("network_id", pubNet.getId()); VlanVO vlanVO = _vlanDao.findOneBy(sc); // add tenant external network external TopologyData.Network network = topo.new Network(); network.setId("external"); network.setName("external"); network.setTenantId("external"); network.setTenantName("external"); String pubVlan = null; try { pubVlan = BroadcastDomainType.getValue(vlanVO.getVlanTag()); if(StringUtils.isNumeric(pubVlan)){ network.setVlan(Integer.valueOf(pubVlan)); } else { // untagged pubVlan = "0"; } } catch (URISyntaxException e) { e.printStackTrace(); } topo.addNetwork(network); } } // routerMap used internally for multiple updates to same tenant's router // add back to topo.routers after loop HashMap<String, RouterData> routerMap = new HashMap<String, RouterData>(); for (NetworkVO netVO: networks){ TopologyData.Network network = topo.new Network(); network.setId(netVO.getUuid()); network.setName(netVO.getName()); Integer vlan = null; if (netVO.getBroadcastUri() != null) { String vlanStr = BroadcastDomainType.getValue(netVO.getBroadcastUri()); if(StringUtils.isNumeric(vlanStr)){ vlan = Integer.valueOf(vlanStr); } else { // untagged vlan = 0; } } network.setVlan(vlan); network.setState(netVO.getState().name()); nics = _nicDao.listByNetworkId(netVO.getId()); List<Port> ports = new ArrayList<Port>(); String tenantId = null; String tenantName = null; // if VPC network, assign BCF tenant id with vpc uuid Vpc vpc = null; if(netVO.getVpcId()!=null){ vpc = _vpcDao.acquireInLockTable(netVO.getVpcId()); } if (vpc != null) { tenantId = vpc.getUuid(); tenantName = vpc.getName(); } else { tenantId = netVO.getUuid(); tenantName = netVO.getName(); } for(NicVO nic: nics){ NetworkData netData = new NetworkData(); TopologyData.Port p = topo.new Port(); p.setAttachmentInfo(netData.new AttachmentInfo(nic.getUuid(),nic.getMacAddress())); VMInstanceVO vm = _vmDao.findById(nic.getInstanceId()); HostVO host = _hostDao.findById(vm.getHostId()); // if host not found, ignore this nic if (host == null) { continue; } String hostname = host.getName(); long zoneId = netVO.getDataCenterId(); String vmwareVswitchLabel = _networkModel.getDefaultGuestTrafficLabel(zoneId, HypervisorType.VMware); String[] labelArray = null; String vswitchName = null; if(vmwareVswitchLabel!=null){ labelArray=vmwareVswitchLabel.split(","); vswitchName = labelArray[0]; } // hypervisor type: // kvm: ivs port name // vmware: specific portgroup naming convention String pgName = ""; if (host.getHypervisorType() == HypervisorType.KVM){ pgName = hostname; } else if (host.getHypervisorType() == HypervisorType.VMware){ pgName = hostname + "-" + vswitchName; } p.setHostId(pgName); p.setSegmentInfo(netData.new SegmentInfo(BroadcastDomainType.Vlan.name(), vlan)); p.setOwner(BigSwitchBcfApi.getCloudstackInstanceId()); List<AttachmentData.Attachment.IpAddress> ipList = new ArrayList<AttachmentData.Attachment.IpAddress>(); ipList.add(new AttachmentData().getAttachment().new IpAddress(nic.getIPv4Address())); p.setIpAddresses(ipList); p.setId(nic.getUuid()); p.setMac(nic.getMacAddress()); netData.getNetwork().setId(network.getId()); netData.getNetwork().setName(network.getName()); netData.getNetwork().setTenantId(tenantId); netData.getNetwork().setTenantName(tenantName); netData.getNetwork().setState(netVO.getState().name()); p.setNetwork(netData.getNetwork()); ports.add(p); } network.setTenantId(tenantId); network.setTenantName(tenantName); network.setPorts(ports); topo.addNetwork(network); // add router for network RouterData routerData; if(tenantId != null){ if(!routerMap.containsKey(tenantId)){ routerData = new RouterData(tenantId); routerMap.put(tenantId, routerData); } else { routerData = routerMap.get(tenantId); } routerData.getRouter().getAcls().addAll(listACLbyNetwork(netVO)); if(vpc != null){ routerData.getRouter().addExternalGateway(getPublicIpByVpc(vpc)); } else { routerData.getRouter().addExternalGateway(getPublicIpByNetwork(netVO)); } RouterInterfaceData intf = new RouterInterfaceData(tenantId, netVO.getGateway(), netVO.getCidr(), netVO.getUuid(), netVO.getName()); routerData.getRouter().addInterface(intf); } } for(RouterData rd: routerMap.values()) { topo.addRouter(rd.getRouter()); } return topo; } public String getPublicIpByNetwork(Network network){ List<IPAddressVO> allocatedIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), true); for (IPAddressVO ip: allocatedIps){ if(ip.isSourceNat()){ return ip.getAddress().addr(); } } return null; } public String getPublicIpByVpc(Vpc vpc){ List<IPAddressVO> allocatedIps = _ipAddressDao.listByAssociatedVpc(vpc.getId(), true); for (IPAddressVO ip: allocatedIps){ if(ip.isSourceNat()){ return ip.getAddress().addr(); } } return null; } public List<AclData> listACLbyNetwork(Network network){ List<AclData> aclList = new ArrayList<AclData>(); List<FirewallRuleVO> fwRules; fwRules = _fwRulesDao.listByNetworkAndPurposeAndNotRevoked(network.getId(), Purpose.Firewall); List<FirewallRulesCidrsVO> fwCidrList = null; SubnetUtils utils; for(FirewallRuleVO rule: fwRules){ AclData acl = new AclData(); acl.setId(rule.getUuid()); acl.setPriority((int)rule.getId()); // CloudStack Firewall interface does not have priority acl.setIpProto(rule.getProtocol()); String cidr = null; Integer port = rule.getSourcePortStart(); fwCidrList = _fwCidrsDao.listByFirewallRuleId(rule.getId()); if(fwCidrList != null){ if(fwCidrList.size()>1 || !rule.getSourcePortEnd().equals(port)){ continue; } else { cidr = fwCidrList.get(0).getCidr(); } } if (cidr == null || cidr.equalsIgnoreCase("0.0.0.0/0")) { cidr = ""; } else { utils = new SubnetUtils(cidr); if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){ continue; } } acl.setSource(acl.new AclNetwork(cidr, port)); acl.setAction("permit"); aclList.add(acl); } List<NetworkACLItemVO> aclItems; List<NetworkACLItemCidrsVO> aclCidrList; if (network.getNetworkACLId() != null){ aclItems = _aclItemDao.listByACL(network.getNetworkACLId()); for(NetworkACLItem item: aclItems){ AclData acl = new AclData(); acl.setId(item.getUuid()); acl.setPriority(item.getNumber()); acl.setIpProto(item.getProtocol()); String cidr = null; // currently BCF supports single cidr policy Integer port = item.getSourcePortStart(); // currently BCF supports single port policy aclCidrList = _aclItemCidrsDao.listByNetworkACLItemId(item.getId()); if(aclCidrList != null){ if(aclCidrList.size()>1 || !item.getSourcePortEnd().equals(port)){ continue; } else { cidr = aclCidrList.get(0).getCidr(); } } if (cidr == null || cidr.equalsIgnoreCase("0.0.0.0/0")) { cidr = ""; } else { utils = new SubnetUtils(cidr); if(!utils.getInfo().getNetworkAddress().equals(utils.getInfo().getAddress())){ continue; } } acl.setSource(acl.new AclNetwork(cidr, port)); acl.setAction(item.getAction().name()); aclList.add(acl); } } return aclList; } public String syncTopologyToBcfHost(HostVO bigswitchBcfHost){ SyncBcfTopologyCommand syncCmd; if(isNatEnabled()){ syncCmd = new SyncBcfTopologyCommand(true, true); } else { syncCmd = new SyncBcfTopologyCommand(true, false); } BcfAnswer syncAnswer = (BcfAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), syncCmd); if (syncAnswer == null || !syncAnswer.getResult()) { s_logger.error("SyncBcfTopologyCommand failed"); return null; } return syncAnswer.getHash(); } public String syncTopologyToBcfHost(HostVO bigswitchBcfHost, boolean natEnabled){ SyncBcfTopologyCommand syncCmd; if(natEnabled){ syncCmd = new SyncBcfTopologyCommand(true, true); } else { syncCmd = new SyncBcfTopologyCommand(true, false); } BcfAnswer syncAnswer = (BcfAnswer) _agentMgr.easySend(bigswitchBcfHost.getId(), syncCmd); if (syncAnswer == null || !syncAnswer.getResult()) { s_logger.error("SyncBcfTopologyCommand failed"); return null; } return syncAnswer.getHash(); } public BcfAnswer sendBcfCommandWithNetworkSyncCheck(BcfCommand cmd, Network network)throws IllegalArgumentException{ // get registered Big Switch controller ControlClusterData cluster = getControlClusterData(network.getPhysicalNetworkId()); if(cluster.getMaster()==null){ return new BcfAnswer(cmd, new CloudRuntimeException("Big Switch Network controller temporarily unavailable")); } TopologyData topo = getTopology(network.getPhysicalNetworkId()); cmd.setTopology(topo); BcfAnswer answer = (BcfAnswer) _agentMgr.easySend(cluster.getMaster().getId(), cmd); if (answer == null || !answer.getResult()) { s_logger.error ("BCF API Command failed"); throw new IllegalArgumentException("Failed API call to Big Switch Network plugin"); } String newHash = answer.getHash(); if (cmd.isTopologySyncRequested()) { newHash = syncTopologyToBcfHost(cluster.getMaster()); } if(newHash != null){ commitTopologyHash(network.getPhysicalNetworkId(), newHash); } HostVO slave = cluster.getSlave(); if(slave != null){ TopologyData newTopo = getTopology(network.getPhysicalNetworkId()); CacheBcfTopologyCommand cacheCmd = new CacheBcfTopologyCommand(newTopo); _agentMgr.easySend(cluster.getSlave().getId(), cacheCmd); } return answer; } @DB public Boolean commitTopologyHash(long physicalNetworkId, final String hash) { final List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listByPhysicalNetwork(physicalNetworkId); return Transaction.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus status) { for (BigSwitchBcfDeviceVO d: devices){ d.setHash(hash); _bigswitchBcfDao.update(d.getId(), d); } return true; } }); } public Boolean isNatEnabled(){ List<BigSwitchBcfDeviceVO> devices = _bigswitchBcfDao.listAll(); if(devices != null && !devices.isEmpty()){ return devices.get(0).getNat(); } else { return false; } } public Integer getSubnetMaskLength(String maskString){ if(!IPAddress.isValidIPv4(maskString)){ return null; } String[] octets = maskString.split("\\."); Integer bits = 0; for (String o: octets){ switch(o){ case "255": bits += 8; continue; case "254": bits += 7; return bits; case "252": bits += 6; return bits; case "248": bits += 5; return bits; case "240": bits += 4; return bits; case "224": bits += 3; return bits; case "192": bits += 2; return bits; case "128": bits += 1; return bits; case "0": return bits; default: throw new NumberFormatException("non-contiguous subnet mask not supported"); } } return bits; } }