org.onosproject.openstackinterface.impl.OpenstackInterfaceManager.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.openstackinterface.impl.OpenstackInterfaceManager.java

Source

/*
 * 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());
            }
        }
    }
}