2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.hosttracker.northbound;
11 import java.net.InetAddress;
12 import java.net.UnknownHostException;
13 import java.util.HashSet;
14 import java.util.List;
17 import javax.ws.rs.Consumes;
18 import javax.ws.rs.DELETE;
19 import javax.ws.rs.GET;
20 import javax.ws.rs.PUT;
21 import javax.ws.rs.Path;
22 import javax.ws.rs.PathParam;
23 import javax.ws.rs.Produces;
24 import javax.ws.rs.core.Context;
25 import javax.ws.rs.core.MediaType;
26 import javax.ws.rs.core.Response;
27 import javax.ws.rs.core.SecurityContext;
28 import javax.ws.rs.core.UriInfo;
30 import org.codehaus.enunciate.jaxrs.ResponseCode;
31 import org.codehaus.enunciate.jaxrs.StatusCodes;
32 import org.codehaus.enunciate.jaxrs.TypeHint;
33 import org.opendaylight.controller.containermanager.IContainerManager;
34 import org.opendaylight.controller.hosttracker.IfIptoHost;
35 import org.opendaylight.controller.hosttracker.hostAware.HostNodeConnector;
36 import org.opendaylight.controller.northbound.commons.RestMessages;
37 import org.opendaylight.controller.northbound.commons.exception.BadRequestException;
38 import org.opendaylight.controller.northbound.commons.exception.ResourceConflictException;
39 import org.opendaylight.controller.northbound.commons.exception.ResourceNotFoundException;
40 import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
41 import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
42 import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
43 import org.opendaylight.controller.sal.authorization.Privilege;
44 import org.opendaylight.controller.sal.core.Node;
45 import org.opendaylight.controller.sal.core.NodeConnector;
46 import org.opendaylight.controller.sal.utils.GlobalConstants;
47 import org.opendaylight.controller.sal.utils.ServiceHelper;
48 import org.opendaylight.controller.sal.utils.Status;
49 import org.opendaylight.controller.switchmanager.ISwitchManager;
52 * Host Tracker Northbound REST APIs.<br>
53 * This class provides REST APIs to track host location in a network. Host
54 * Location is represented by Host node connector which is essentially a logical
55 * entity that represents a Switch/Port. A host is represented by it's
56 * IP-address and mac-address.
60 * Authentication scheme : <b>HTTP Basic</b><br>
61 * Authentication realm : <b>opendaylight</b><br>
62 * Transport : <b>HTTP and HTTPS</b><br>
64 * HTTPS Authentication is disabled by default.
69 public class HostTrackerNorthbound {
71 private String username;
74 public void setSecurityContext(SecurityContext context) {
75 if (context != null && context.getUserPrincipal() != null) username = context.getUserPrincipal().getName();
78 protected String getUserName() {
82 private IfIptoHost getIfIpToHostService(String containerName) {
83 IContainerManager containerManager = (IContainerManager) ServiceHelper.getGlobalInstance(
84 IContainerManager.class, this);
85 if (containerManager == null) {
86 throw new ServiceUnavailableException("Container " + RestMessages.SERVICEUNAVAILABLE.toString());
89 boolean found = false;
90 List<String> containerNames = containerManager.getContainerNames();
91 for (String cName : containerNames) {
92 if (cName.trim().equalsIgnoreCase(containerName.trim())) {
99 throw new ResourceNotFoundException(containerName + " " + RestMessages.NOCONTAINER.toString());
102 IfIptoHost hostTracker = (IfIptoHost) ServiceHelper.getInstance(IfIptoHost.class, containerName, this);
103 if (hostTracker == null) {
104 throw new ServiceUnavailableException("Host Tracker " + RestMessages.SERVICEUNAVAILABLE.toString());
110 private Hosts convertHosts(Set<HostNodeConnector> hostNodeConnectors) {
111 if(hostNodeConnectors == null) {
114 Set<HostConfig> hosts = new HashSet<HostConfig>();
115 for(HostNodeConnector hnc : hostNodeConnectors) {
116 hosts.add(HostConfig.convert(hnc));
118 return new Hosts(hosts);
122 * Returns a list of all Hosts : both configured via PUT API and dynamically
123 * learnt on the network.
125 * @param containerName
126 * Name of the Container. The Container name for the base
127 * controller is "default".
128 * @return List of Active Hosts.
134 * http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/active
136 * Response body in XML
139 *  <hostConfig>
140 *   <dataLayerAddress>00:00:00:00:01:01</dataLayerAddress>
141 *   <networkAddress>1.1.1.1</networkAddress>
142 *   <nodeType>OF</nodeType>
143 *   <nodeId>00:00:00:00:00:00:00:01</nodeId>
144 *   <nodeConnectorType>OF</nodeConnectorType>
145 *   <nodeConnectorId>9</nodeConnectorId>
146 *   <vlan>0</vlan>
147 *   <staticHost>false</staticHost>
148 *  </hostConfig>
149 *  <hostConfig>
150 *   <dataLayerAddress>00:00:00:00:02:02</dataLayerAddress>
151 *   <networkAddress>2.2.2.2</networkAddress>
152 *   <nodeType>OF</nodeType>
153 *   <nodeId>00:00:00:00:00:00:00:02</nodeId>
154 *   <nodeConnectorType>OF</nodeConnectorType>
155 *   <nodeConnectorId>5</nodeConnectorId>
156 *   <vlan>0</vlan>
157 *   <staticHost>false</staticHost>
158 *  </hostConfig>
161 * Response body in JSON:
164 *  "hostConfig":[
166 *    "dataLayerAddress":"00:00:00:00:01:01",
167 *    "nodeType":"OF",
168 *    "nodeId":"00:00:00:00:00:00:00:01",
169 *    "nodeConnectorType":"OF",
170 *    "nodeConnectorId":"9",
171 *    "vlan":"0",
172 *    "staticHost":"false",
173 *    "networkAddress":"1.1.1.1"
176 *    "dataLayerAddress":"00:00:00:00:02:02",
177 *    "nodeType":"OF",
178 *    "nodeId":"00:00:00:00:00:00:00:02",
179 *    "nodeConnectorType":"OF",
180 *    "nodeConnectorId":"5",
181 *    "vlan":"0",
182 *    "staticHost":"false",
183 *    "networkAddress":"2.2.2.2"
189 @Path("/{containerName}/hosts/active")
191 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
192 @TypeHint(Hosts.class)
194 @ResponseCode(code = 200, condition = "Operation successful"),
195 @ResponseCode(code = 404, condition = "The containerName is not found"),
196 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
197 public Hosts getActiveHosts(@PathParam("containerName") String containerName) {
199 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
200 throw new UnauthorizedException("User is not authorized to perform this operation on container "
203 IfIptoHost hostTracker = getIfIpToHostService(containerName);
204 return convertHosts(hostTracker.getAllHosts());
208 * Returns a list of Hosts that are statically configured and are connected
209 * to a NodeConnector that is down.
211 * @param containerName
212 * Name of the Container. The Container name for the base
213 * controller is "default".
214 * @return List of inactive Hosts.
220 * http://localhost:8080/controller/nb/v2/hosttracker/default/hosts/inactive
222 * Response body in XML
225 *  <hostConfig>
226 *   <dataLayerAddress>00:00:00:00:01:01</dataLayerAddress>
227 *   <networkAddress>1.1.1.1</networkAddress>
228 *   <nodeType>OF</nodeType>
229 *   <nodeId>00:00:00:00:00:00:00:01</nodeId>
230 *   <nodeConnectorType>OF</nodeConnectorType>
231 *   <nodeConnectorId>9</nodeConnectorId>
232 *   <vlan>0</vlan>
233 *   <staticHost>false</staticHost>
234 *  </hostConfig>
235 *  <hostConfig>
236 *   <dataLayerAddress>00:00:00:00:02:02</dataLayerAddress>
237 *   <networkAddress>2.2.2.2</networkAddress>
238 *   <nodeType>OF</nodeType>
239 *   <nodeId>00:00:00:00:00:00:00:02</nodeId>
240 *   <nodeConnectorType>OF</nodeConnectorType>
241 *   <nodeConnectorId>5</nodeConnectorId>
242 *   <vlan>0</vlan>
243 *   <staticHost>false</staticHost>
244 *  </hostConfig>
247 * Response body in JSON:
250 *  "hostConfig":[
252 *    "dataLayerAddress":"00:00:00:00:01:01",
253 *    "nodeType":"OF",
254 *    "nodeId":"00:00:00:00:00:00:00:01",
255 *    "nodeConnectorType":"OF",
256 *    "nodeConnectorId":"9",
257 *    "vlan":"0",
258 *    "staticHost":"false",
259 *    "networkAddress":"1.1.1.1"
262 *    "dataLayerAddress":"00:00:00:00:02:02",
263 *    "nodeType":"OF",
264 *    "nodeId":"00:00:00:00:00:00:00:02",
265 *    "nodeConnectorType":"OF",
266 *    "nodeConnectorId":"5",
267 *    "vlan":"0",
268 *    "staticHost":"false",
269 *    "networkAddress":"2.2.2.2"
275 @Path("/{containerName}/hosts/inactive")
277 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
278 @TypeHint(Hosts.class)
280 @ResponseCode(code = 200, condition = "Operation successful"),
281 @ResponseCode(code = 404, condition = "The containerName is not found"),
282 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
283 public Hosts getInactiveHosts(
284 @PathParam("containerName") String containerName) {
285 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
286 throw new UnauthorizedException("User is not authorized to perform this operation on container "
289 IfIptoHost hostTracker = getIfIpToHostService(containerName);
290 return convertHosts(hostTracker.getInactiveStaticHosts());
294 * Returns a host that matches the IP Address value passed as parameter.
296 * @param containerName
297 * Name of the Container. The Container name for the base
298 * controller is "default".
299 * @param networkAddress
300 * IP Address being looked up
301 * @return host that matches the IP Address
307 * http://localhost:8080/controller/nb/v2/hosttracker/default/address/1.1.1.1
309 * Response body in XML
312 *  <dataLayerAddress>00:00:00:00:01:01</dataLayerAddress>
313 *  <networkAddress>1.1.1.1</networkAddress>
314 *  <nodeType>OF</nodeType>
315 *  <nodeId>00:00:00:00:00:00:00:01</nodeId>
316 *  <nodeConnectorType>OF</nodeConnectorType>
317 *  <nodeConnectorId>9</nodeConnectorId>
318 *  <vlan>0</vlan>
319 *  <staticHost>false</staticHost>
320 * </hostConfig>
322 * Response body in JSON:
325 *  "dataLayerAddress":"00:00:00:00:01:01",
326 *  "nodeType":"OF",
327 *  "nodeId":"00:00:00:00:00:00:00:01",
328 *  "nodeConnectorType":"OF",
329 *  "nodeConnectorId":"9",
331 *  "staticHost":"false",
332 *  "networkAddress":"1.1.1.1"
336 @Path("/{containerName}/address/{networkAddress}")
338 @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
339 @TypeHint(HostConfig.class)
341 @ResponseCode(code = 200, condition = "Operation successful"),
342 @ResponseCode(code = 400, condition = "Invalid IP specified in networkAddress parameter"),
343 @ResponseCode(code = 404, condition = "The containerName is not found"),
344 @ResponseCode(code = 503, condition = "One or more of Controller Services are unavailable") })
345 public HostConfig getHostDetails(
346 @PathParam("containerName") String containerName,
347 @PathParam("networkAddress") String networkAddress) {
348 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.READ, this)) {
349 throw new UnauthorizedException("User is not authorized to perform this operation on container "
352 IfIptoHost hostTracker = getIfIpToHostService(containerName);
356 ip = InetAddress.getByName(networkAddress);
357 } catch (UnknownHostException e) {
358 throw new BadRequestException(RestMessages.INVALIDADDRESS.toString() + " " + networkAddress);
360 for (HostNodeConnector host : hostTracker.getAllHosts()) {
361 if (host.getNetworkAddress().equals(ip)) {
362 return HostConfig.convert(host);
365 throw new ResourceNotFoundException(RestMessages.NOHOST.toString());
369 * Add a Static Host configuration. If a host by the given address already
370 * exists, this method will respond with a non-successful status response.
372 * @param containerName
373 * Name of the Container. The Container name for the base
374 * controller is "default".
375 * @param networkAddress
378 * Host Config Details
379 * @return Response as dictated by the HTTP Response Status code
386 * http://localhost:8080/controller/nb/v2/hosttracker/default/address/1.1.1.1
388 * Request body in XML
391 *  <dataLayerAddress>00:00:00:00:01:01</dataLayerAddress>
392 *  <networkAddress>1.1.1.1</networkAddress>
393 *  <nodeType>OF</nodeType>
394 *  <nodeId>00:00:00:00:00:00:00:01</nodeId>
395 *  <nodeConnectorType>OF</nodeConnectorType>
396 *  <nodeConnectorId>9</nodeConnectorId>
397 *  <vlan>0</vlan>
398 *  <staticHost>false</staticHost>
399 * </hostConfig>
401 * Request body in JSON:
404 *  "dataLayerAddress":"00:00:00:00:01:01",
405 *  "nodeType":"OF",
406 *  "nodeId":"00:00:00:00:00:00:00:01",
407 *  "nodeConnectorType":"OF",
408 *  "nodeConnectorId":"9",
410 *  "staticHost":"false",
411 *  "networkAddress":"1.1.1.1"
416 @Path("/{containerName}/address/{networkAddress}")
418 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
420 @ResponseCode(code = 201, condition = "Static host created successfully"),
421 @ResponseCode(code = 400, condition = "Invalid parameters specified, see response body for details"),
422 @ResponseCode(code = 404, condition = "The container or resource is not found"),
423 @ResponseCode(code = 409, condition = "Resource conflict, see response body for details"),
424 @ResponseCode(code = 503, condition = "One or more of Controller services are unavailable") })
425 public Response addHost(@Context UriInfo uriInfo, @PathParam("containerName") String containerName,
426 @PathParam("networkAddress") String networkAddress,
427 @TypeHint(HostConfig.class) HostConfig hostConfig) {
429 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
430 return Response.status(Response.Status.UNAUTHORIZED)
431 .entity("User is not authorized to perform this operation on container " + containerName)
434 handleDefaultDisabled(containerName);
436 IfIptoHost hostTracker = getIfIpToHostService(containerName);
438 HostConfig hc = hostConfig;
439 if (!networkAddress.equals(hc.getNetworkAddress())) {
440 return Response.status(Response.Status.CONFLICT)
441 .entity("Resource name in config object doesn't match URI")
444 if (!hc.isStaticHost()) {
445 return Response.status(Response.Status.BAD_REQUEST)
446 .entity("Can only add static host.")
449 Node node = handleNodeAvailability(containerName, hc.getNodeType(), hc.getNodeId());
450 NodeConnector nc = NodeConnector.fromStringNoNode(hc.getNodeConnectorType(), hc.getNodeConnectorId(), node);
452 Status status = hostTracker.addStaticHost(networkAddress, hc.getDataLayerAddress(), nc, hc.getVlan());
453 if (status.isSuccess()) {
454 NorthboundUtils.auditlog("Static Host", username, "added", networkAddress, containerName);
455 return Response.created(uriInfo.getRequestUri()).build();
458 return NorthboundUtils.getResponse(status);
462 * Delete a Static Host configuration
464 * @param containerName
465 * Name of the Container. The Container name for the base
466 * controller is "default".
467 * @param networkAddress
469 * @return Response as dictated by the HTTP Response code.
474 * http://localhost:8080/controller/nb/v2/hosttracker/default/address/1.1.1.1
478 @Path("/{containerName}/address/{networkAddress}")
480 @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
482 @ResponseCode(code = 204, condition = "Static host deleted successfully"),
483 @ResponseCode(code = 404, condition = "The container or a specified resource was not found"),
484 @ResponseCode(code = 406, condition = "Cannot operate on Default Container when other Containers are active"),
485 @ResponseCode(code = 503, condition = "One or more of Controller service is unavailable") })
486 public Response deleteHost(
487 @PathParam(value = "containerName") String containerName,
488 @PathParam(value = "networkAddress") String networkAddress) {
490 if (!NorthboundUtils.isAuthorized(getUserName(), containerName, Privilege.WRITE, this)) {
491 return Response.status(Response.Status.UNAUTHORIZED)
492 .entity("User is not authorized to perform this operation on container " + containerName)
495 handleDefaultDisabled(containerName);
496 IfIptoHost hostTracker = getIfIpToHostService(containerName);
498 Status status = hostTracker.removeStaticHost(networkAddress);
499 if (status.isSuccess()) {
500 NorthboundUtils.auditlog("Static Host", username, "removed", networkAddress, containerName);
501 return Response.noContent().build();
503 return NorthboundUtils.getResponse(status);
507 private void handleDefaultDisabled(String containerName) {
508 IContainerManager containerManager = (IContainerManager) ServiceHelper
509 .getGlobalInstance(IContainerManager.class, this);
510 if (containerManager == null) {
511 throw new ServiceUnavailableException(
512 RestMessages.SERVICEUNAVAILABLE.toString());
514 if (containerName.equals(GlobalConstants.DEFAULT.toString())
515 && containerManager.hasNonDefaultContainer()) {
516 throw new ResourceConflictException(
517 RestMessages.DEFAULTDISABLED.toString());
521 private Node handleNodeAvailability(String containerName, String nodeType, String nodeId) {
523 Node node = Node.fromString(nodeType, nodeId);
525 throw new ResourceNotFoundException(nodeId + " : "
526 + RestMessages.NONODE.toString());
529 ISwitchManager sm = (ISwitchManager) ServiceHelper.getInstance(
530 ISwitchManager.class, containerName, this);
533 throw new ServiceUnavailableException("Switch Manager "
534 + RestMessages.SERVICEUNAVAILABLE.toString());
537 if (!sm.getNodes().contains(node)) {
538 throw new ResourceNotFoundException(node.toString() + " : "
539 + RestMessages.NONODE.toString());