blob: 475c0d4e061ac7689e54bc23a85b0c2ef7ea87de [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 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);
}
}