// 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.network.element; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import org.apache.cloudstack.api.commands.AddSspCmd; import org.apache.cloudstack.api.commands.DeleteSspCmd; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.network.dao.SspCredentialDao; import org.apache.cloudstack.network.dao.SspCredentialVO; import org.apache.cloudstack.network.dao.SspTenantDao; import org.apache.cloudstack.network.dao.SspTenantVO; import org.apache.cloudstack.network.dao.SspUuidDao; import org.apache.cloudstack.network.dao.SspUuidVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DeployDestination; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.NetworkMigrationResponder; import com.cloud.network.NetworkModel; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.PhysicalNetwork; import com.cloud.network.PhysicalNetworkServiceProvider; import com.cloud.network.PhysicalNetworkServiceProvider.State; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderVO; import com.cloud.network.element.ConnectivityProvider; import com.cloud.offering.NetworkOffering; import com.cloud.resource.ResourceManager; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.ReservationContext; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.NicDao; /** * Stratosphere sdn platform network element * * This class will be called per network setup operations. * This class also have ssp specific methods. * * Current implementation use HostVO for storage of api endpoint information, * but note this is not necessary. The other way is create our own database * table for that information. */ public class SspElement extends AdapterBase implements ConnectivityProvider, SspManager, SspService, NetworkMigrationResponder { private static final Logger s_logger = Logger.getLogger(SspElement.class); public static final String s_SSP_NAME = "StratosphereSsp"; private static final Provider s_ssp_provider = new Provider(s_SSP_NAME, false); @Inject NetworkServiceMapDao _ntwkSrvcDao; @Inject NetworkModel _networkModel; @Inject NetworkOrchestrationService _networkMgr; @Inject ResourceManager _resourceMgr; @Inject PhysicalNetworkDao _physicalNetworkDao; @Inject PhysicalNetworkServiceProviderDao _physicalNetworkServiceProviderDao; @Inject SspCredentialDao _sspCredentialDao; @Inject SspTenantDao _sspTenantDao; @Inject SspUuidDao _sspUuidDao; @Inject DataCenterDao _dcDao; @Inject HostDao _hostDao; @Inject ConfigurationDao _configDao; @Inject NicDao _nicDao = null; @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { return super.configure(name, params); } @Override public Map<Service, Map<Capability, String>> getCapabilities() { Map<Service, Map<Capability, String>> capabilities = new HashMap<Service, Map<Capability, String>>(); capabilities.put(Service.Connectivity, new HashMap<Capability, String>()); // XXX: We might need some more category here. return capabilities; } @Override public Provider getProvider() { return s_ssp_provider; } private List<SspClient> fetchSspClients(Long physicalNetworkId, Long dataCenterId, boolean enabledOnly) { ArrayList<SspClient> clients = new ArrayList<SspClient>(); boolean provider_found = false; PhysicalNetworkServiceProviderVO provider = _physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetworkId, s_SSP_NAME); if (enabledOnly) { if (provider != null && provider.getState() == State.Enabled) { provider_found = true; } } else { provider_found = true; } if (physicalNetworkId != null && provider_found) { SspCredentialVO credential = _sspCredentialDao.findByZone(dataCenterId); List<HostVO> hosts = _resourceMgr.listAllHostsInOneZoneByType(Host.Type.L2Networking, dataCenterId); for (HostVO host : hosts) { assert (credential != null); _hostDao.loadDetails(host); if ("v1Api".equals(host.getDetail("sspHost"))) { clients.add(new SspClient(host.getDetail("url"), credential.getUsername(), credential.getPassword())); } } } if (clients.size() == 0) { String global_apiUrl = _configDao.getValueAndInitIfNotExist("ssp.url", "Network", null); String global_username = _configDao.getValueAndInitIfNotExist("ssp.username", "Network", null); String global_password = _configDao.getValueAndInitIfNotExist("ssp.password", "Network", null); if (global_apiUrl != null && global_username != null && global_password != null) { clients.add(new SspClient(global_apiUrl, global_username, global_password)); } } return clients; } /* (non-Javadoc) * @see org.apache.cloudstack.network.element.NetworkElement#isReady(com.cloud.network.PhysicalNetworkServiceProvider) */ @Override public boolean isReady(PhysicalNetworkServiceProvider provider) { PhysicalNetwork physicalNetwork = _physicalNetworkDao.findById(provider.getPhysicalNetworkId()); assert (physicalNetwork != null); if (fetchSspClients(physicalNetwork.getId(), physicalNetwork.getDataCenterId(), false).size() > 0) { return true; } s_logger.warn("Ssp api endpoint not found. " + physicalNetwork.toString()); return false; } /* (non-Javadoc) * If this element is ready, then it can be enabled. * @see org.apache.cloudstack.network.element.SspManager#isEnabled(com.cloud.network.PhysicalNetwork) */ @Override public boolean canHandle(PhysicalNetwork physicalNetwork) { if (physicalNetwork != null) { if (fetchSspClients(physicalNetwork.getId(), physicalNetwork.getDataCenterId(), true).size() > 0) { return true; } s_logger.warn("enabled Ssp api endpoint not found. " + physicalNetwork.toString()); } else { s_logger.warn("PhysicalNetwork is NULL."); } return false; } private boolean canHandle(Network network) { if (canHandle(_physicalNetworkDao.findById(network.getPhysicalNetworkId()))) { if (!_ntwkSrvcDao.canProviderSupportServiceInNetwork(network.getId(), Service.Connectivity, getProvider())) { s_logger.info("SSP is implicitly active for " + network); } return true; } return false; } @Override public Host addSspHost(AddSspCmd cmd) { SspClient client = new SspClient(cmd.getUrl(), cmd.getUsername(), cmd.getPassword()); if (!client.login()) { throw new CloudRuntimeException("Ssp login failed."); } long zoneId = cmd.getZoneId(); SspCredentialVO credential = _sspCredentialDao.findByZone(zoneId); if (credential == null) { if (cmd.getUsername() == null || cmd.getPassword() == null) { throw new InvalidParameterValueException("Initial credential required for zone: " + zoneId); } credential = new SspCredentialVO(); credential.setZoneId(zoneId); credential.setUsername(cmd.getUsername()); credential.setPassword(cmd.getPassword()); _sspCredentialDao.persist(credential); } else { if (cmd.getUsername() != null || cmd.getPassword() != null) { s_logger.warn("Tenant credential already configured for zone:" + zoneId); } } String tenantUuid = _sspTenantDao.findUuidByZone(zoneId); if (tenantUuid == null) { if (cmd.getTenantUuid() == null) { throw new InvalidParameterValueException("Initial tenant uuid required for zone: " + zoneId); } SspTenantVO tenant = new SspTenantVO(); tenant.setZoneId(zoneId); tenant.setUuid(cmd.getTenantUuid()); _sspTenantDao.persist(tenant); } else { if (cmd.getTenantUuid() != null) { s_logger.warn("Tenant uuid already configured for zone:" + zoneId); } } String normalizedUrl = null; String hostname = null; try { URL url = new URL(cmd.getUrl()); normalizedUrl = url.toString(); hostname = url.getHost(); } catch (MalformedURLException e1) { throw new CloudRuntimeException("Invalid url " + cmd.getUrl()); } List<HostVO> hosts = _resourceMgr.listAllHostsInOneZoneByType(Host.Type.L2Networking, zoneId); for (HostVO host : hosts) { assert (credential != null); _hostDao.loadDetails(host); if ("v1Api".equals(host.getDetail("sspHost"))) { if (normalizedUrl.equals(host.getDetail("url"))) { s_logger.warn("Ssp host already registered " + normalizedUrl); return host; } } } // SspHost HostVO will be created per zone and url. HostVO host = new HostVO(UUID.randomUUID().toString()); host.setDataCenterId(zoneId); host.setType(Host.Type.L2Networking); host.setPrivateIpAddress(hostname); // db schema not null. It may be a name, not IP address. // host.setPrivateMacAddress(""); // db schema nullable // host.setPrivateNetmask(""); // db schema nullable host.setVersion("1"); // strange db schema not null host.setName(cmd.getName()); host.setDetails(new HashMap<String, String>()); host.setDetail("sspHost", "v1Api"); host.setDetail("url", normalizedUrl); return _hostDao.persist(host); } @Override public boolean deleteSspHost(DeleteSspCmd cmd) { s_logger.info("deleteStratosphereSsp"); return _hostDao.remove(cmd.getHostId()); } @Override public boolean createNetwork(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) { if (_sspUuidDao.findUuidByNetwork(network) != null) { s_logger.info("Network already has ssp TenantNetwork uuid :" + network.toString()); return true; } if (!canHandle(network)) { return false; } String tenantUuid = _sspTenantDao.findUuidByZone(network.getDataCenterId()); if (tenantUuid == null) { tenantUuid = _configDao.getValueAndInitIfNotExist("ssp.tenant", "Network", null); } boolean processed = false; for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { SspClient.TenantNetwork sspNet = client.createTenantNetwork(tenantUuid, network.getName()); if (sspNet != null) { SspUuidVO uuid = new SspUuidVO(); uuid.setUuid(sspNet.uuid); uuid.setObjClass(SspUuidVO.objClassNetwork); uuid.setObjId(network.getId()); _sspUuidDao.persist(uuid); return true; } processed = true; } if (processed) { s_logger.error("Could not allocate an uuid for network " + network.toString()); return false; } else { s_logger.error("Skipping #createNetwork() for " + network.toString()); return true; } } @Override public boolean deleteNetwork(Network network) { String tenantNetworkUuid = _sspUuidDao.findUuidByNetwork(network); if (tenantNetworkUuid != null) { boolean processed = false; for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { if (client.deleteTenantNetwork(tenantNetworkUuid)) { _sspUuidDao.removeUuid(tenantNetworkUuid); processed = true; break; } } if (!processed) { s_logger.error("Ssp api tenant network deletion failed " + network.toString()); } } else { s_logger.debug("Silently skipping #deleteNetwork() for " + network.toString()); } return true; } // we use context.reservationId for dedup of guru & element operations. @Override public boolean createNicEnv(Network network, NicProfile nic, DeployDestination dest, ReservationContext context) { String tenantNetworkUuid = _sspUuidDao.findUuidByNetwork(network); if (tenantNetworkUuid == null) { s_logger.debug("Skipping #createNicEnv() for nic on " + network.toString()); return true; } String reservationId = context.getReservationId(); List<SspUuidVO> tenantPortUuidVos = _sspUuidDao.listUUidVoByNicProfile(nic); for (SspUuidVO tenantPortUuidVo : tenantPortUuidVos) { if (reservationId.equals(tenantPortUuidVo.getReservationId())) { s_logger.info("Skipping because reservation found " + reservationId); return true; } } String tenantPortUuid = null; for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { SspClient.TenantPort sspPort = client.createTenantPort(tenantNetworkUuid); if (sspPort != null) { tenantPortUuid = sspPort.uuid; nic.setReservationId(reservationId); SspUuidVO uuid = new SspUuidVO(); uuid.setUuid(tenantPortUuid); uuid.setObjClass(SspUuidVO.objClassNicProfile); uuid.setObjId(nic.getId()); uuid.setReservationId(reservationId); _sspUuidDao.persist(uuid); break; } } if (tenantPortUuid == null) { s_logger.debug("#createNicEnv() failed for nic on " + network.toString()); return false; } for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { SspClient.TenantPort sspPort = client.updateTenantVifBinding(tenantPortUuid, dest.getHost().getPrivateIpAddress()); if (sspPort != null) { if (sspPort.vlanId != null) { nic.setBroadcastType(BroadcastDomainType.Vlan); nic.setBroadcastUri(BroadcastDomainType.Vlan.toUri(String.valueOf(sspPort.vlanId))); } return true; } } s_logger.error("Updating vif failed " + nic.toString()); return false; } @Override public boolean deleteNicEnv(Network network, NicProfile nic, ReservationContext context) { if (context == null) { s_logger.error("ReservationContext was null for " + nic + " " + network); return false; } String reservationId = context.getReservationId(); SspUuidVO deleteTarget = null; SspUuidVO remainingTarget = null; List<SspUuidVO> tenantPortUuidVos = _sspUuidDao.listUUidVoByNicProfile(nic); for (SspUuidVO tenantPortUuidVo : tenantPortUuidVos) { if (reservationId.equals(tenantPortUuidVo.getReservationId())) { deleteTarget = tenantPortUuidVo; } else { remainingTarget = tenantPortUuidVo; } } if (deleteTarget != null) { // delete the target ssp uuid (tenant-port) String tenantPortUuid = deleteTarget.getUuid(); boolean processed = false; for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { SspClient.TenantPort sspPort = client.updateTenantVifBinding(tenantPortUuid, null); if (sspPort != null) { processed = true; break; } } if (!processed) { s_logger.warn("Ssp api nic detach failed " + nic.toString()); } processed = false; for (SspClient client : fetchSspClients(network.getPhysicalNetworkId(), network.getDataCenterId(), true)) { if (client.deleteTenantPort(tenantPortUuid)) { _sspUuidDao.removeUuid(tenantPortUuid); processed = true; break; } } if (!processed) { s_logger.warn("Ssp api tenant port deletion failed " + nic.toString()); } _sspUuidDao.removeUuid(tenantPortUuid); } if (remainingTarget != null) { NicVO nicVo = _nicDao.findById(nic.getId()); nicVo.setReservationId(remainingTarget.getReservationId()); _nicDao.persist(nicVo); // persist the new reservationId } return true; } /* (non-Javadoc) * Implements a network using ssp element. * * This method will be called right after NetworkGuru#implement(). * see also {@link #shutdown(Network, ReservationContext, boolean)} * @see org.apache.cloudstack.network.element.NetworkElement#implement(com.cloud.network.Network, com.cloud.offering.NetworkOffering, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext) */ @Override public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { s_logger.info("implement"); return createNetwork(network, offering, dest, context); } /* (non-Javadoc) * Shutdown the network implementation * * This method will be called right BEFORE NetworkGuru#shutdown(). * The entities was acquired by {@link #implement(Network, NetworkOffering, DeployDestination, ReservationContext)} * @see org.apache.cloudstack.network.element.NetworkElement#shutdown(com.cloud.network.Network, com.cloud.vm.ReservationContext, boolean) */ @Override public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException { s_logger.trace("shutdown"); return deleteNetwork(network); } /* (non-Javadoc) * Prepares a network environment for a VM nic. * * This method will be called right after NetworkGuru#reserve(). * The entities will be released by {@link #release(Network, NicProfile, VirtualMachineProfile, ReservationContext)} * @see org.apache.cloudstack.network.element.NetworkElement#prepare(com.cloud.network.Network, com.cloud.vm.NicProfile, com.cloud.vm.VirtualMachineProfile, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext) */ @Override public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { s_logger.trace("prepare"); return createNicEnv(network, nic, dest, context); } /* (non-Javadoc) * Release the network environment that was prepared for a VM nic. * * This method will be called right AFTER NetworkGuru#release(). * The entities was acquired in {@link #prepare(Network, NicProfile, VirtualMachineProfile, DeployDestination, ReservationContext)} * @see org.apache.cloudstack.network.element.NetworkElement#release(com.cloud.network.Network, com.cloud.vm.NicProfile, com.cloud.vm.VirtualMachineProfile, com.cloud.vm.ReservationContext) */ @Override public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { s_logger.trace("release"); return deleteNicEnv(network, nic, context); } /* (non-Javadoc) * Destroy a network implementation. * * This method will be called right BEFORE NetworkGuru#trash() in "Expunge" phase. * @see org.apache.cloudstack.network.element.NetworkElement#destroy(com.cloud.network.Network) */ @Override public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { s_logger.trace("destroy"); // nothing to do here. return true; } @Override public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException { s_logger.trace("shutdownProviderInstances"); return true; } @Override public boolean canEnableIndividualServices() { s_logger.trace("canEnableIndividualServices"); return true; // because there is only Connectivity } @Override public boolean verifyServicesCombination(Set<Service> services) { s_logger.trace("verifyServicesCombination " + services.toString()); return true; } @Override public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) { try { prepare(network, nic, vm, dest, context); } catch (ConcurrentOperationException e) { s_logger.error("prepareForMigration failed.", e); return false; } catch (ResourceUnavailableException e) { s_logger.error("prepareForMigration failed.", e); return false; } catch (InsufficientCapacityException e) { s_logger.error("prepareForMigration failed.", e); return false; } return true; } @Override public void rollbackMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) { try { release(network, nic, vm, dst); } catch (ConcurrentOperationException e) { s_logger.error("rollbackMigration failed.", e); } catch (ResourceUnavailableException e) { s_logger.error("rollbackMigration failed.", e); } } @Override public void commitMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) { try { release(network, nic, vm, src); } catch (ConcurrentOperationException e) { s_logger.error("commitMigration failed.", e); } catch (ResourceUnavailableException e) { s_logger.error("commitMigration failed.", e); } } @Override public List<Class<?>> getCommands() { return Arrays.<Class<?>> asList(AddSspCmd.class, DeleteSspCmd.class); } }