package org.ovirt.engine.core.bll.provider.network.openstack; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.ovirt.engine.core.bll.provider.BaseProviderProxy; import org.ovirt.engine.core.bll.provider.network.NetworkProviderProxy; import org.ovirt.engine.core.common.businessentities.OpenstackNetworkProviderProperties; import org.ovirt.engine.core.common.businessentities.Provider; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.network.ExternalSubnet; import org.ovirt.engine.core.common.businessentities.network.ExternalSubnet.IpVersion; import org.ovirt.engine.core.common.businessentities.network.Network; import org.ovirt.engine.core.common.businessentities.network.ProviderNetwork; import org.ovirt.engine.core.common.businessentities.network.VmNic; import org.ovirt.engine.core.common.businessentities.network.VnicProfile; import org.ovirt.engine.core.common.errors.EngineError; import org.ovirt.engine.core.common.errors.EngineException; import org.ovirt.engine.core.utils.NetworkUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.woorea.openstack.base.client.HttpMethod; import com.woorea.openstack.base.client.OpenStackRequest; import com.woorea.openstack.base.client.OpenStackResponseException; import com.woorea.openstack.keystone.utils.KeystoneTokenProvider; import com.woorea.openstack.quantum.Quantum; import com.woorea.openstack.quantum.model.Networks; import com.woorea.openstack.quantum.model.Port; import com.woorea.openstack.quantum.model.Port.Binding; import com.woorea.openstack.quantum.model.Subnet; import com.woorea.openstack.quantum.model.Subnets; public abstract class BaseNetworkProviderProxy<P extends OpenstackNetworkProviderProperties> extends BaseProviderProxy implements NetworkProviderProxy { private static final List<String> DEFAULT_SECURITY_GROUP = null; private static final List<String> NO_SECURITY_GROUPS = Collections.emptyList(); private static final String SECURITY_GROUPS_PROPERTY = "SecurityGroups"; private static final String API_VERSION = "/v2.0"; protected static final String DEVICE_OWNER = "oVirt"; private static final String FLAT_NETWORK = "flat"; private static final String VLAN_NETWORK = "vlan"; private Quantum client; private static Logger log = LoggerFactory.getLogger(BaseNetworkProviderProxy.class); public BaseNetworkProviderProxy(Provider<P> provider) { super(provider); } private Quantum getClient() { if (client == null) { client = new Quantum(getProvider().getUrl() + API_VERSION, new CustomizedRESTEasyConnector()); if (getProvider().isRequiringAuthentication()) { setClientTokenProvider(client); } } return client; } protected void setClientTokenProvider(Quantum client) { String tenantName = getProvider().getAdditionalProperties().getTenantName(); KeystoneTokenProvider keystoneTokenProvider = new KeystoneTokenProvider(getProvider().getAuthUrl(), getProvider().getUsername(), getProvider().getPassword()); client.setTokenProvider(keystoneTokenProvider.getProviderByTenant(tenantName)); } @Override public String add(Network network) { com.woorea.openstack.quantum.model.Network networkForCreate = createNewNetworkEntity(network); try { com.woorea.openstack.quantum.model.Network createdNetwork = getClient().networks().create(networkForCreate).execute(); return createdNetwork.getId(); } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } protected com.woorea.openstack.quantum.model.Network createNewNetworkEntity(Network network) { com.woorea.openstack.quantum.model.Network networkForCreate = new com.woorea.openstack.quantum.model.Network(); networkForCreate.setAdminStateUp(true); networkForCreate.setName(network.getName()); if (NetworkUtils.isLabeled(network)) { networkForCreate.setProviderPhysicalNetwork(network.getLabel()); if (NetworkUtils.isVlan(network)) { networkForCreate.setProviderNetworkType(VLAN_NETWORK); networkForCreate.setProviderSegmentationId(network.getVlanId()); } else { networkForCreate.setProviderNetworkType(FLAT_NETWORK); } } if (!getProvider().isRequiringAuthentication()) { networkForCreate.setTenantId(DEVICE_OWNER); } return networkForCreate; } @Override public void remove(String id) { try { getClient().networks().delete(id).execute(); } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } @Override public List<Network> getAll() { try { Networks networks = getClient().networks().list().execute(); return map(networks.getList()); } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } @Override public List<ExternalSubnet> getAllSubnets(ProviderNetwork network) { List<ExternalSubnet> result = new ArrayList<>(); Subnets subnets = getClient().subnets().list().execute(); for (Subnet subnet : subnets.getList()) { if (network.getExternalId().equals(subnet.getNetworkId())) { result.add(map(subnet, network)); } } return result; } private ExternalSubnet map(Subnet subnet, ProviderNetwork network) { ExternalSubnet s = new ExternalSubnet(); s.setId(subnet.getId()); s.setCidr(subnet.getCidr()); s.setIpVersion(Subnet.IpVersion.IPV6.equals(subnet.getIpversion()) ? IpVersion.IPV6 : IpVersion.IPV4); s.setName(subnet.getName()); s.setExternalNetwork(network); s.setGateway(subnet.getGw()); s.setDnsServers(subnet.getDnsNames()); return s; } @Override public void addSubnet(ExternalSubnet subnet) { com.woorea.openstack.quantum.model.Network externalNetwork = getExternalNetwork(subnet.getExternalNetwork()); Subnet subnetForCreate = createNewSubnetEntity(subnet, externalNetwork); try { getClient().subnets().create(subnetForCreate).execute(); } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } protected Subnet createNewSubnetEntity(ExternalSubnet subnet, com.woorea.openstack.quantum.model.Network externalNetwork) { Subnet subnetForCreate = new Subnet(); subnetForCreate.setCidr(subnet.getCidr()); subnetForCreate.setIpversion(subnet.getIpVersion() == IpVersion.IPV6 ? Subnet.IpVersion.IPV6 : Subnet.IpVersion.IPV4); subnetForCreate.setName(subnet.getName()); subnetForCreate.setNetworkId(externalNetwork.getId()); subnetForCreate.setEnableDHCP(true); subnetForCreate.setGw(subnet.getGateway()); subnetForCreate.setDnsNames(subnet.getDnsServers()); subnetForCreate.setTenantId(externalNetwork.getTenantId()); return subnetForCreate; } @Override public void removeSubnet(String id) { try { getClient().subnets().delete(id).execute(); } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } @Override public void testConnection() { try { getClient().execute(new OpenStackRequest<>(getClient(), HttpMethod.GET, "", null, ApiRootResponse.class)); } catch (OpenStackResponseException e) { log.error("{} (OpenStack response error code: {})", e.getMessage(), e.getStatus()); log.debug("Exception", e); throw new EngineException(EngineError.PROVIDER_FAILURE, e); }catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } private List<Network> map(List<com.woorea.openstack.quantum.model.Network> externalNetworks) { List<Network> networks = new ArrayList<>(externalNetworks.size()); for (com.woorea.openstack.quantum.model.Network externalNetwork : externalNetworks) { Network network = new Network(); network.setVmNetwork(true); network.setProvidedBy(new ProviderNetwork(getProvider().getId(), externalNetwork.getId())); network.setName(externalNetwork.getName()); networks.add(network); } return networks; } @Override public Map<String, String> allocate(Network network, VnicProfile vnicProfile, VmNic nic, VDS host) { try { Port port = locatePort(nic); List<String> securityGroups = getSecurityGroups(vnicProfile); String hostId = getHostId(host); if (port == null) { com.woorea.openstack.quantum.model.Network externalNetwork = getExternalNetwork(network.getProvidedBy()); Port portForCreate = createNewPortForAllocate(nic, securityGroups, hostId, externalNetwork); port = getClient().ports().create(portForCreate).execute(); } else { boolean securityGroupsChanged = securityGroupsChanged(port.getSecurityGroups(), securityGroups); boolean hostChanged = hostChanged(port, hostId); if (securityGroupsChanged || hostChanged) { List<String> modifiedSecurityGroups = securityGroupsChanged ? securityGroups : port.getSecurityGroups(); Port portForUpdate = modifyPortForAllocate(port, hostId, hostChanged, modifiedSecurityGroups, nic.getMacAddress()); port = getClient().ports().update(portForUpdate).execute(); } } Map<String, String> runtimeProperties = createPortAllocationRuntimeProperties(port); return runtimeProperties; } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } private String getHostId(VDS host) { if (host.getStaticData().getOpenstackNetworkProviderId() == null) { return host.getHostName(); } else { return NetworkUtils.getUniqueHostName(host); } } protected Port modifyPortForAllocate(Port port, String hostId, boolean hostChanged, List<String> modifiedSecurityGroups, String macAddress) { Port portForUpdate = new PortForUpdate(); portForUpdate.setId(port.getId()); portForUpdate.setSecurityGroups(modifiedSecurityGroups); if (hostChanged) { portForUpdate.setBinding(new Binding()); portForUpdate.getBinding().setHostId(hostId); portForUpdate.setMacAddress(macAddress); } return portForUpdate; } protected Port createNewPortForAllocate(VmNic nic, List<String> securityGroups, String hostId, com.woorea.openstack.quantum.model.Network externalNetwork) { Port portForCreate = new Port(); portForCreate.setAdminStateUp(true); portForCreate.setName(nic.getName()); portForCreate.setMacAddress(nic.getMacAddress()); portForCreate.setNetworkId(externalNetwork.getId()); portForCreate.setDeviceOwner(DEVICE_OWNER); portForCreate.setDeviceId(nic.getId().toString()); portForCreate.setSecurityGroups(securityGroups); portForCreate.setBinding(new Binding()); portForCreate.getBinding().setHostId(hostId); portForCreate.setTenantId(externalNetwork.getTenantId()); return portForCreate; } protected Map<String, String> createPortAllocationRuntimeProperties(Port port) { Map<String, String> runtimeProperties = new HashMap<>(); runtimeProperties.put("vnic_id", port.getId()); String providerType = getProvider().getType().name(); runtimeProperties.put("provider_type", providerType); if (port.getSecurityGroups() != null && !port.getSecurityGroups().isEmpty()) { runtimeProperties.put("security_groups", StringUtils.join(port.getSecurityGroups(), ',')); } return runtimeProperties; } private com.woorea.openstack.quantum.model.Network getExternalNetwork(ProviderNetwork providerNetwork) { return getClient().networks().show(providerNetwork.getExternalId()).execute(); } private boolean hostChanged(Port port, String hostId) { return port.getBinding() == null || !StringUtils.equals(port.getBinding().getHostId(), hostId); } private boolean securityGroupsChanged(List<String> existingSecurityGroups, List<String> desiredSecurityGroups) { existingSecurityGroups = existingSecurityGroups == null ? NO_SECURITY_GROUPS : existingSecurityGroups; return (desiredSecurityGroups == DEFAULT_SECURITY_GROUP && existingSecurityGroups.isEmpty()) || (desiredSecurityGroups != DEFAULT_SECURITY_GROUP && !CollectionUtils.isEqualCollection(existingSecurityGroups, desiredSecurityGroups)); } private List<String> getSecurityGroups(VnicProfile vnicProfile) { Map<String, String> customProperties = vnicProfile.getCustomProperties(); if (customProperties.containsKey(SECURITY_GROUPS_PROPERTY)) { String securityGroupsString = customProperties.get(SECURITY_GROUPS_PROPERTY); if (StringUtils.isEmpty(securityGroupsString)) { return NO_SECURITY_GROUPS; } return Arrays.asList(securityGroupsString.split(",\\w*")); } return DEFAULT_SECURITY_GROUP; } @Override public void deallocate(VmNic nic) { try { Port port = locatePort(nic); if (port != null) { getClient().ports().delete(port.getId()).execute(); } } catch (RuntimeException e) { throw new EngineException(EngineError.PROVIDER_FAILURE, e); } } private Port locatePort(VmNic nic) { List<Port> ports = getClient().ports().list().execute().getList(); for (Port port : ports) { if (DEVICE_OWNER.equals(port.getDeviceOwner()) && nic.getId().toString().equals(port.getDeviceId())) { return port; } } return null; } @JsonIgnoreProperties(ignoreUnknown = true) private static class ApiRootResponse { // No implementation since we don't care what's inside the response, just that it succeeded. } @Override public Provider<P> getProvider() { return (Provider<P>)super.getProvider(); } }