blob: 21a4d734b9797f4c90e7837163a95e91b817a1f1 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed 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.onosproject.openstackinterface.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.glassfish.jersey.client.ClientProperties;
import org.onlab.packet.Ip4Address;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.Port;
import org.onosproject.net.config.ConfigFactory;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.openstackinterface.OpenstackFloatingIP;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackInterfaceConfig;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstackinterface.OpenstackRouter;
import org.onosproject.openstackinterface.OpenstackSecurityGroup;
import org.onosproject.openstackinterface.OpenstackSubnet;
import org.onosproject.openstackinterface.web.OpenstackFloatingIpCodec;
import org.onosproject.openstackinterface.web.OpenstackNetworkCodec;
import org.onosproject.openstackinterface.web.OpenstackPortCodec;
import org.onosproject.openstackinterface.web.OpenstackRouterCodec;
import org.onosproject.openstackinterface.web.OpenstackSecurityGroupCodec;
import org.onosproject.openstackinterface.web.OpenstackSubnetCodec;
import org.slf4j.Logger;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.net.MediaType.JSON_UTF_8;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.AnnotationKeys.PORT_NAME;
import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Handles REST Calls to Openstack Neutron.
*
*/
@Service
@Component(immediate = true)
public class OpenstackInterfaceManager implements OpenstackInterfaceService {
private static final String URI_NETWORKS = "networks";
private static final String URI_PORTS = "ports";
private static final String URI_SUBNETS = "subnets";
private static final String URI_SECURITY_GROUPS = "security-groups";
private static final String URI_FLOATINGIPS = "floatingips";
private static final String URI_TOKENS = "tokens";
private static final String FLOATINGIP = "floatingip";
private static final String PORT_ID = "port_id";
private static final String FIXED_IP_ADDRESS = "fixed_ip_address";
private static final String PATH_ROUTERS = "routers";
private static final String PATH_NETWORKS = "networks";
private static final String PATH_PORTS = "ports";
private static final String PATH_SUBNETS = "subnets";
private static final String PATH_FLOATINGIPS = "floatingips";
private static final String PATH_ACCESS = "access";
private static final String PATH_TOKEN = "token";
private static final String PATH_ID = "id";
private static final String PATH_EXPIRES = "expires";
private static final String HEADER_AUTH_TOKEN = "X-Auth-Token";
private static final String TOKEN_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private static final int DEFAULT_TIMEOUT_MS = 2000;
private final Logger log = getLogger(getClass());
private final Client client = ClientBuilder.newClient();
private String neutronUrl;
private String keystoneUrl;
private String tokenId;
private String tokenExpires;
private String userName;
private String pass;
private ApplicationId appId;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected NetworkConfigRegistry cfgService;
private InternalConfigListener internalConfigListener = new InternalConfigListener();
private ExecutorService networkEventExcutorService =
Executors.newSingleThreadExecutor(groupedThreads("onos/openstackinterface", "config-event", log));
private final Set<ConfigFactory> factories = ImmutableSet.of(
new ConfigFactory<ApplicationId, OpenstackInterfaceConfig>(APP_SUBJECT_FACTORY,
OpenstackInterfaceConfig.class,
"openstackinterface") {
@Override
public OpenstackInterfaceConfig createConfig() {
return new OpenstackInterfaceConfig();
}
}
);
@Activate
protected void activate() {
appId = coreService
.registerApplication("org.onosproject.openstackinterface");
factories.forEach(cfgService::registerConfigFactory);
cfgService.addListener(internalConfigListener);
client.property(ClientProperties.CONNECT_TIMEOUT, DEFAULT_TIMEOUT_MS);
client.property(ClientProperties.READ_TIMEOUT, DEFAULT_TIMEOUT_MS);
configureNetwork();
log.info("started");
}
@Deactivate
protected void deactivate() {
cfgService.removeListener(internalConfigListener);
factories.forEach(cfgService::unregisterConfigFactory);
log.info("stopped");
}
/**
* Returns network information stored in Neutron.
*
* @return List of OpenstackNetwork
*/
public Collection<OpenstackNetwork> getNetworks() {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_NETWORKS);
if (builder == null) {
log.warn("Failed to get networks");
return Collections.EMPTY_LIST;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
log.debug("networks response:" + response);
ObjectMapper mapper = new ObjectMapper();
List<OpenstackNetwork> openstackNetworks = Lists.newArrayList();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode networkList = (ArrayNode) node.path(PATH_NETWORKS);
OpenstackNetworkCodec networkCodec = new OpenstackNetworkCodec();
networkList.forEach(n -> openstackNetworks.add(networkCodec.decode((ObjectNode) n, null)));
} catch (IOException e) {
log.warn("getNetworks()", e);
}
openstackNetworks.removeAll(Collections.singleton(null));
openstackNetworks.forEach(n -> log.debug("network ID: {}", n.id()));
return openstackNetworks;
}
/**
* Returns port information stored in Neutron.
*
* @return List of OpenstackPort
*/
public Collection<OpenstackPort> getPorts() {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_PORTS);
if (builder == null) {
log.warn("Failed to get ports");
return Collections.EMPTY_LIST;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
ObjectMapper mapper = new ObjectMapper();
List<OpenstackPort> openstackPorts = Lists.newArrayList();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode portList = (ArrayNode) node.path(PATH_PORTS);
OpenstackPortCodec portCodec = new OpenstackPortCodec();
portList.forEach(p -> openstackPorts.add(portCodec.decode((ObjectNode) p, null)));
} catch (IOException e) {
log.warn("getPorts()", e);
}
log.debug("port response:" + response);
openstackPorts.forEach(n -> log.debug("port ID: {}", n.id()));
return openstackPorts;
}
public Collection<OpenstackRouter> getRouters() {
Invocation.Builder builder = getClientBuilder(neutronUrl, PATH_ROUTERS);
if (builder == null) {
log.warn("Failed to get routers");
return Collections.EMPTY_LIST;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
ObjectMapper mapper = new ObjectMapper();
List<OpenstackRouter> openstackRouters = Lists.newArrayList();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode routerList = (ArrayNode) node.path(PATH_ROUTERS);
OpenstackRouterCodec openstackRouterCodec = new OpenstackRouterCodec();
routerList.forEach(r -> openstackRouters
.add(openstackRouterCodec.decode((ObjectNode) r, null)));
} catch (IOException e) {
log.warn("getRouters()", e);
}
log.debug("router response:" + response);
openstackRouters.forEach(r -> log.debug("router ID: {}", r.id()));
return openstackRouters;
}
/**
* Returns Subnet information in Neutron.
*
* @return List of OpenstackSubnet
*/
public Collection<OpenstackSubnet> getSubnets() {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_SUBNETS);
if (builder == null) {
log.warn("Failed to get subnets");
return Collections.EMPTY_LIST;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
ObjectMapper mapper = new ObjectMapper();
List<OpenstackSubnet> subnets = Lists.newArrayList();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode subnetList = (ArrayNode) node.path(PATH_SUBNETS);
OpenstackSubnetCodec subnetCodec = new OpenstackSubnetCodec();
subnetList.forEach(s -> subnets.add(subnetCodec.decode((ObjectNode) s, null)));
} catch (IOException e) {
log.warn("getSubnets()", e);
}
log.debug("subnets response:" + response);
subnets.forEach(s -> log.debug("subnet ID: {}", s.id()));
return subnets;
}
/**
* Extracts OpenstackSecurityGroup information for the ID.
*
* @param id Security Group ID
* @return OpenstackSecurityGroup object or null if fails
*/
@Override
public OpenstackSecurityGroup securityGroup(String id) {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_SECURITY_GROUPS + "/" + id);
if (builder == null) {
log.warn("Failed to get security group {}", id);
return null;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
ObjectMapper mapper = new ObjectMapper();
OpenstackSecurityGroup securityGroup = null;
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
OpenstackSecurityGroupCodec sgCodec = new OpenstackSecurityGroupCodec();
securityGroup = sgCodec.decode(node, null);
} catch (IOException e) {
log.warn("securityGroup()", e);
}
return securityGroup;
}
private Invocation.Builder getClientBuilder(String baseUrl, String path) {
if (Strings.isNullOrEmpty(baseUrl)) {
log.warn("Keystone or Neutron URL is not set");
return null;
}
WebTarget wt = client.target(baseUrl + path);
return wt.request(JSON_UTF_8.toString());
}
private String getToken() {
if (!isTokenValid()) {
String request = "{\"auth\": {\"tenantName\": \"admin\", " +
"\"passwordCredentials\": {\"username\": \"" +
userName + "\",\"password\": \"" + pass + "\"}}}";
Invocation.Builder builder = getClientBuilder(keystoneUrl, URI_TOKENS);
if (builder == null) {
log.warn("Failed to get token");
return null;
}
String response = builder.accept(MediaType.APPLICATION_JSON).post(Entity.json(request), String.class);
ObjectMapper mapper = new ObjectMapper();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
tokenId = node.path(PATH_ACCESS).path(PATH_TOKEN).path(PATH_ID).asText();
tokenExpires = node.path(PATH_ACCESS).path(PATH_TOKEN).path(PATH_EXPIRES).asText();
} catch (IOException e) {
log.warn("getToken()", e);
}
log.debug("token response:" + response);
}
return tokenId;
}
private boolean isTokenValid() {
if (tokenExpires == null || tokenId == null || tokenExpires.isEmpty()) {
return false;
}
try {
SimpleDateFormat dateFormat = new SimpleDateFormat(TOKEN_DATE_FORMAT);
Date exireDate = dateFormat.parse(tokenExpires);
Calendar today = Calendar.getInstance();
if (exireDate.after(today.getTime())) {
return true;
}
} catch (ParseException e) {
log.error("Token parse exception error : {}", e.getMessage());
return false;
}
log.debug("token is Invalid");
return false;
}
@Override
public Collection<OpenstackPort> ports(String networkId) {
return getPorts().stream()
.filter(port -> port.networkId().equals(networkId))
.collect(Collectors.toList());
}
@Override
public Collection<OpenstackPort> ports() {
return getPorts();
}
@Override
public OpenstackPort port(Port port) {
String uuid = port.annotations().value(PORT_NAME).substring(3);
return getPorts().stream()
.filter(p -> p.id().startsWith(uuid))
.findAny().orElse(null);
}
@Override
public OpenstackPort port(String portId) {
return getPorts().stream()
.filter(p -> p.id().equals(portId))
.findAny().orElse(null);
}
@Override
public OpenstackNetwork network(String networkId) {
Collection<OpenstackSubnet> subnets = getSubnets().stream()
.filter(s -> s.networkId().equals(networkId))
.collect(Collectors.toList());
OpenstackNetwork openstackNetwork = getNetworks().stream()
.filter(n -> n.id().equals(networkId))
.findAny().orElse(null);
if (openstackNetwork == null) {
return null;
}
return OpenstackNetwork.builder()
.id(openstackNetwork.id())
.name(openstackNetwork.name())
.networkType(openstackNetwork.networkType())
.segmentId(openstackNetwork.segmentId())
.tenantId(openstackNetwork.tenantId())
.subnets(subnets)
.build();
}
@Override
public Collection<OpenstackNetwork> networks() {
return getNetworks();
}
@Override
public OpenstackSubnet subnet(String subnetId) {
return getSubnets().stream()
.filter(subnet -> subnet.id().equals(subnetId))
.findAny().orElse(null);
}
@Override
public Collection<OpenstackSubnet> subnets() {
return getSubnets();
}
@Override
public Collection<OpenstackRouter> routers() {
return getRouters();
}
@Override
public OpenstackRouter router(String routerId) {
return getRouters().stream()
.filter(router -> router.id().equals(routerId))
.findAny().orElse(null);
}
@Override
public Collection<OpenstackFloatingIP> floatingIps() {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_FLOATINGIPS);
if (builder == null) {
log.warn("Failed to get floating IPs");
return Collections.EMPTY_LIST;
}
String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
log.debug("floatingIps response:" + response);
ObjectMapper mapper = new ObjectMapper();
List<OpenstackFloatingIP> openstackFloatingIPs = Lists.newArrayList();
try {
ObjectNode node = (ObjectNode) mapper.readTree(response);
ArrayNode floatingIpList = (ArrayNode) node.path(PATH_FLOATINGIPS);
OpenstackFloatingIpCodec fipCodec = new OpenstackFloatingIpCodec();
floatingIpList.forEach(f -> openstackFloatingIPs.add(fipCodec.decode((ObjectNode) f, null)));
} catch (IOException e) {
log.warn("floatingIps()", e);
}
openstackFloatingIPs.removeAll(Collections.singleton(null));
return openstackFloatingIPs;
}
@Override
public boolean updateFloatingIp(String id, String portId, Optional<Ip4Address> fixedIpAddress) {
Invocation.Builder builder = getClientBuilder(neutronUrl, URI_FLOATINGIPS + "/" + id);
if (builder == null || (portId != null && !fixedIpAddress.isPresent())) {
log.warn("Failed to update floating IP");
return false;
}
ObjectNode objectNode = createFloatingIpObject(portId, fixedIpAddress);
InputStream inputStream = new ByteArrayInputStream(objectNode.toString().getBytes());
try {
Response response = builder.header(HEADER_AUTH_TOKEN, getToken())
.put(Entity.entity(IOUtils.toString(inputStream, StandardCharsets.UTF_8),
MediaType.APPLICATION_JSON));
log.debug("updateFloatingIp called: {}, status: {}", response.readEntity(String.class),
String.valueOf(response.getStatus()));
return checkReply(response);
} catch (IOException e) {
log.error("Cannot do PUT {} request");
return false;
}
}
private ObjectNode createFloatingIpObject(String portId, Optional<Ip4Address> fixedIpAddress) {
ObjectMapper mapper = new ObjectMapper();
ObjectNode objectNode = mapper.createObjectNode();
objectNode.putObject(FLOATINGIP)
.put(PORT_ID, portId);
if (portId != null) {
objectNode.put(FIXED_IP_ADDRESS, fixedIpAddress.get().toString());
}
return objectNode;
}
private boolean checkReply(Response response) {
if (response != null) {
return checkStatusCode(response.getStatus());
}
log.warn("Null floating IP response from openstack");
return false;
}
private boolean checkStatusCode(int statusCode) {
if (statusCode == Response.Status.OK.getStatusCode()) {
return true;
}
return false;
}
private void configureNetwork() {
OpenstackInterfaceConfig cfg =
cfgService.getConfig(appId, OpenstackInterfaceConfig.class);
if (cfg == null) {
log.error("There is no openstack server information in config.");
return;
}
neutronUrl = checkNotNull(cfg.neutronServer());
keystoneUrl = checkNotNull(cfg.keystoneServer());
userName = checkNotNull(cfg.userName());
pass = checkNotNull(cfg.password());
}
private class InternalConfigListener implements NetworkConfigListener {
@Override
public void event(NetworkConfigEvent event) {
if (((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) &&
event.configClass().equals(OpenstackInterfaceConfig.class)) {
log.info("Network configuration changed");
networkEventExcutorService.execute(() -> configureNetwork());
}
}
}
}