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