blob: 722305c839fff2e8bfe3d16b83e5ad03cff6b2eb [file] [log] [blame]
sanghoshinf25d2e02015-11-11 23:07:17 +09001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
sanghoshinf25d2e02015-11-11 23:07:17 +09003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Hyunsun Moon0dba61f2016-03-03 14:05:21 -080016package org.onosproject.openstackinterface.impl;
sanghoshinf25d2e02015-11-11 23:07:17 +090017
18import com.fasterxml.jackson.databind.ObjectMapper;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.ObjectNode;
sangho93447f12016-02-24 00:33:22 +090021import com.google.common.collect.ImmutableSet;
sanghoshinf25d2e02015-11-11 23:07:17 +090022import com.google.common.collect.Lists;
sangho0c2a3da2016-02-16 13:39:07 +090023import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.apache.felix.scr.annotations.Service;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.core.CoreService;
31import org.onosproject.net.Port;
sangho93447f12016-02-24 00:33:22 +090032import org.onosproject.net.config.ConfigFactory;
33import org.onosproject.net.config.NetworkConfigEvent;
34import org.onosproject.net.config.NetworkConfigListener;
35import org.onosproject.net.config.NetworkConfigRegistry;
Hyunsun Moon0dba61f2016-03-03 14:05:21 -080036import org.onosproject.openstackinterface.OpenstackInterfaceService;
37import org.onosproject.openstackinterface.OpenstackNetwork;
sangho48907542016-03-28 16:07:07 +090038import org.onosproject.openstackinterface.OpenstackInterfaceConfig;
Hyunsun Moon0dba61f2016-03-03 14:05:21 -080039import org.onosproject.openstackinterface.OpenstackPort;
40import org.onosproject.openstackinterface.OpenstackRouter;
41import org.onosproject.openstackinterface.OpenstackSecurityGroup;
42import org.onosproject.openstackinterface.OpenstackSubnet;
sangho93447f12016-02-24 00:33:22 +090043import org.onosproject.openstackinterface.web.OpenstackNetworkCodec;
44import org.onosproject.openstackinterface.web.OpenstackPortCodec;
45import org.onosproject.openstackinterface.web.OpenstackRouterCodec;
46import org.onosproject.openstackinterface.web.OpenstackSecurityGroupCodec;
47import org.onosproject.openstackinterface.web.OpenstackSubnetCodec;
sanghoshinf25d2e02015-11-11 23:07:17 +090048import org.slf4j.Logger;
Jian Li9d616492016-03-09 10:52:49 -080049
50import javax.ws.rs.client.Client;
51import javax.ws.rs.client.ClientBuilder;
52import javax.ws.rs.client.Entity;
53import javax.ws.rs.client.Invocation;
54import javax.ws.rs.client.WebTarget;
sanghoshinf25d2e02015-11-11 23:07:17 +090055import javax.ws.rs.core.MediaType;
56import java.io.IOException;
sangho9f189ec2016-04-05 14:22:17 +090057import java.text.ParseException;
58import java.text.SimpleDateFormat;
59import java.util.Calendar;
sanghoshinf25d2e02015-11-11 23:07:17 +090060import java.util.Collection;
Hyunsun Moonf7895202016-01-12 12:21:48 -080061import java.util.Collections;
sangho9f189ec2016-04-05 14:22:17 +090062import java.util.Date;
sanghoshinf25d2e02015-11-11 23:07:17 +090063import java.util.List;
sangho93447f12016-02-24 00:33:22 +090064import java.util.Set;
65import java.util.concurrent.ExecutorService;
66import java.util.concurrent.Executors;
sangho0c2a3da2016-02-16 13:39:07 +090067import java.util.stream.Collectors;
sanghoshinf25d2e02015-11-11 23:07:17 +090068
69import static com.google.common.base.Preconditions.checkNotNull;
70import static com.google.common.net.MediaType.JSON_UTF_8;
sangho93447f12016-02-24 00:33:22 +090071import static org.onlab.util.Tools.groupedThreads;
72import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
sanghoshinf25d2e02015-11-11 23:07:17 +090073import static org.slf4j.LoggerFactory.getLogger;
74
75/**
76 * Handles REST Calls to Openstack Neutron.
77 *
78 */
sangho0c2a3da2016-02-16 13:39:07 +090079@Service
80@Component(immediate = true)
sangho93447f12016-02-24 00:33:22 +090081public class OpenstackInterfaceManager implements OpenstackInterfaceService {
sanghoshinf25d2e02015-11-11 23:07:17 +090082
sangho5db8e052016-01-29 16:08:23 +090083 private static final String URI_NETWORKS = "networks";
84 private static final String URI_PORTS = "ports";
85 private static final String URI_SUBNETS = "subnets";
86 private static final String URI_SECURITY_GROUPS = "security-groups";
87 private static final String URI_TOKENS = "tokens";
88
Daniel Park3a06c522016-01-28 20:51:12 +090089 private static final String PATH_ROUTERS = "routers";
sangho5db8e052016-01-29 16:08:23 +090090 private static final String PATH_NETWORKS = "networks";
91 private static final String PATH_PORTS = "ports";
92 private static final String PATH_SUBNETS = "subnets";
93 private static final String PATH_ACCESS = "access";
94 private static final String PATH_TOKEN = "token";
95 private static final String PATH_ID = "id";
sangho9f189ec2016-04-05 14:22:17 +090096 private static final String PATH_EXPIRES = "expires";
sangho5db8e052016-01-29 16:08:23 +090097
98 private static final String HEADER_AUTH_TOKEN = "X-Auth-Token";
sangho9f189ec2016-04-05 14:22:17 +090099 private static final String TOKEN_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
sangho5db8e052016-01-29 16:08:23 +0900100
sanghoshinf25d2e02015-11-11 23:07:17 +0900101 private final Logger log = getLogger(getClass());
102 private String neutronUrl;
103 private String keystoneUrl;
104 private String tokenId;
sangho9f189ec2016-04-05 14:22:17 +0900105 private String tokenExpires;
sanghoshinf25d2e02015-11-11 23:07:17 +0900106 private String userName;
107 private String pass;
108
sangho0c2a3da2016-02-16 13:39:07 +0900109 private static final String PORT_NAME = "portName";
110
111 private ApplicationId appId;
112
113 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
114 protected CoreService coreService;
115
116 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
sangho93447f12016-02-24 00:33:22 +0900117 protected NetworkConfigRegistry cfgService;
sangho0c2a3da2016-02-16 13:39:07 +0900118
sangho93447f12016-02-24 00:33:22 +0900119 private InternalConfigListener internalConfigListener = new InternalConfigListener();
120 private ExecutorService networkEventExcutorService =
121 Executors.newSingleThreadExecutor(groupedThreads("onos/openstackinterface", "config-event"));
122
123 private final Set<ConfigFactory> factories = ImmutableSet.of(
sangho48907542016-03-28 16:07:07 +0900124 new ConfigFactory<ApplicationId, OpenstackInterfaceConfig>(APP_SUBJECT_FACTORY,
125 OpenstackInterfaceConfig.class,
sangho93447f12016-02-24 00:33:22 +0900126 "openstackinterface") {
127 @Override
sangho48907542016-03-28 16:07:07 +0900128 public OpenstackInterfaceConfig createConfig() {
129 return new OpenstackInterfaceConfig();
sangho93447f12016-02-24 00:33:22 +0900130 }
131 }
132 );
133
sangho0c2a3da2016-02-16 13:39:07 +0900134
135 @Activate
136 protected void activate() {
137 appId = coreService
sangho93447f12016-02-24 00:33:22 +0900138 .registerApplication("org.onosproject.openstackinterface");
139
140 factories.forEach(cfgService::registerConfigFactory);
141 cfgService.addListener(internalConfigListener);
sangho0c2a3da2016-02-16 13:39:07 +0900142
143 log.info("started");
144 }
145
146 @Deactivate
147 protected void deactivate() {
sangho93447f12016-02-24 00:33:22 +0900148 cfgService.removeListener(internalConfigListener);
149 factories.forEach(cfgService::unregisterConfigFactory);
sangho0c2a3da2016-02-16 13:39:07 +0900150 log.info("stopped");
sanghoshinf25d2e02015-11-11 23:07:17 +0900151 }
152
153 /**
154 * Returns network information stored in Neutron.
155 *
156 * @return List of OpenstackNetwork
157 */
158 public Collection<OpenstackNetwork> getNetworks() {
159
Jian Li9d616492016-03-09 10:52:49 -0800160 Invocation.Builder builder = getClientBuilder(neutronUrl + URI_NETWORKS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900161 String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
sangho5db8e052016-01-29 16:08:23 +0900162 header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
sanghoshinf25d2e02015-11-11 23:07:17 +0900163
Hyunsun Moonf7895202016-01-12 12:21:48 -0800164 log.debug("networks response:" + response);
165
sanghoshinf25d2e02015-11-11 23:07:17 +0900166 ObjectMapper mapper = new ObjectMapper();
167 List<OpenstackNetwork> openstackNetworks = Lists.newArrayList();
168 try {
169 ObjectNode node = (ObjectNode) mapper.readTree(response);
sangho5db8e052016-01-29 16:08:23 +0900170 ArrayNode networkList = (ArrayNode) node.path(PATH_NETWORKS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900171 OpenstackNetworkCodec networkCodec = new OpenstackNetworkCodec();
172 networkList.forEach(n -> openstackNetworks.add(networkCodec.decode((ObjectNode) n, null)));
173 } catch (IOException e) {
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800174 log.warn("getNetworks()", e);
sanghoshinf25d2e02015-11-11 23:07:17 +0900175 }
176
Hyunsun Moonf7895202016-01-12 12:21:48 -0800177 openstackNetworks.removeAll(Collections.singleton(null));
sanghoshinf25d2e02015-11-11 23:07:17 +0900178 openstackNetworks.forEach(n -> log.debug("network ID: {}", n.id()));
179
180 return openstackNetworks;
181 }
182
183 /**
184 * Returns port information stored in Neutron.
185 *
186 * @return List of OpenstackPort
187 */
188 public Collection<OpenstackPort> getPorts() {
189
Jian Li9d616492016-03-09 10:52:49 -0800190 Invocation.Builder builder = getClientBuilder(neutronUrl + URI_PORTS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900191 String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
sangho5db8e052016-01-29 16:08:23 +0900192 header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
sanghoshinf25d2e02015-11-11 23:07:17 +0900193
194 ObjectMapper mapper = new ObjectMapper();
195 List<OpenstackPort> openstackPorts = Lists.newArrayList();
196 try {
197 ObjectNode node = (ObjectNode) mapper.readTree(response);
sangho5db8e052016-01-29 16:08:23 +0900198 ArrayNode portList = (ArrayNode) node.path(PATH_PORTS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900199 OpenstackPortCodec portCodec = new OpenstackPortCodec();
200 portList.forEach(p -> openstackPorts.add(portCodec.decode((ObjectNode) p, null)));
201 } catch (IOException e) {
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800202 log.warn("getPorts()", e);
sanghoshinf25d2e02015-11-11 23:07:17 +0900203 }
204
205 log.debug("port response:" + response);
206 openstackPorts.forEach(n -> log.debug("port ID: {}", n.id()));
207
208 return openstackPorts;
209 }
210
Daniel Park3a06c522016-01-28 20:51:12 +0900211 public Collection<OpenstackRouter> getRouters() {
Jian Li9d616492016-03-09 10:52:49 -0800212 Invocation.Builder builder = getClientBuilder(neutronUrl + PATH_ROUTERS);
Daniel Park3a06c522016-01-28 20:51:12 +0900213 String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
214 header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
215
216 ObjectMapper mapper = new ObjectMapper();
217 List<OpenstackRouter> openstackRouters = Lists.newArrayList();
218
219 try {
220 ObjectNode node = (ObjectNode) mapper.readTree(response);
221 ArrayNode routerList = (ArrayNode) node.path(PATH_ROUTERS);
222 OpenstackRouterCodec openstackRouterCodec = new OpenstackRouterCodec();
223 routerList.forEach(r -> openstackRouters
224 .add(openstackRouterCodec.decode((ObjectNode) r, null)));
225 } catch (IOException e) {
226 log.warn("getRouters()", e);
227 }
228
229 log.debug("router response:" + response);
230 openstackRouters.forEach(r -> log.debug("router ID: {}", r.id()));
231
232 return openstackRouters;
233 }
234
sanghoshinf25d2e02015-11-11 23:07:17 +0900235 /**
236 * Returns Subnet information in Neutron.
237 *
238 * @return List of OpenstackSubnet
239 */
240 public Collection<OpenstackSubnet> getSubnets() {
Jian Li9d616492016-03-09 10:52:49 -0800241 Invocation.Builder builder = getClientBuilder(neutronUrl + URI_SUBNETS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900242 String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
sangho5db8e052016-01-29 16:08:23 +0900243 header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
sanghoshinf25d2e02015-11-11 23:07:17 +0900244
245 ObjectMapper mapper = new ObjectMapper();
246 List<OpenstackSubnet> subnets = Lists.newArrayList();
247 try {
248 ObjectNode node = (ObjectNode) mapper.readTree(response);
sangho5db8e052016-01-29 16:08:23 +0900249 ArrayNode subnetList = (ArrayNode) node.path(PATH_SUBNETS);
sanghoshinf25d2e02015-11-11 23:07:17 +0900250 OpenstackSubnetCodec subnetCodec = new OpenstackSubnetCodec();
251 subnetList.forEach(s -> subnets.add(subnetCodec.decode((ObjectNode) s, null)));
252 } catch (IOException e) {
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800253 log.warn("getSubnets()", e);
sanghoshinf25d2e02015-11-11 23:07:17 +0900254 }
255
256 log.debug("subnets response:" + response);
257 subnets.forEach(s -> log.debug("subnet ID: {}", s.id()));
258
259 return subnets;
260 }
261
sangho5db8e052016-01-29 16:08:23 +0900262 /**
263 * Extracts OpenstackSecurityGroup information for the ID.
264 *
265 * @param id Security Group ID
266 * @return OpenstackSecurityGroup object or null if fails
267 */
268 public OpenstackSecurityGroup getSecurityGroup(String id) {
Jian Li9d616492016-03-09 10:52:49 -0800269 Invocation.Builder builder = getClientBuilder(neutronUrl + URI_SECURITY_GROUPS + "/" + id);
sangho5db8e052016-01-29 16:08:23 +0900270 String response = builder.accept(MediaType.APPLICATION_JSON_TYPE).
271 header(HEADER_AUTH_TOKEN, getToken()).get(String.class);
272
273 ObjectMapper mapper = new ObjectMapper();
274 OpenstackSecurityGroup securityGroup = null;
275 try {
276 ObjectNode node = (ObjectNode) mapper.readTree(response);
277 OpenstackSecurityGroupCodec sgCodec = new OpenstackSecurityGroupCodec();
278 securityGroup = sgCodec.decode(node, null);
279 } catch (IOException e) {
280 log.warn("getSecurityGroup()", e);
281 }
282
283 return securityGroup;
284 }
285
Jian Li9d616492016-03-09 10:52:49 -0800286 private Invocation.Builder getClientBuilder(String uri) {
287 Client client = ClientBuilder.newClient();
288 WebTarget wt = client.target(uri);
289 return wt.request(JSON_UTF_8.toString());
sanghoshinf25d2e02015-11-11 23:07:17 +0900290 }
291
292 private String getToken() {
sangho9f189ec2016-04-05 14:22:17 +0900293 if (!isTokenValid()) {
sanghoshinf25d2e02015-11-11 23:07:17 +0900294 String request = "{\"auth\": {\"tenantName\": \"admin\", " +
295 "\"passwordCredentials\": {\"username\": \"" +
296 userName + "\",\"password\": \"" + pass + "\"}}}";
Jian Li9d616492016-03-09 10:52:49 -0800297 Invocation.Builder builder = getClientBuilder(keystoneUrl + URI_TOKENS);
298 String response = builder.accept(MediaType.APPLICATION_JSON).post(Entity.json(request), String.class);
sanghoshinf25d2e02015-11-11 23:07:17 +0900299
300 ObjectMapper mapper = new ObjectMapper();
301 try {
302 ObjectNode node = (ObjectNode) mapper.readTree(response);
sangho5db8e052016-01-29 16:08:23 +0900303 tokenId = node.path(PATH_ACCESS).path(PATH_TOKEN).path(PATH_ID).asText();
sangho9f189ec2016-04-05 14:22:17 +0900304 tokenExpires = node.path(PATH_ACCESS).path(PATH_TOKEN).path(PATH_EXPIRES).asText();
sanghoshinf25d2e02015-11-11 23:07:17 +0900305 } catch (IOException e) {
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800306 log.warn("getToken()", e);
sanghoshinf25d2e02015-11-11 23:07:17 +0900307 }
308 log.debug("token response:" + response);
309 }
310
311 return tokenId;
312 }
313
sangho9f189ec2016-04-05 14:22:17 +0900314 private boolean isTokenValid() {
315
316 if (tokenExpires == null || tokenId == null || tokenExpires.isEmpty()) {
317 return false;
318 }
319
320 try {
321 SimpleDateFormat dateFormat = new SimpleDateFormat(TOKEN_DATE_FORMAT);
322 Date exireDate = dateFormat.parse(tokenExpires);
323
324 Calendar today = Calendar.getInstance();
325 if (exireDate.after(today.getTime())) {
326 return true;
327 }
328 } catch (ParseException e) {
329 log.error("Token parse exception error : {}", e.getMessage());
330 return false;
331 }
332
333 log.debug("token is Invalid");
334 return false;
sanghoshinf25d2e02015-11-11 23:07:17 +0900335 }
336
sangho0c2a3da2016-02-16 13:39:07 +0900337 @Override
338 public Collection<OpenstackPort> ports(String networkId) {
339 return getPorts().stream()
340 .filter(port -> port.networkId().equals(networkId))
341 .collect(Collectors.toList());
342 }
343
344 @Override
345 public Collection<OpenstackPort> ports() {
346 return getPorts();
347 }
348
349 @Override
350 public OpenstackPort port(Port port) {
351 String uuid = port.annotations().value(PORT_NAME).substring(3);
352 return getPorts().stream()
353 .filter(p -> p.id().startsWith(uuid))
354 .findAny().orElse(null);
355 }
356
357 @Override
358 public OpenstackPort port(String portId) {
359 return getPorts().stream()
360 .filter(p -> p.id().equals(portId))
361 .findAny().orElse(null);
362 }
363
364 @Override
365 public OpenstackNetwork network(String networkId) {
Hyunsun Moonb0f09be2016-02-23 04:21:42 -0800366 Collection<OpenstackSubnet> subnets = getSubnets().stream()
367 .filter(s -> s.networkId().equals(networkId))
368 .collect(Collectors.toList());
369
370 OpenstackNetwork openstackNetwork = getNetworks().stream()
sangho0c2a3da2016-02-16 13:39:07 +0900371 .filter(n -> n.id().equals(networkId))
372 .findAny().orElse(null);
Hyunsun Moonb0f09be2016-02-23 04:21:42 -0800373
374 if (openstackNetwork == null) {
375 return null;
376 }
377
378 return OpenstackNetwork.builder()
379 .id(openstackNetwork.id())
380 .name(openstackNetwork.name())
381 .networkType(openstackNetwork.networkType())
382 .segmentId(openstackNetwork.segmentId())
383 .tenantId(openstackNetwork.tenantId())
384 .subnets(subnets)
385 .build();
sangho0c2a3da2016-02-16 13:39:07 +0900386 }
387
388 @Override
389 public Collection<OpenstackNetwork> networks() {
390 return getNetworks();
391 }
392
393 @Override
394 public OpenstackSubnet subnet(String subnetId) {
395 return getSubnets().stream()
396 .filter(subnet -> subnet.id().equals(subnetId))
397 .findAny().orElse(null);
398 }
399
400 @Override
401 public Collection<OpenstackSubnet> subnets() {
402 return getSubnets();
403 }
404
405 @Override
406 public Collection<OpenstackRouter> routers() {
407 return getRouters();
408 }
409
410 @Override
411 public OpenstackRouter router(String routerId) {
412 return getRouters().stream()
413 .filter(router -> router.id().equals(routerId))
414 .findAny().orElse(null);
415 }
416
sangho93447f12016-02-24 00:33:22 +0900417 private class InternalConfigListener implements NetworkConfigListener {
sangho0c2a3da2016-02-16 13:39:07 +0900418
sangho93447f12016-02-24 00:33:22 +0900419 public void configureNetwork() {
sangho48907542016-03-28 16:07:07 +0900420 OpenstackInterfaceConfig cfg =
421 cfgService.getConfig(appId, OpenstackInterfaceConfig.class);
sangho93447f12016-02-24 00:33:22 +0900422 if (cfg == null) {
423 log.error("There is no openstack server information in config.");
424 return;
425 }
426
427 neutronUrl = checkNotNull(cfg.neutronServer());
428 keystoneUrl = checkNotNull(cfg.keystoneServer());
429 userName = checkNotNull(cfg.userName());
430 pass = checkNotNull(cfg.password());
431 }
432
433 @Override
434 public void event(NetworkConfigEvent event) {
435 if (((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
436 event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)) &&
sangho48907542016-03-28 16:07:07 +0900437 event.configClass().equals(OpenstackInterfaceConfig.class)) {
sangho93447f12016-02-24 00:33:22 +0900438
439 log.info("Network configuration changed");
440 networkEventExcutorService.execute(this::configureNetwork);
441 }
442 }
sangho0c2a3da2016-02-16 13:39:07 +0900443 }
Jian Li9d616492016-03-09 10:52:49 -0800444}