aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/web/api/src/main/java
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/api/src/main/java
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/api/src/main/java')
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/AbstractMapper.java77
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/BadRequestMapper.java32
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/EntityNotFoundMapper.java32
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java31
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalStateExceptionMapper.java31
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/NotFoundMapper.java34
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServerErrorMapper.java30
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServiceNotFoundMapper.java32
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/WebApplicationExceptionMapper.java45
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/package-info.java20
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/ApiDocManager.java73
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/package-info.java20
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApiDocResource.java179
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java150
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java95
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ComponentConfigWebResource.java118
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java610
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigWebResource.java73
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/DevicesWebResource.java106
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java190
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java230
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java221
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/JsonBodyWriter.java61
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/LinksWebResource.java102
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java305
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/PathsWebResource.java85
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java95
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/TopologyWebResource.java220
-rw-r--r--framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/package-info.java20
29 files changed, 3317 insertions, 0 deletions
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/AbstractMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/AbstractMapper.java
new file mode 100644
index 00000000..f49202dd
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/AbstractMapper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2014-2015 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.rest.exceptions;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Base exception mapper implementation.
+ */
+public abstract class AbstractMapper<E extends Throwable> implements ExceptionMapper<E> {
+
+ /**
+ * Returns the response status to be given when the exception occurs.
+ *
+ * @return response status
+ */
+ protected abstract Response.Status responseStatus();
+
+ @Override
+ public Response toResponse(E exception) {
+ return response(responseStatus(), exception).build();
+ }
+
+ /**
+ * Produces a response builder primed with the supplied status code
+ * and JSON entity with the status code and exception message.
+ *
+ * @param status response status
+ * @param exception exception to encode
+ * @return response builder
+ */
+ protected Response.ResponseBuilder response(Response.Status status,
+ Throwable exception) {
+ ObjectMapper mapper = new ObjectMapper();
+ String message = messageFrom(exception);
+ ObjectNode result = mapper.createObjectNode()
+ .put("code", status.getStatusCode())
+ .put("message", message);
+ return Response.status(status).entity(result.toString());
+ }
+
+ /**
+ * Produces a response message from the supplied exception. Either it will
+ * use the exception message, if there is one, or it will use the top
+ * stack-frame message.
+ *
+ * @param exception exception from which to produce a message
+ * @return response message
+ */
+ protected String messageFrom(Throwable exception) {
+ if (isNullOrEmpty(exception.getMessage())) {
+ StackTraceElement[] trace = exception.getStackTrace();
+ return trace.length == 0 ? "Unknown error" : trace[0].toString();
+ }
+ return exception.getMessage();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/BadRequestMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/BadRequestMapper.java
new file mode 100644
index 00000000..89b13685
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/BadRequestMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 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.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import java.io.IOException;
+
+/**
+ * Mapper for IO exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class BadRequestMapper extends AbstractMapper<IOException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.BAD_REQUEST;
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/EntityNotFoundMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/EntityNotFoundMapper.java
new file mode 100644
index 00000000..9e42a56e
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/EntityNotFoundMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2015 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.rest.exceptions;
+
+import org.onlab.util.ItemNotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for service not found exceptions to the NOT_FOUND response code.
+ */
+@Provider
+public class EntityNotFoundMapper extends AbstractMapper<ItemNotFoundException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.NOT_FOUND;
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java
new file mode 100644
index 00000000..2d7a1eae
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalArgumentExceptionMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014-2015 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.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for illegal argument exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class IllegalArgumentExceptionMapper extends AbstractMapper<IllegalArgumentException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.BAD_REQUEST;
+ }
+}
+
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalStateExceptionMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalStateExceptionMapper.java
new file mode 100644
index 00000000..a9f977ce
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/IllegalStateExceptionMapper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015 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.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for illegal state exceptions to the BAD_REQUEST response code.
+ */
+@Provider
+public class IllegalStateExceptionMapper extends AbstractMapper<IllegalStateException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.CONFLICT;
+ }
+}
+
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/NotFoundMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/NotFoundMapper.java
new file mode 100644
index 00000000..2bf3614d
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/NotFoundMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 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.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import com.sun.jersey.api.NotFoundException;
+
+/**
+ * Mapper for api not found exceptions to the NOT_FOUND response code.
+ */
+@Provider
+public class NotFoundMapper extends AbstractMapper<NotFoundException> {
+
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.NOT_FOUND;
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServerErrorMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServerErrorMapper.java
new file mode 100644
index 00000000..5a9050d0
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServerErrorMapper.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014-2015 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.rest.exceptions;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for service not found exceptions to the INTERNAL_SERVER_ERROR response code.
+ */
+@Provider
+public class ServerErrorMapper extends AbstractMapper<RuntimeException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.INTERNAL_SERVER_ERROR;
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServiceNotFoundMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServiceNotFoundMapper.java
new file mode 100644
index 00000000..69e55088
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/ServiceNotFoundMapper.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014-2015 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.rest.exceptions;
+
+import org.onlab.osgi.ServiceNotFoundException;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Mapper for service not found exceptions to the SERVICE_UNAVAILABLE response code.
+ */
+@Provider
+public class ServiceNotFoundMapper extends AbstractMapper<ServiceNotFoundException> {
+ @Override
+ protected Response.Status responseStatus() {
+ return Response.Status.SERVICE_UNAVAILABLE;
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/WebApplicationExceptionMapper.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/WebApplicationExceptionMapper.java
new file mode 100644
index 00000000..86d8434f
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/WebApplicationExceptionMapper.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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.rest.exceptions;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Exception mapper for WebApplicationExceptions.
+ */
+@Provider
+public class WebApplicationExceptionMapper extends AbstractMapper<WebApplicationException> {
+
+ /**
+ * Extracts and returns the response from a WebApplicationException.
+ *
+ * @param e WebApplicationException that was thrown
+ * @return precomputed Response from the exception
+ */
+ @Override
+ public Response toResponse(WebApplicationException e) {
+ return e.getResponse();
+ }
+
+ @Override
+ public Response.Status responseStatus() {
+ // This should never be called because this class overrides toResponse()
+ throw new UnsupportedOperationException(
+ "responseStatus() for a WebApplicationException should never be called");
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/package-info.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/package-info.java
new file mode 100644
index 00000000..6b581bc7
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/exceptions/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/**
+ * Various exception mappers to map errors to proper response status codes.
+ */
+package org.onosproject.rest.exceptions;
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/ApiDocManager.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/ApiDocManager.java
new file mode 100644
index 00000000..8a31b02b
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/ApiDocManager.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 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.rest.impl;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+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.Service;
+import org.onosproject.rest.ApiDocProvider;
+import org.onosproject.rest.ApiDocService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of the REST API documentation tracker.
+ */
+@Component(immediate = true)
+@Service
+public class ApiDocManager implements ApiDocService {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ // Set of doc providers
+ private final Map<String, ApiDocProvider> providers = Maps.newConcurrentMap();
+
+ @Activate
+ public void activate() {
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ @Override
+ public void register(ApiDocProvider provider) {
+ providers.put(provider.key(), provider);
+ }
+
+ @Override
+ public void unregister(ApiDocProvider provider) {
+ providers.remove(provider.name());
+ }
+
+ @Override
+ public Set<ApiDocProvider> getDocProviders() {
+ return ImmutableSet.copyOf(providers.values());
+ }
+
+ @Override
+ public ApiDocProvider getDocProvider(String key) {
+ return providers.get(key);
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/package-info.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/package-info.java
new file mode 100644
index 00000000..8fa8468d
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 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.
+ */
+
+/**
+ * REST API related service.
+ */
+package org.onosproject.rest.impl; \ No newline at end of file
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApiDocResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApiDocResource.java
new file mode 100644
index 00000000..804f05ed
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApiDocResource.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.rest.AbstractInjectionResource;
+import org.onosproject.rest.ApiDocService;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static javax.ws.rs.core.MediaType.*;
+import static javax.ws.rs.core.Response.temporaryRedirect;
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * REST API documentation.
+ */
+@Path("docs")
+public class ApiDocResource extends AbstractInjectionResource {
+
+ private static final String CONTENT_TYPE = "Content-Type";
+ private static final String STYLESHEET = "text/css";
+ private static final String SCRIPT = "text/javascript";
+ private static final String DOCS = "/docs/";
+
+ private static final String INJECT_START = "<!-- {API-START} -->";
+ private static final String INJECT_END = "<!-- {API-END} -->";
+
+ @Context
+ private UriInfo uriInfo;
+
+ /**
+ * Get all registered REST API docs.
+ * Returns array of all registered API docs.
+ *
+ * @return 200 OK
+ */
+ @GET
+ @Path("apis")
+ public Response getApiList() {
+ ObjectNode root = mapper().createObjectNode();
+ ArrayNode apis = newArray(root, "apis");
+ get(ApiDocService.class).getDocProviders().forEach(p -> apis.add(p.name()));
+ return ok(root.toString()).build();
+ }
+
+ /**
+ * Get Swagger UI JSON.
+ *
+ * @param key REST API web context
+ * @return 200 OK
+ */
+ @GET
+ @Path("apis/{key: .*?}/swagger.json")
+ public Response getApi(@PathParam("key") String key) {
+ String k = key.startsWith("/") ? key : "/" + key;
+ InputStream stream = nullIsNotFound(get(ApiDocService.class).getDocProvider(k),
+ "REST API not found for " + k).docs();
+ return ok(nullIsNotFound(stream, "REST API docs not found for " + k))
+ .header(CONTENT_TYPE, APPLICATION_JSON).build();
+ }
+
+ /**
+ * Get REST API model schema.
+ *
+ * @param key REST API web context
+ * @return 200 OK
+ */
+ @GET
+ @Path("apis/{key: .*?}/model.json")
+ public Response getApiModel(@PathParam("name") String key) {
+ String k = key.startsWith("/") ? key : "/" + key;
+ InputStream stream = nullIsNotFound(get(ApiDocService.class).getDocProvider(k),
+ "REST API not found for " + k).model();
+ return ok(nullIsNotFound(stream, "REST API model not found for " + k))
+ .header(CONTENT_TYPE, APPLICATION_JSON).build();
+ }
+
+ /**
+ * Get Swagger UI main index page.
+ *
+ * @return 200 OK
+ * @throws IOException if unable to get index resource
+ * @throws URISyntaxException if unable to create redirect URI
+ */
+ @GET
+ @Path("/")
+ public Response getDefault() throws IOException, URISyntaxException {
+ return uriInfo.getPath().endsWith("/") ? getIndex() :
+ temporaryRedirect(new URI(uriInfo.getPath() + "/")).build();
+ }
+
+ /**
+ * Get Swagger UI main index page.
+ *
+ * @return 200 OK
+ * @throws IOException if unable to get index resource
+ */
+ @GET
+ @Path("index.html")
+ public Response getIndex() throws IOException {
+ InputStream stream = getClass().getClassLoader().getResourceAsStream(DOCS + "index.html");
+ nullIsNotFound(stream, "index.html not found");
+
+ String index = new String(toByteArray(stream));
+
+ int p1s = split(index, 0, INJECT_START);
+ int p1e = split(index, p1s, INJECT_END);
+ int p2s = split(index, p1e, null);
+
+ StreamEnumeration streams =
+ new StreamEnumeration(of(stream(index, 0, p1s),
+ includeOptions(get(ApiDocService.class)),
+ stream(index, p1e, p2s)));
+
+ return ok(new SequenceInputStream(streams))
+ .header(CONTENT_TYPE, TEXT_HTML).build();
+ }
+
+ private InputStream includeOptions(ApiDocService service) {
+ StringBuilder sb = new StringBuilder();
+ service.getDocProviders().forEach(p -> {
+ sb.append("<option value=\"").append(p.key()).append("\"")
+ .append(p.key().equals("/onos/v1") ? " selected>" : ">")
+ .append(p.name())
+ .append("</option>");
+ });
+ return new ByteArrayInputStream(sb.toString().getBytes());
+ }
+
+ /**
+ * Get Swagger UI resource.
+ *
+ * @param resource path of the resource
+ * @return 200 OK
+ * @throws IOException if unable to get named resource
+ */
+ @GET
+ @Path("{resource: .*}")
+ public Response getResource(@PathParam("resource") String resource) throws IOException {
+ InputStream stream = getClass().getClassLoader().getResourceAsStream(DOCS + resource);
+ return ok(nullIsNotFound(stream, resource + " not found"))
+ .header(CONTENT_TYPE, contentType(resource)).build();
+ }
+
+ static String contentType(String resource) {
+ return resource.endsWith(".html") ? TEXT_HTML :
+ resource.endsWith(".css") ? STYLESHEET :
+ resource.endsWith(".js") ? SCRIPT :
+ APPLICATION_OCTET_STREAM;
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
new file mode 100644
index 00000000..636fc333
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ApplicationsWebResource.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.Application;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.InputStream;
+import java.util.Set;
+
+/**
+ * Manage inventory of applications.
+ */
+@Path("applications")
+public class ApplicationsWebResource extends AbstractWebResource {
+
+ /**
+ * Get all installed applications.
+ * Returns array of all installed applications.
+ *
+ * @return 200 OK
+ */
+ @GET
+ public Response getApps() {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ Set<Application> apps = service.getApplications();
+ return ok(encodeArray(Application.class, "applications", apps)).build();
+ }
+
+ /**
+ * Get application details.
+ * Returns details of the specified application.
+ *
+ * @param name application name
+ * @return 200 OK; 404; 401
+ */
+ @GET
+ @Path("{name}")
+ public Response getApp(@PathParam("name") String name) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ ApplicationId appId = service.getId(name);
+ return response(service, appId);
+ }
+
+ /**
+ * Install a new application.
+ * Uploads application archive stream and optionally activates the
+ * application.
+ *
+ * @param activate true to activate app also
+ * @param stream application archive stream
+ * @return 200 OK; 404; 401
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_OCTET_STREAM)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response installApp(@QueryParam("activate")
+ @DefaultValue("false") boolean activate,
+ InputStream stream) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ Application app = service.install(stream);
+ if (activate) {
+ service.activate(app.id());
+ }
+ return ok(codec(Application.class).encode(app, this)).build();
+ }
+
+ /**
+ * Uninstall application.
+ * Uninstalls the specified application deactivating it first if necessary.
+ *
+ * @param name application name
+ * @return 200 OK; 404; 401
+ */
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{name}")
+ public Response uninstallApp(@PathParam("name") String name) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ ApplicationId appId = service.getId(name);
+ service.uninstall(appId);
+ return Response.ok().build();
+ }
+
+ /**
+ * Activate application.
+ * Activates the specified application.
+ *
+ * @param name application name
+ * @return 200 OK; 404; 401
+ */
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{name}/active")
+ public Response activateApp(@PathParam("name") String name) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ ApplicationId appId = service.getId(name);
+ service.activate(appId);
+ return response(service, appId);
+ }
+
+ /**
+ * De-activate application.
+ * De-activates the specified application.
+ *
+ * @param name application name
+ * @return 200 OK; 404; 401
+ */
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{name}/active")
+ public Response deactivateApp(@PathParam("name") String name) {
+ ApplicationAdminService service = get(ApplicationAdminService.class);
+ ApplicationId appId = service.getId(name);
+ service.deactivate(appId);
+ return response(service, appId);
+ }
+
+ private Response response(ApplicationAdminService service, ApplicationId appId) {
+ Application app = service.getApplication(appId);
+ return ok(codec(Application.class).encode(app, this)).build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java
new file mode 100644
index 00000000..dd9b9fc3
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ClusterWebResource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.cluster.ClusterAdminService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * Manage cluster of ONOS instances.
+ */
+@Path("cluster")
+public class ClusterWebResource extends AbstractWebResource {
+
+ public static final String NODE_NOT_FOUND = "Node is not found";
+
+ /**
+ * Get all cluster nodes.
+ * Returns array of all cluster nodes.
+ *
+ * @return 200 OK
+ */
+ @GET
+ public Response getClusterNodes() {
+ Iterable<ControllerNode> nodes = get(ClusterService.class).getNodes();
+ return ok(encodeArray(ControllerNode.class, "nodes", nodes)).build();
+ }
+
+ /**
+ * Get cluster node details.
+ * Returns details of the specified cluster node.
+ *
+ * @param id cluster node identifier
+ * @return 200 OK
+ */
+ @GET
+ @Path("{id}")
+ public Response getClusterNode(@PathParam("id") String id) {
+ ControllerNode node = nullIsNotFound(get(ClusterService.class).getNode(new NodeId(id)),
+ NODE_NOT_FOUND);
+ return ok(codec(ControllerNode.class).encode(node, this)).build();
+ }
+
+ /**
+ * Forms cluster of ONOS instances.
+ * Forms ONOS cluster using the uploaded JSON definition.
+ *
+ * @param config cluster definition
+ * @return 200 OK
+ * @throws IOException to signify bad request
+ */
+ @POST
+ @Path("configuration")
+ public Response formCluster(InputStream config) throws IOException {
+ JsonCodec<ControllerNode> codec = codec(ControllerNode.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(config);
+ String ipPrefix = root.path("ipPrefix").asText();
+
+ List<ControllerNode> nodes = codec.decode((ArrayNode) root.path("nodes"), this);
+ get(ClusterAdminService.class).formCluster(new HashSet<>(nodes), ipPrefix);
+
+ return Response.ok().build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ComponentConfigWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ComponentConfigWebResource.java
new file mode 100644
index 00000000..468a3764
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ComponentConfigWebResource.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cfg.ConfigProperty;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * Manage component configurations.
+ */
+@Path("configuration")
+public class ComponentConfigWebResource extends AbstractWebResource {
+
+ /**
+ * Get all component configurations.
+ * Returns collection of all registered component configurations.
+ *
+ * @return 200 OK
+ */
+ @GET
+ public Response getComponentConfigs() {
+ ComponentConfigService service = get(ComponentConfigService.class);
+ Set<String> components = service.getComponentNames();
+ ObjectNode root = mapper().createObjectNode();
+ components.forEach(c -> encodeConfigs(c, service.getProperties(c), root));
+ return ok(root).build();
+ }
+
+ /**
+ * Get configuration of the specified component.
+ *
+ * @param component component name
+ * @return 200 OK
+ */
+ @GET
+ @Path("{component}")
+ public Response getComponentConfigs(@PathParam("component") String component) {
+ ComponentConfigService service = get(ComponentConfigService.class);
+ ObjectNode root = mapper().createObjectNode();
+ encodeConfigs(component, nullIsNotFound(service.getProperties(component),
+ "No such component"), root);
+ return ok(root).build();
+ }
+
+ // Encodes the specified properties as an object in the given node.
+ private void encodeConfigs(String component, Set<ConfigProperty> props,
+ ObjectNode node) {
+ ObjectNode compNode = mapper().createObjectNode();
+ node.set(component, compNode);
+ props.forEach(p -> compNode.put(p.name(), p.value()));
+ }
+
+ /**
+ * Selectively set configuration properties.
+ * Sets only the properties present in the JSON request.
+ *
+ * @param component component name
+ * @param request JSON configuration
+ * @return 200 OK
+ * @throws IOException to signify bad request
+ */
+ @POST
+ @Path("{component}")
+ public Response setConfigs(@PathParam("component") String component,
+ InputStream request) throws IOException {
+ ComponentConfigService service = get(ComponentConfigService.class);
+ ObjectNode props = (ObjectNode) mapper().readTree(request);
+ props.fieldNames().forEachRemaining(k -> service.setProperty(component, k,
+ props.path(k).asText()));
+ return Response.noContent().build();
+ }
+
+ /**
+ * Selectively clear configuration properties.
+ * Clears only the properties present in the JSON request.
+ *
+ * @param component component name
+ * @param request JSON configuration
+ * @return 200 OK
+ * @throws IOException to signify bad request
+ */
+ @DELETE
+ @Path("{component}")
+ public Response unsetConfigs(@PathParam("component") String component,
+ InputStream request) throws IOException {
+ ComponentConfigService service = get(ComponentConfigService.class);
+ ObjectNode props = (ObjectNode) mapper().readTree(request);
+ props.fieldNames().forEachRemaining(k -> service.unsetProperty(component, k));
+ return Response.noContent().build();
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java
new file mode 100644
index 00000000..dbd80cca
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java
@@ -0,0 +1,610 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import org.onlab.packet.ChassisId;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onlab.util.Frequency;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.Link;
+import org.onosproject.net.MastershipRole;
+import org.onosproject.net.OchPort;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OduCltPort;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.OmsPort;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceProvider;
+import org.onosproject.net.device.DeviceProviderRegistry;
+import org.onosproject.net.device.DeviceProviderService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.OchPortDescription;
+import org.onosproject.net.device.OduCltPortDescription;
+import org.onosproject.net.device.OmsPortDescription;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkProvider;
+import org.onosproject.net.link.LinkProviderRegistry;
+import org.onosproject.net.link.LinkProviderService;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
+
+/**
+ * Provider of devices and links parsed from a JSON configuration structure.
+ */
+class ConfigProvider implements DeviceProvider, LinkProvider, HostProvider {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final ProviderId PID =
+ new ProviderId("cfg", "org.onosproject.rest", true);
+
+ private static final String UNKNOWN = "unknown";
+
+ private static final Frequency CENTER = Frequency.ofTHz(193.1);
+ // C-band has 4.4 THz (4,400 GHz) total bandwidth
+ private static final Frequency TOTAL = Frequency.ofTHz(4.4);
+
+ private CountDownLatch deviceLatch;
+
+ private final JsonNode cfg;
+ private final DeviceService deviceService;
+
+ private final DeviceProviderRegistry deviceProviderRegistry;
+ private final LinkProviderRegistry linkProviderRegistry;
+ private final HostProviderRegistry hostProviderRegistry;
+
+ private DeviceProviderService deviceProviderService;
+ private LinkProviderService linkProviderService;
+ private HostProviderService hostProviderService;
+
+ private DeviceListener deviceEventCounter = new DeviceEventCounter();
+ private List<ConnectPoint> connectPoints = Lists.newArrayList();
+ private Map<ConnectPoint, PortDescription> descriptions = Maps.newHashMap();
+
+ /**
+ * Creates a new configuration provider.
+ *
+ * @param cfg JSON configuration
+ * @param deviceService device service
+ * @param deviceProviderRegistry device provider registry
+ * @param linkProviderRegistry link provider registry
+ * @param hostProviderRegistry host provider registry
+ */
+ ConfigProvider(JsonNode cfg,
+ DeviceService deviceService,
+ DeviceProviderRegistry deviceProviderRegistry,
+ LinkProviderRegistry linkProviderRegistry,
+ HostProviderRegistry hostProviderRegistry) {
+ this.cfg = checkNotNull(cfg, "Configuration cannot be null");
+ this.deviceService = checkNotNull(deviceService, "Device service cannot be null");
+ this.deviceProviderRegistry = checkNotNull(deviceProviderRegistry, "Device provider registry cannot be null");
+ this.linkProviderRegistry = checkNotNull(linkProviderRegistry, "Link provider registry cannot be null");
+ this.hostProviderRegistry = checkNotNull(hostProviderRegistry, "Host provider registry cannot be null");
+ }
+
+ /**
+ * Parses the given JSON and provides links as configured.
+ */
+ void parse() {
+ try {
+ register();
+ parseDevices();
+ parseLinks();
+ parseHosts();
+ addMissingPorts();
+ } finally {
+ unregister();
+ }
+ }
+
+ private void register() {
+ deviceProviderService = deviceProviderRegistry.register(this);
+ linkProviderService = linkProviderRegistry.register(this);
+ hostProviderService = hostProviderRegistry.register(this);
+ }
+
+ private void unregister() {
+ deviceProviderRegistry.unregister(this);
+ linkProviderRegistry.unregister(this);
+ hostProviderRegistry.unregister(this);
+ }
+
+ // Parses the given JSON and provides devices.
+ private void parseDevices() {
+ try {
+ JsonNode nodes = cfg.get("devices");
+ if (nodes != null) {
+ prepareForDeviceEvents(nodes.size());
+ for (JsonNode node : nodes) {
+ parseDevice(node);
+
+ // FIXME: hack to make sure device attributes take
+ // This will be fixed when GossipDeviceStore uses ECM
+ parseDevice(node);
+ }
+ }
+ } finally {
+ waitForDeviceEvents();
+ }
+ }
+
+ // Parses the given node with device data and supplies the device.
+ private void parseDevice(JsonNode node) {
+ URI uri = URI.create(get(node, "uri"));
+ Device.Type type = Device.Type.valueOf(get(node, "type", "SWITCH"));
+ String mfr = get(node, "mfr", UNKNOWN);
+ String hw = get(node, "hw", UNKNOWN);
+ String sw = get(node, "sw", UNKNOWN);
+ String serial = get(node, "serial", UNKNOWN);
+ ChassisId cid = new ChassisId(get(node, "mac", "000000000000"));
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+
+ DeviceDescription desc =
+ new DefaultDeviceDescription(uri, type, mfr, hw, sw, serial,
+ cid, annotations);
+ DeviceId deviceId = deviceId(uri);
+ deviceProviderService.deviceConnected(deviceId, desc);
+
+ JsonNode ports = node.get("ports");
+ if (ports != null) {
+ parsePorts(deviceId, ports);
+ }
+ }
+
+ // Parses the given node with list of device ports.
+ private void parsePorts(DeviceId deviceId, JsonNode nodes) {
+ List<PortDescription> ports = new ArrayList<>();
+ for (JsonNode node : nodes) {
+ ports.add(parsePort(deviceId, node));
+ }
+ deviceProviderService.updatePorts(deviceId, ports);
+ }
+
+ // Parses the given node with port information.
+ private PortDescription parsePort(DeviceId deviceId, JsonNode node) {
+ Port.Type type = Port.Type.valueOf(node.path("type").asText("COPPER"));
+ // TL1-based ports have a name
+ PortNumber port = null;
+ if (node.has("name")) {
+ for (Port p : deviceService.getPorts(deviceId)) {
+ if (p.number().name().equals(node.get("name").asText())) {
+ port = p.number();
+ break;
+ }
+ }
+ } else {
+ port = portNumber(node.path("port").asLong(0));
+ }
+
+ if (port == null) {
+ log.error("Cannot find port given in node {}", node);
+ return null;
+ }
+
+ String portName = Strings.emptyToNull(port.name());
+ SparseAnnotations annotations = null;
+ if (portName != null) {
+ annotations = DefaultAnnotations.builder()
+ .set(AnnotationKeys.PORT_NAME, portName).build();
+ }
+ switch (type) {
+ case COPPER:
+ return new DefaultPortDescription(port, node.path("enabled").asBoolean(true),
+ type, node.path("speed").asLong(1_000),
+ annotations);
+ case FIBER:
+ // Currently, assume OMS when FIBER. Provide sane defaults.
+ annotations = annotations(node.get("annotations"));
+ return new OmsPortDescription(port, node.path("enabled").asBoolean(true),
+ CENTER, CENTER.add(TOTAL),
+ Frequency.ofGHz(100), annotations);
+ case ODUCLT:
+ annotations = annotations(node.get("annotations"));
+ OduCltPort oduCltPort = (OduCltPort) deviceService.getPort(deviceId, port);
+ return new OduCltPortDescription(port, node.path("enabled").asBoolean(true),
+ oduCltPort.signalType(), annotations);
+ case OCH:
+ annotations = annotations(node.get("annotations"));
+ OchPort ochPort = (OchPort) deviceService.getPort(deviceId, port);
+ return new OchPortDescription(port, node.path("enabled").asBoolean(true),
+ ochPort.signalType(), ochPort.isTunable(),
+ ochPort.lambda(), annotations);
+ case OMS:
+ annotations = annotations(node.get("annotations"));
+ OmsPort omsPort = (OmsPort) deviceService.getPort(deviceId, port);
+ return new OmsPortDescription(port, node.path("enabled").asBoolean(true),
+ omsPort.minFrequency(), omsPort.maxFrequency(), omsPort.grid(), annotations);
+ default:
+ log.warn("{}: Unsupported Port Type");
+ }
+ return new DefaultPortDescription(port, node.path("enabled").asBoolean(true),
+ type, node.path("speed").asLong(1_000),
+ annotations);
+ }
+
+ // Parses the given JSON and provides links as configured.
+ private void parseLinks() {
+ JsonNode nodes = cfg.get("links");
+ if (nodes != null) {
+ for (JsonNode node : nodes) {
+ parseLink(node, false);
+ if (!node.has("halfplex")) {
+ parseLink(node, true);
+ }
+ }
+ }
+ }
+
+ // Parses the given node with link data and supplies the link.
+ private void parseLink(JsonNode node, boolean reverse) {
+ ConnectPoint src = connectPoint(get(node, "src"));
+ ConnectPoint dst = connectPoint(get(node, "dst"));
+ Link.Type type = Link.Type.valueOf(get(node, "type", "DIRECT"));
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+ // take annotations to update optical ports with correct attributes.
+ updatePorts(src, dst, annotations);
+ DefaultLinkDescription desc = reverse ?
+ new DefaultLinkDescription(dst, src, type, annotations) :
+ new DefaultLinkDescription(src, dst, type, annotations);
+ linkProviderService.linkDetected(desc);
+
+ connectPoints.add(src);
+ connectPoints.add(dst);
+ }
+
+ private void updatePorts(ConnectPoint src, ConnectPoint dst, SparseAnnotations annotations) {
+ final String linkType = annotations.value("optical.type");
+ if ("cross-connect".equals(linkType)) {
+ String value = annotations.value("bandwidth").trim();
+ try {
+ double bw = Double.parseDouble(value);
+ updateOchPort(bw, src, dst);
+ } catch (NumberFormatException e) {
+ log.warn("Invalid bandwidth ({}), can't configure port(s)", value);
+ return;
+ }
+ } else if ("WDM".equals(linkType)) {
+ String value = annotations.value("optical.waves").trim();
+ try {
+ int numChls = Integer.parseInt(value);
+ updateOMSPorts(numChls, src, dst);
+ } catch (NumberFormatException e) {
+ log.warn("Invalid channel ({}), can't configure port(s)", value);
+ return;
+ }
+ }
+ }
+
+ // uses 'bandwidth' annotation to determine the channel spacing.
+ private void updateOchPort(double bw, ConnectPoint srcCp, ConnectPoint dstCp) {
+ Device src = deviceService.getDevice(srcCp.deviceId());
+ Device dst = deviceService.getDevice(dstCp.deviceId());
+ // bandwidth in MHz (assuming Hz - linc is not clear if that or Mb).
+ Frequency spacing = Frequency.ofMHz(bw);
+ // channel bandwidth is smaller than smallest standard channel spacing.
+ ChannelSpacing chsp = null;
+ if (spacing.compareTo(ChannelSpacing.CHL_6P25GHZ.frequency()) <= 0) {
+ chsp = ChannelSpacing.CHL_6P25GHZ;
+ }
+ for (int i = 1; i < ChannelSpacing.values().length; i++) {
+ Frequency val = ChannelSpacing.values()[i].frequency();
+ // pick the next highest or equal channel interval.
+ if (val.isLessThan(spacing)) {
+ chsp = ChannelSpacing.values()[i - 1];
+ break;
+ }
+ }
+ if (chsp == null) {
+ log.warn("Invalid channel spacing ({}), can't configure port(s)", spacing);
+ return;
+ }
+ OchSignal signal = new OchSignal(GridType.DWDM, chsp, 1, 1);
+ if (src.type() == Device.Type.ROADM) {
+ PortDescription portDesc = new OchPortDescription(srcCp.port(), true,
+ OduSignalType.ODU4, true, signal);
+ descriptions.put(srcCp, portDesc);
+ deviceProviderService.portStatusChanged(srcCp.deviceId(), portDesc);
+ }
+ if (dst.type() == Device.Type.ROADM) {
+ PortDescription portDesc = new OchPortDescription(dstCp.port(), true,
+ OduSignalType.ODU4, true, signal);
+ descriptions.put(dstCp, portDesc);
+ deviceProviderService.portStatusChanged(dstCp.deviceId(), portDesc);
+ }
+ }
+
+ private void updateOMSPorts(int numChls, ConnectPoint srcCp, ConnectPoint dstCp) {
+ // round down to largest slot that allows numChl channels to fit into C band range
+ ChannelSpacing chl = null;
+ Frequency perChl = TOTAL.floorDivision(numChls);
+ for (int i = 0; i < ChannelSpacing.values().length; i++) {
+ Frequency val = ChannelSpacing.values()[i].frequency();
+ if (val.isLessThan(perChl)) {
+ chl = ChannelSpacing.values()[i];
+ break;
+ }
+ }
+ if (chl == null) {
+ chl = ChannelSpacing.CHL_6P25GHZ;
+ }
+
+ // if true, there was less channels than can be tightly packed.
+ Frequency grid = chl.frequency();
+ // say Linc's 1st slot starts at CENTER and goes up from there.
+ Frequency min = CENTER.add(grid);
+ Frequency max = CENTER.add(grid.multiply(numChls));
+
+ PortDescription srcPortDesc = new OmsPortDescription(srcCp.port(), true, min, max, grid);
+ PortDescription dstPortDesc = new OmsPortDescription(dstCp.port(), true, min, max, grid);
+ descriptions.put(srcCp, srcPortDesc);
+ descriptions.put(dstCp, dstPortDesc);
+ deviceProviderService.portStatusChanged(srcCp.deviceId(), srcPortDesc);
+ deviceProviderService.portStatusChanged(dstCp.deviceId(), dstPortDesc);
+ }
+
+ // Parses the given JSON and provides hosts as configured.
+ private void parseHosts() {
+ try {
+ JsonNode nodes = cfg.get("hosts");
+ if (nodes != null) {
+ for (JsonNode node : nodes) {
+ parseHost(node);
+
+ // FIXME: hack to make sure host attributes take
+ // This will be fixed when GossipHostStore uses ECM
+ parseHost(node);
+ }
+ }
+ } finally {
+ hostProviderRegistry.unregister(this);
+ }
+ }
+
+ // Parses the given node with host data and supplies the host.
+ private void parseHost(JsonNode node) {
+ MacAddress mac = MacAddress.valueOf(get(node, "mac"));
+ VlanId vlanId = VlanId.vlanId((short) node.get("vlan").asInt(VlanId.UNTAGGED));
+ HostId hostId = HostId.hostId(mac, vlanId);
+ SparseAnnotations annotations = annotations(node.get("annotations"));
+ HostLocation location = new HostLocation(connectPoint(get(node, "location")), 0);
+
+ String[] ipStrings = get(node, "ip", "").split(",");
+ Set<IpAddress> ips = new HashSet<>();
+ for (String ip : ipStrings) {
+ ips.add(IpAddress.valueOf(ip.trim()));
+ }
+
+ DefaultHostDescription desc =
+ new DefaultHostDescription(mac, vlanId, location, ips, annotations);
+ hostProviderService.hostDetected(hostId, desc);
+
+ connectPoints.add(location);
+ }
+
+ // Adds any missing device ports for configured links and host locations.
+ private void addMissingPorts() {
+ deviceService.getDevices().forEach(this::addMissingPorts);
+ }
+
+ // Adds any missing device ports.
+ private void addMissingPorts(Device device) {
+ try {
+ List<Port> ports = deviceService.getPorts(device.id());
+ Set<ConnectPoint> existing = ports.stream()
+ .map(p -> new ConnectPoint(device.id(), p.number()))
+ .collect(Collectors.toSet());
+ Set<ConnectPoint> missing = connectPoints.stream()
+ .filter(cp -> cp.deviceId().equals(device.id()))
+ .filter(cp -> !existing.contains(cp))
+ .collect(Collectors.toSet());
+
+ if (!missing.isEmpty()) {
+ List<PortDescription> newPorts = Stream.concat(
+ ports.stream().map(this::description),
+ missing.stream().map(this::description)
+ ).collect(Collectors.toList());
+ deviceProviderService.updatePorts(device.id(), newPorts);
+ }
+ } catch (IllegalArgumentException e) {
+ log.warn("Error pushing ports: {}", e.getMessage());
+ }
+ }
+
+ // Creates a port description from the specified port.
+ private PortDescription description(Port p) {
+ switch (p.type()) {
+ case OMS:
+ OmsPort op = (OmsPort) p;
+ return new OmsPortDescription(
+ op.number(), op.isEnabled(), op.minFrequency(), op.maxFrequency(), op.grid());
+ case OCH:
+ OchPort ochp = (OchPort) p;
+ return new OchPortDescription(
+ ochp.number(), ochp.isEnabled(), ochp.signalType(), ochp.isTunable(), ochp.lambda());
+ case ODUCLT:
+ OduCltPort odup = (OduCltPort) p;
+ return new OduCltPortDescription(
+ odup.number(), odup.isEnabled(), odup.signalType());
+ default:
+ return new DefaultPortDescription(p.number(), p.isEnabled(), p.type(), p.portSpeed());
+ }
+ }
+
+ // Creates a port description from the specified connection point if none created earlier.
+ private PortDescription description(ConnectPoint cp) {
+ PortDescription saved = descriptions.get(cp);
+ if (saved != null) {
+ return saved;
+ }
+ Port p = deviceService.getPort(cp.deviceId(), cp.port());
+ if (p == null) {
+ return new DefaultPortDescription(cp.port(), true);
+ }
+ return description(p);
+ }
+
+ // Produces set of annotations from the given JSON node.
+ private SparseAnnotations annotations(JsonNode node) {
+ if (node == null) {
+ return DefaultAnnotations.EMPTY;
+ }
+
+ DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+ Iterator<String> it = node.fieldNames();
+ while (it.hasNext()) {
+ String k = it.next();
+ builder.set(k, node.get(k).asText());
+ }
+ return builder.build();
+ }
+
+ // Produces a connection point from the specified uri/port text.
+ private ConnectPoint connectPoint(String text) {
+ int i = text.lastIndexOf("/");
+ String portName = text.substring(i + 1);
+ DeviceId deviceId = deviceId(text.substring(0, i));
+
+ for (Port port : deviceService.getPorts(deviceId)) {
+ PortNumber pn = port.number();
+ if (pn.name().equals(portName)) {
+ return new ConnectPoint(deviceId, pn);
+ }
+ }
+
+ long portNum;
+ try {
+ portNum = Long.parseLong(portName);
+ } catch (NumberFormatException e) {
+ portNum = 0;
+ }
+
+ return new ConnectPoint(deviceId, portNumber(portNum, portName));
+ }
+
+ // Returns string form of the named property in the given JSON object.
+ private String get(JsonNode node, String name) {
+ return node.path(name).asText();
+ }
+
+ // Returns string form of the named property in the given JSON object.
+ private String get(JsonNode node, String name, String defaultValue) {
+ return node.path(name).asText(defaultValue);
+ }
+
+ @Override
+ public void roleChanged(DeviceId device, MastershipRole newRole) {
+ deviceProviderService.receivedRoleReply(device, newRole, newRole);
+ }
+
+ @Override
+ public void triggerProbe(DeviceId deviceId) {
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ }
+
+ @Override
+ public ProviderId id() {
+ return PID;
+ }
+
+ @Override
+ public boolean isReachable(DeviceId device) {
+ return true;
+ }
+
+ /**
+ * Prepares to count device added/available/removed events.
+ *
+ * @param count number of events to count
+ */
+ protected void prepareForDeviceEvents(int count) {
+ deviceLatch = new CountDownLatch(count);
+ deviceService.addListener(deviceEventCounter);
+ }
+
+ /**
+ * Waits for all expected device added/available/removed events.
+ */
+ protected void waitForDeviceEvents() {
+ try {
+ deviceLatch.await(2, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.warn("Device events did not arrive in time");
+ }
+ deviceService.removeListener(deviceEventCounter);
+ }
+
+ // Counts down number of device added/available/removed events.
+ private class DeviceEventCounter implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ DeviceEvent.Type type = event.type();
+ if (type == DEVICE_ADDED || type == DEVICE_AVAILABILITY_CHANGED) {
+ deviceLatch.countDown();
+ }
+ }
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigWebResource.java
new file mode 100644
index 00000000..1bf5fe9a
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/ConfigWebResource.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import java.io.InputStream;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onlab.rest.BaseResource;
+import org.onosproject.net.device.DeviceProviderRegistry;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.link.LinkProviderRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+
+/**
+ * Inject devices, ports, links and end-station hosts.
+ */
+@Path("config")
+public class ConfigWebResource extends BaseResource {
+
+ private static Logger log = LoggerFactory.getLogger(ConfigWebResource.class);
+
+ /**
+ * Upload device, port, link and host data.
+ *
+ * @param input JSON blob
+ * @return 200 OK
+ */
+ @POST
+ @Path("topology")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response topology(InputStream input) {
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode cfg = mapper.readTree(input);
+ new ConfigProvider(cfg, get(DeviceService.class),
+ get(DeviceProviderRegistry.class),
+ get(LinkProviderRegistry.class),
+ get(HostProviderRegistry.class)).parse();
+ return Response.ok().build();
+ } catch (Exception e) {
+ log.error("Unable to parse topology configuration", e);
+ return Response.status(INTERNAL_SERVER_ERROR).entity(e.toString()).build();
+ }
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/DevicesWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/DevicesWebResource.java
new file mode 100644
index 00000000..05756e5a
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/DevicesWebResource.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceAdminService;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.nullIsNotFound;
+import static org.onosproject.net.DeviceId.deviceId;
+
+/**
+ * Manage inventory of infrastructure devices.
+ */
+@Path("devices")
+public class DevicesWebResource extends AbstractWebResource {
+
+ public static final String DEVICE_NOT_FOUND = "Device is not found";
+
+ /**
+ * Get all infrastructure devices.
+ * Returns array of all discovered infrastructure devices.
+ *
+ * @return 200 OK
+ */
+ @GET
+ public Response getDevices() {
+ Iterable<Device> devices = get(DeviceService.class).getDevices();
+ return ok(encodeArray(Device.class, "devices", devices)).build();
+ }
+
+ /**
+ * Get details of infrastructure device.
+ * Returns details of the specified infrastructure device.
+ *
+ * @param id device identifier
+ * @return 200 OK
+ */
+ @GET
+ @Path("{id}")
+ public Response getDevice(@PathParam("id") String id) {
+ Device device = nullIsNotFound(get(DeviceService.class).getDevice(deviceId(id)),
+ DEVICE_NOT_FOUND);
+ return ok(codec(Device.class).encode(device, this)).build();
+ }
+
+ /**
+ * Remove infrastructure device.
+ * Administratively deletes the specified device from the inventory of
+ * known devices.
+ *
+ * @param id device identifier
+ * @return 200 OK
+ */
+ @DELETE
+ @Path("{id}")
+ public Response removeDevice(@PathParam("id") String id) {
+ Device device = nullIsNotFound(get(DeviceService.class).getDevice(deviceId(id)),
+ DEVICE_NOT_FOUND);
+ get(DeviceAdminService.class).removeDevice(deviceId(id));
+ return ok(codec(Device.class).encode(device, this)).build();
+ }
+
+ /**
+ * Get ports of infrastructure device.
+ * Returns details of the specified infrastructure device.
+ *
+ * @param id device identifier
+ * @return 200 OK
+ */
+ @GET
+ @Path("{id}/ports")
+ public Response getDevicePorts(@PathParam("id") String id) {
+ DeviceService service = get(DeviceService.class);
+ Device device = nullIsNotFound(service.getDevice(deviceId(id)), DEVICE_NOT_FOUND);
+ List<Port> ports = checkNotNull(service.getPorts(deviceId(id)), "Ports could not be retrieved");
+ ObjectNode result = codec(Device.class).encode(device, this);
+ result.set("ports", codec(Port.class).encode(ports, this));
+ return ok(result).build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
new file mode 100644
index 00000000..325e191b
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/FlowsWebResource.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.rest.AbstractWebResource;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Query and program flow rules.
+ */
+
+@Path("flows")
+public class FlowsWebResource extends AbstractWebResource {
+ public static final String DEVICE_NOT_FOUND = "Device is not found";
+
+ final FlowRuleService service = get(FlowRuleService.class);
+ final ObjectNode root = mapper().createObjectNode();
+ final ArrayNode flowsNode = root.putArray("flows");
+
+ /**
+ * Get all flow entries. Returns array of all flow rules in the system.
+ *
+ * @return array of all the intents in the system
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getFlows() {
+ final Iterable<Device> devices = get(DeviceService.class).getDevices();
+ for (final Device device : devices) {
+ final Iterable<FlowEntry> deviceEntries = service.getFlowEntries(device.id());
+ if (deviceEntries != null) {
+ for (final FlowEntry entry : deviceEntries) {
+ flowsNode.add(codec(FlowEntry.class).encode(entry, this));
+ }
+ }
+ }
+
+ return ok(root).build();
+ }
+
+ /**
+ * Get flow entries of a device. Returns array of all flow rules for the
+ * specified device.
+ *
+ * @param deviceId device identifier
+ * @return flow data as an array
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{deviceId}")
+ public Response getFlowByDeviceId(@PathParam("deviceId") String deviceId) {
+ final Iterable<FlowEntry> deviceEntries =
+ service.getFlowEntries(DeviceId.deviceId(deviceId));
+
+ if (!deviceEntries.iterator().hasNext()) {
+ throw new ItemNotFoundException(DEVICE_NOT_FOUND);
+ }
+ for (final FlowEntry entry : deviceEntries) {
+ flowsNode.add(codec(FlowEntry.class).encode(entry, this));
+ }
+ return ok(root).build();
+ }
+
+ /**
+ * Get flow rule. Returns the flow entry specified by the device id and
+ * flow rule id.
+ *
+ * @param deviceId device identifier
+ * @param flowId flow rule identifier
+ * @return flow data as an array
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{deviceId}/{flowId}")
+ public Response getFlowByDeviceIdAndFlowId(@PathParam("deviceId") String deviceId,
+ @PathParam("flowId") long flowId) {
+ final Iterable<FlowEntry> deviceEntries =
+ service.getFlowEntries(DeviceId.deviceId(deviceId));
+
+ if (!deviceEntries.iterator().hasNext()) {
+ throw new ItemNotFoundException(DEVICE_NOT_FOUND);
+ }
+ for (final FlowEntry entry : deviceEntries) {
+ if (entry.id().value() == flowId) {
+ flowsNode.add(codec(FlowEntry.class).encode(entry, this));
+ }
+ }
+ return ok(root).build();
+ }
+
+ /**
+ * Create new flow rule. Creates and installs a new flow rule for the
+ * specified device.
+ *
+ * @param deviceId device identifier
+ * @param stream flow rule JSON
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ */
+ @POST
+ @Path("{deviceId}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createFlow(@PathParam("deviceId") String deviceId,
+ InputStream stream) {
+ URI location;
+ try {
+ ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
+ JsonNode specifiedDeviceId = jsonTree.get("deviceId");
+ if (specifiedDeviceId != null &&
+ !specifiedDeviceId.asText().equals(deviceId)) {
+ throw new IllegalArgumentException(
+ "Invalid deviceId in flow creation request");
+ }
+ jsonTree.put("deviceId", deviceId);
+ FlowRule rule = codec(FlowRule.class).decode(jsonTree, this);
+ service.applyFlowRules(rule);
+ location = new URI(Long.toString(rule.id().value()));
+ } catch (IOException | URISyntaxException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+
+ return Response
+ .created(location)
+ .build();
+ }
+
+ /**
+ * Remove flow rule. Removes the specified flow rule.
+ *
+ * @param deviceId device identifier
+ * @param flowId flow rule identifier
+ */
+ @DELETE
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{deviceId}/{flowId}")
+ public void deleteFlowByDeviceIdAndFlowId(@PathParam("deviceId") String deviceId,
+ @PathParam("flowId") long flowId) {
+ final Iterable<FlowEntry> deviceEntries =
+ service.getFlowEntries(DeviceId.deviceId(deviceId));
+
+ if (!deviceEntries.iterator().hasNext()) {
+ throw new ItemNotFoundException(DEVICE_NOT_FOUND);
+ }
+
+ StreamSupport.stream(deviceEntries.spliterator(), false)
+ .filter(entry -> entry.id().value() == flowId)
+ .forEach(service::removeFlowRules);
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
new file mode 100644
index 00000000..b89f5add
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/HostsWebResource.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+import static org.onosproject.net.HostId.hostId;
+
+/**
+ * Manage inventory of end-station hosts.
+ */
+@Path("hosts")
+public class HostsWebResource extends AbstractWebResource {
+
+ @Context
+ UriInfo uriInfo;
+ public static final String HOST_NOT_FOUND = "Host is not found";
+
+ /**
+ * Get all end-station hosts.
+ * Returns array of all known end-station hosts.
+ *
+ * @return 200 OK
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getHosts() {
+ final Iterable<Host> hosts = get(HostService.class).getHosts();
+ final ObjectNode root = encodeArray(Host.class, "hosts", hosts);
+ return ok(root).build();
+ }
+
+ /**
+ * Get details of end-station host.
+ * Returns detailed properties of the specified end-station host.
+ *
+ * @param id host identifier
+ * @return 200 OK
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}")
+ public Response getHostById(@PathParam("id") String id) {
+ final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(id)),
+ HOST_NOT_FOUND);
+ final ObjectNode root = codec(Host.class).encode(host, this);
+ return ok(root).build();
+ }
+
+ /**
+ * Get details of end-station host with MAC/VLAN.
+ * Returns detailed properties of the specified end-station host.
+ *
+ * @param mac host MAC address
+ * @param vlan host VLAN identifier
+ * @return 200 OK
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{mac}/{vlan}")
+ public Response getHostByMacAndVlan(@PathParam("mac") String mac,
+ @PathParam("vlan") String vlan) {
+ final Host host = nullIsNotFound(get(HostService.class).getHost(hostId(mac + "/" + vlan)),
+ HOST_NOT_FOUND);
+ final ObjectNode root = codec(Host.class).encode(host, this);
+ return ok(root).build();
+ }
+
+ /**
+ * Creates a new host based on JSON input and adds it to the current
+ * host inventory.
+ *
+ * @param stream input JSON
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createAndAddHost(InputStream stream) {
+ URI location;
+ try {
+ // Parse the input stream
+ ObjectNode root = (ObjectNode) mapper().readTree(stream);
+
+ HostProviderRegistry hostProviderRegistry = get(HostProviderRegistry.class);
+ InternalHostProvider hostProvider = new InternalHostProvider();
+ HostProviderService hostProviderService = hostProviderRegistry.register(hostProvider);
+ hostProvider.setHostProviderService(hostProviderService);
+ HostId hostId = hostProvider.parseHost(root);
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("hosts")
+ .path(hostId.mac().toString())
+ .path(hostId.vlanId().toString());
+ location = locationBuilder.build();
+ hostProviderRegistry.unregister(hostProvider);
+
+ } catch (IOException ex) {
+ throw new IllegalArgumentException(ex);
+ }
+ return Response
+ .created(location)
+ .build();
+ }
+
+ private final class InternalHostProvider implements HostProvider {
+ private final ProviderId providerId =
+ new ProviderId("host", "org.onosproject.rest", true);
+ private HostProviderService hostProviderService;
+
+ private InternalHostProvider() {
+ }
+
+ public void triggerProbe(Host host) {
+ // Not implemented since there is no need to check for hosts on network
+ }
+
+ public void setHostProviderService(HostProviderService service) {
+ this.hostProviderService = service;
+ }
+
+ /*
+ * Return the ProviderId of "this"
+ */
+ public ProviderId id() {
+ return providerId;
+ }
+
+ /**
+ * Creates and adds new host based on given data and returns its host ID.
+ *
+ * @param node JsonNode containing host information
+ * @return host ID of new host created
+ */
+ private HostId parseHost(JsonNode node) {
+ MacAddress mac = MacAddress.valueOf(node.get("mac").asText());
+ VlanId vlanId = VlanId.vlanId((short) node.get("vlan").asInt(VlanId.UNTAGGED));
+ JsonNode locationNode = node.get("location");
+ String deviceAndPort = locationNode.get("elementId").asText() + "/" +
+ locationNode.get("port").asText();
+ HostLocation hostLocation = new HostLocation(ConnectPoint.deviceConnectPoint(deviceAndPort), 0);
+
+ Iterator<JsonNode> ipStrings = node.get("ipAddresses").elements();
+ Set<IpAddress> ips = new HashSet<>();
+ while (ipStrings.hasNext()) {
+ ips.add(IpAddress.valueOf(ipStrings.next().asText()));
+ }
+ SparseAnnotations annotations = annotations(node);
+ // Update host inventory
+
+ HostId hostId = HostId.hostId(mac, vlanId);
+ DefaultHostDescription desc = new DefaultHostDescription(mac, vlanId, hostLocation, ips, annotations);
+ hostProviderService.hostDetected(hostId, desc);
+ return hostId;
+ }
+
+ /**
+ * Produces annotations from specified JsonNode. Copied from the ConfigProvider
+ * class for use in the POST method.
+ *
+ * @param node node to be annotated
+ * @return SparseAnnotations object with information about node
+ */
+ private SparseAnnotations annotations(JsonNode node) {
+ if (node == null) {
+ return DefaultAnnotations.EMPTY;
+ }
+
+ DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+ Iterator<String> it = node.fieldNames();
+ while (it.hasNext()) {
+ String k = it.next();
+ builder.set(k, node.get(k).asText());
+ }
+ return builder.build();
+ }
+
+ }
+}
+
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java
new file mode 100644
index 00000000..a4dd9380
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/IntentsWebResource.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.intent.HostToHostIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.IntentEvent;
+import org.onosproject.net.intent.IntentListener;
+import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
+import org.onosproject.net.intent.Key;
+import org.onosproject.net.intent.PointToPointIntent;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+import static org.onosproject.net.intent.IntentState.FAILED;
+import static org.onosproject.net.intent.IntentState.WITHDRAWN;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Query, submit and withdraw network intents.
+ */
+@Path("intents")
+public class IntentsWebResource extends AbstractWebResource {
+ @Context
+ UriInfo uriInfo;
+
+ private static final Logger log = getLogger(IntentsWebResource.class);
+ private static final int WITHDRAW_EVENT_TIMEOUT_SECONDS = 5;
+
+ public static final String INTENT_NOT_FOUND = "Intent is not found";
+
+ /**
+ * Get all intents.
+ * Returns array containing all the intents in the system.
+ *
+ * @return array of all the intents in the system
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getIntents() {
+ final Iterable<Intent> intents = get(IntentService.class).getIntents();
+ final ObjectNode root = encodeArray(Intent.class, "intents", intents);
+ return ok(root).build();
+ }
+
+ /**
+ * Get intent by application and key.
+ * Returns details of the specified intent.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ * @return intent data
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{appId}/{key}")
+ public Response getIntentById(@PathParam("appId") String appId,
+ @PathParam("key") String key) {
+ final ApplicationId app = get(CoreService.class).getAppId(appId);
+
+ Intent intent = get(IntentService.class).getIntent(Key.of(key, app));
+ if (intent == null) {
+ long numericalKey = Long.decode(key);
+ intent = get(IntentService.class).getIntent(Key.of(numericalKey, app));
+ }
+ nullIsNotFound(intent, INTENT_NOT_FOUND);
+
+ final ObjectNode root;
+ if (intent instanceof HostToHostIntent) {
+ root = codec(HostToHostIntent.class).encode((HostToHostIntent) intent, this);
+ } else if (intent instanceof PointToPointIntent) {
+ root = codec(PointToPointIntent.class).encode((PointToPointIntent) intent, this);
+ } else {
+ root = codec(Intent.class).encode(intent, this);
+ }
+ return ok(root).build();
+ }
+
+ class DeleteListener implements IntentListener {
+ final Key key;
+ final CountDownLatch latch;
+
+ DeleteListener(Key key, CountDownLatch latch) {
+ this.key = key;
+ this.latch = latch;
+ }
+
+ @Override
+ public void event(IntentEvent event) {
+ if (Objects.equals(event.subject().key(), key) &&
+ (event.type() == IntentEvent.Type.WITHDRAWN ||
+ event.type() == IntentEvent.Type.FAILED)) {
+ latch.countDown();
+ }
+ }
+ }
+
+ /**
+ * Submit a new intent.
+ * Creates and submits intent from the JSON request.
+ *
+ * @param stream input JSON
+ * @return status of the request - CREATED if the JSON is correct,
+ * BAD_REQUEST if the JSON is invalid
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createIntent(InputStream stream) {
+ try {
+ IntentService service = get(IntentService.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(stream);
+ Intent intent = codec(Intent.class).decode(root, this);
+ service.submit(intent);
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("intents")
+ .path(intent.appId().name())
+ .path(Long.toString(intent.id().fingerprint()));
+ return Response
+ .created(locationBuilder.build())
+ .build();
+ } catch (IOException ioe) {
+ throw new IllegalArgumentException(ioe);
+ }
+ }
+
+ /**
+ * Withdraw intent.
+ * Withdraws the specified intent from the system.
+ *
+ * @param appId application identifier
+ * @param key intent key
+ */
+ @DELETE
+ @Path("{appId}/{key}")
+ public void deleteIntentById(@PathParam("appId") String appId,
+ @PathParam("key") String key) {
+ final ApplicationId app = get(CoreService.class).getAppId(appId);
+
+ Intent intent = get(IntentService.class).getIntent(Key.of(key, app));
+ IntentService service = get(IntentService.class);
+
+ if (intent == null) {
+ intent = service
+ .getIntent(Key.of(Long.decode(key), app));
+ }
+ if (intent == null) {
+ // No such intent. REST standards recommend a positive status code
+ // in this case.
+ return;
+ }
+
+
+ Key k = intent.key();
+
+ // set up latch and listener to track uninstall progress
+ CountDownLatch latch = new CountDownLatch(1);
+
+ IntentListener listener = new DeleteListener(k, latch);
+ service.addListener(listener);
+
+ try {
+ // request the withdraw
+ service.withdraw(intent);
+
+ try {
+ latch.await(WITHDRAW_EVENT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.info("REST Delete operation timed out waiting for intent {}", k);
+ }
+ // double check the state
+ IntentState state = service.getIntentState(k);
+ if (state == WITHDRAWN || state == FAILED) {
+ service.purge(intent);
+ }
+
+ } finally {
+ // clean up the listener
+ service.removeListener(listener);
+ }
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/JsonBodyWriter.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/JsonBodyWriter.java
new file mode 100644
index 00000000..31f20691
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/JsonBodyWriter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014-2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.ext.MessageBodyWriter;
+import javax.ws.rs.ext.Provider;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+/**
+ * JAX-RS Response message body writer.
+ */
+@Provider
+@Produces("application/json")
+public class JsonBodyWriter implements MessageBodyWriter<ObjectNode> {
+
+ private ObjectMapper mapper = new ObjectMapper();
+
+ @Override
+ public boolean isWriteable(Class<?> type, Type genericType,
+ Annotation[] annotations, MediaType mediaType) {
+ return type == ObjectNode.class;
+ }
+
+ @Override
+ public long getSize(ObjectNode node, Class<?> type, Type genericType,
+ Annotation[] annotations, MediaType mediaType) {
+ return -1;
+ }
+
+ @Override
+ public void writeTo(ObjectNode node, Class<?> type, Type genericType,
+ Annotation[] annotations, MediaType mediaType,
+ MultivaluedMap<String, Object> httpHeaders,
+ OutputStream entityStream) throws IOException {
+ mapper.writer().writeValue(entityStream, node);
+ entityStream.flush();
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/LinksWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/LinksWebResource.java
new file mode 100644
index 00000000..c6270199
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/LinksWebResource.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 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.rest.resources;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Manage inventory of infrastructure links.
+ */
+@Path("links")
+public class LinksWebResource extends AbstractWebResource {
+
+ enum Direction {
+ ALL,
+ INGRESS,
+ EGRESS
+ }
+
+ /**
+ * Get infrastructure links.
+ * Returns array of all links, or links for the specified device or port.
+ *
+ * @param deviceId (optional) device identifier
+ * @param port (optional) port number
+ * @param direction (optional) direction qualifier
+ * @return 200 OK
+ */
+ @GET
+ public Response getLinks(@QueryParam("device") String deviceId,
+ @QueryParam("port") String port,
+ @QueryParam("direction") String direction) {
+ LinkService service = get(LinkService.class);
+ Iterable<Link> links;
+
+ if (deviceId != null && port != null) {
+ links = getConnectPointLinks(new ConnectPoint(deviceId(deviceId),
+ portNumber(port)),
+ direction, service);
+ } else if (deviceId != null) {
+ links = getDeviceLinks(deviceId(deviceId), direction, service);
+ } else {
+ links = service.getLinks();
+ }
+ return ok(encodeArray(Link.class, "links", links)).build();
+ }
+
+ private Iterable<Link> getConnectPointLinks(ConnectPoint point,
+ String direction,
+ LinkService service) {
+ Direction dir = direction != null ?
+ Direction.valueOf(direction.toUpperCase()) : Direction.ALL;
+ switch (dir) {
+ case INGRESS:
+ return service.getIngressLinks(point);
+ case EGRESS:
+ return service.getEgressLinks(point);
+ default:
+ return service.getLinks(point);
+ }
+ }
+
+ private Iterable<Link> getDeviceLinks(DeviceId deviceId,
+ String direction,
+ LinkService service) {
+ Direction dir = direction != null ?
+ Direction.valueOf(direction.toUpperCase()) : Direction.ALL;
+ switch (dir) {
+ case INGRESS:
+ return service.getDeviceIngressLinks(deviceId);
+ case EGRESS:
+ return service.getDeviceEgressLinks(deviceId);
+ default:
+ return service.getDeviceLinks(deviceId);
+ }
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java
new file mode 100644
index 00000000..9e2b6273
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/NetworkConfigWebResource.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.SubjectFactory;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Manage network configurations.
+ */
+@Path("network/configuration")
+public class NetworkConfigWebResource extends AbstractWebResource {
+
+ /**
+ * Get entire network configuration base.
+ *
+ * @return network configuration JSON
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response download() {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = mapper().createObjectNode();
+ service.getSubjectClasses().forEach(sc ->
+ produceJson(service, newObject(root, service.getSubjectFactory(sc).subjectKey()), sc));
+ return ok(root).build();
+ }
+
+ /**
+ * Get all network configuration for a subject class.
+ *
+ * @param subjectKey subject class key
+ * @return network configuration JSON
+ */
+ @GET
+ @Path("{subjectKey}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response download(@PathParam("subjectKey") String subjectKey) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = mapper().createObjectNode();
+ produceJson(service, root, service.getSubjectFactory(subjectKey).subjectClass());
+ return ok(root).build();
+ }
+
+ /**
+ * Get all network configuration for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @return network configuration JSON
+ */
+ @GET
+ @Path("{subjectKey}/{subject}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response download(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = mapper().createObjectNode();
+ produceSubjectJson(service, root,
+ service.getSubjectFactory(subjectKey).createSubject(subject));
+ return ok(root).build();
+ }
+
+ /**
+ * Get specific network configuration for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @param configKey configuration class key
+ * @return network configuration JSON
+ */
+ @GET
+ @Path("{subjectKey}/{subject}/{configKey}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response download(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject,
+ @PathParam("configKey") String configKey) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ return ok(service.getConfig(service.getSubjectFactory(subjectKey).createSubject(subject),
+ service.getConfigClass(subjectKey, configKey)).node()).build();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void produceJson(NetworkConfigService service, ObjectNode node,
+ Class subjectClass) {
+ service.getSubjects(subjectClass).forEach(s ->
+ produceSubjectJson(service, newObject(node, s.toString()), s));
+ }
+
+ private void produceSubjectJson(NetworkConfigService service, ObjectNode node,
+ Object subject) {
+ service.getConfigs(subject).forEach(c -> node.set(c.key(), c.node()));
+ }
+
+
+ /**
+ * Upload bulk network configuration.
+ *
+ * @param request network configuration JSON rooted at the top node
+ * @throws IOException if unable to parse the request
+ * @return empty response
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response upload(InputStream request) throws IOException {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(request);
+ root.fieldNames()
+ .forEachRemaining(sk -> consumeJson(service, (ObjectNode) root.path(sk),
+ service.getSubjectFactory(sk)));
+ return Response.ok().build();
+ }
+
+ /**
+ * Upload multiple network configurations for a subject class.
+ *
+ * @param subjectKey subject class key
+ * @param request network configuration JSON rooted at the top node
+ * @return empty response
+ * @throws IOException if unable to parse the request
+ */
+ @POST
+ @Path("{subjectKey}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response upload(@PathParam("subjectKey") String subjectKey,
+ InputStream request) throws IOException {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(request);
+ consumeJson(service, root, service.getSubjectFactory(subjectKey));
+ return Response.ok().build();
+ }
+
+ /**
+ * Upload mutliple network configurations for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @param request network configuration JSON rooted at the top node
+ * @return empty response
+ * @throws IOException if unable to parse the request
+ */
+ @POST
+ @Path("{subjectKey}/{subject}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response upload(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject,
+ InputStream request) throws IOException {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(request);
+ consumeSubjectJson(service, root,
+ service.getSubjectFactory(subjectKey).createSubject(subject),
+ subjectKey);
+ return Response.ok().build();
+ }
+
+ /**
+ * Upload specific network configuration for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @param configKey configuration class key
+ * @param request network configuration JSON rooted at the top node
+ * @return empty response
+ * @throws IOException if unable to parse the request
+ */
+ @POST
+ @Path("{subjectKey}/{subject}/{configKey}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @SuppressWarnings("unchecked")
+ public Response upload(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject,
+ @PathParam("configKey") String configKey,
+ InputStream request) throws IOException {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ ObjectNode root = (ObjectNode) mapper().readTree(request);
+ service.applyConfig(service.getSubjectFactory(subjectKey).createSubject(subject),
+ service.getConfigClass(subjectKey, configKey), root);
+ return Response.ok().build();
+ }
+
+ private void consumeJson(NetworkConfigService service, ObjectNode classNode,
+ SubjectFactory subjectFactory) {
+ classNode.fieldNames().forEachRemaining(s ->
+ consumeSubjectJson(service, (ObjectNode) classNode.path(s),
+ subjectFactory.createSubject(s),
+ subjectFactory.subjectKey()));
+ }
+
+ private void consumeSubjectJson(NetworkConfigService service,
+ ObjectNode subjectNode, Object subject,
+ String subjectKey) {
+ subjectNode.fieldNames().forEachRemaining(c ->
+ service.applyConfig(subject, service.getConfigClass(subjectKey, c),
+ (ObjectNode) subjectNode.path(c)));
+ }
+
+
+ /**
+ * Clear entire network configuration base.
+ *
+ * @return empty response
+ */
+ @DELETE
+ @SuppressWarnings("unchecked")
+ public Response delete() {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ service.getSubjectClasses()
+ .forEach(subjectClass -> service.getSubjects(subjectClass)
+ .forEach(subject -> service.getConfigs(subject)
+ .forEach(config -> service
+ .removeConfig(subject, config.getClass()))));
+ return Response.ok().build();
+ }
+
+ /**
+ * Clear all network configurations for a subject class.
+ *
+ * @param subjectKey subject class key
+ * @return empty response
+ */
+ @DELETE
+ @Path("{subjectKey}")
+ @SuppressWarnings("unchecked")
+ public Response delete(@PathParam("subjectKey") String subjectKey) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ service.getSubjects(service.getSubjectFactory(subjectKey).getClass())
+ .forEach(subject -> service.getConfigs(subject)
+ .forEach(config -> service
+ .removeConfig(subject, config.getClass())));
+ return Response.ok().build();
+ }
+
+ /**
+ * Clear all network configurations for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @return empty response
+ */
+ @DELETE
+ @Path("{subjectKey}/{subject}")
+ @SuppressWarnings("unchecked")
+ public Response delete(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ Object s = service.getSubjectFactory(subjectKey).createSubject(subject);
+ service.getConfigs(s).forEach(c -> service.removeConfig(s, c.getClass()));
+ return Response.ok().build();
+ }
+
+ /**
+ * Clear specific network configuration for a subject.
+ *
+ * @param subjectKey subject class key
+ * @param subject subject key
+ * @param configKey configuration class key
+ * @return empty response
+ */
+ @DELETE
+ @Path("{subjectKey}/{subject}/{configKey}")
+ @SuppressWarnings("unchecked")
+ public Response delete(@PathParam("subjectKey") String subjectKey,
+ @PathParam("subject") String subject,
+ @PathParam("configKey") String configKey) {
+ NetworkConfigService service = get(NetworkConfigService.class);
+ service.removeConfig(service.getSubjectFactory(subjectKey).createSubject(subject),
+ service.getConfigClass(subjectKey, configKey));
+ return Response.ok().build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/PathsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/PathsWebResource.java
new file mode 100644
index 00000000..baa1b1e6
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/PathsWebResource.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.topology.PathService;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.rest.AbstractWebResource;
+
+/**
+ * Compute paths in the network graph.
+ */
+@Path("paths")
+public class PathsWebResource extends AbstractWebResource {
+
+ /**
+ * Determines if the id appears to be the id of a host.
+ * Host id format is 00:00:00:00:00:01/-1
+ *
+ * @param id id string
+ * @return HostId if the id is valid, null otherwise
+ */
+ private HostId isHostId(String id) {
+ return id.matches("..:..:..:..:..:../.*") ? HostId.hostId(id) : null;
+ }
+
+ /**
+ * Get all shortest paths between any two hosts or devices.
+ * Returns array of all shortest paths between any two elements.
+ *
+ * @param src source identifier
+ * @param dst destination identifier
+ * @return path data
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{src}/{dst}")
+ public Response getPath(@PathParam("src") String src,
+ @PathParam("dst") String dst) {
+ PathService pathService = get(PathService.class);
+
+ ElementId srcElement = isHostId(src);
+ ElementId dstElement = isHostId(dst);
+
+ if (srcElement == null) {
+ // Doesn't look like a host, assume it is a device
+ srcElement = DeviceId.deviceId(src);
+ }
+
+ if (dstElement == null) {
+ // Doesn't look like a host, assume it is a device
+ dstElement = DeviceId.deviceId(dst);
+ }
+
+ Set<org.onosproject.net.Path> paths = pathService.getPaths(srcElement, dstElement);
+ ObjectNode root = encodeArray(org.onosproject.net.Path.class, "paths", paths);
+ return ok(root).build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java
new file mode 100644
index 00000000..2ffa2295
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/StatisticsWebResource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.StreamSupport;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.onosproject.codec.JsonCodec;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Link;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.statistic.Load;
+import org.onosproject.net.statistic.StatisticService;
+import org.onosproject.rest.AbstractWebResource;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.PortNumber.portNumber;
+
+/**
+ * Query flow statistics.
+ */
+@Path("statistics")
+public class StatisticsWebResource extends AbstractWebResource {
+ @Context
+ UriInfo uriInfo;
+
+ /**
+ * Get load statistics for all links or for a specific link.
+ *
+ * @param deviceId (optional) device ID for a specific link
+ * @param port (optional) port number for a specified link
+ * @return JSON encoded array lof Load objects
+ */
+ @GET
+ @Path("flows/link")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getLoads(@QueryParam("device") String deviceId,
+ @QueryParam("port") String port) {
+ Iterable<Link> links;
+
+ if (deviceId == null || port == null) {
+ links = get(LinkService.class).getLinks();
+ } else {
+ ConnectPoint connectPoint = new ConnectPoint(deviceId(deviceId),
+ portNumber(port));
+ links = get(LinkService.class).getLinks(connectPoint);
+ }
+ ObjectNode result = mapper().createObjectNode();
+ ArrayNode loads = mapper().createArrayNode();
+ JsonCodec<Load> loadCodec = codec(Load.class);
+ StatisticService statsService = getService(StatisticService.class);
+
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(
+ links.iterator(), Spliterator.ORDERED), false)
+ .forEach(link -> {
+ ObjectNode loadNode = loadCodec.encode(statsService.load(link), this);
+
+ UriBuilder locationBuilder = uriInfo.getBaseUriBuilder()
+ .path("links")
+ .queryParam("device", link.src().deviceId().toString())
+ .queryParam("port", link.src().port().toString());
+ loadNode.put("link", locationBuilder.build().toString());
+ loads.add(loadNode);
+ });
+ result.set("loads", loads);
+ return ok(result).build();
+ }
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/TopologyWebResource.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/TopologyWebResource.java
new file mode 100644
index 00000000..f6ae8253
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/TopologyWebResource.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2015 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.rest.resources;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Lists;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.topology.ClusterId;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyCluster;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.rest.AbstractWebResource;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+import static org.onlab.util.Tools.nullIsNotFound;
+
+/**
+ * Query network topology graph and its components.
+ */
+@Path("topology")
+public class TopologyWebResource extends AbstractWebResource {
+
+ public static final String CLUSTER_NOT_FOUND = "Cluster is not found";
+
+ /**
+ * Get overview of current topology.
+ *
+ * @return topology overview
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response getTopology() {
+ Topology topology = get(TopologyService.class).currentTopology();
+ ObjectNode root = codec(Topology.class).encode(topology, this);
+ return ok(root).build();
+ }
+
+ /**
+ * Get overview of topology SCCs.
+ *
+ * @return topology clusters overview
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("clusters")
+ public Response getClusters() {
+ TopologyService service = get(TopologyService.class);
+ Topology topology = service.currentTopology();
+ Iterable<TopologyCluster> clusters = service.getClusters(topology);
+ ObjectNode root = encodeArray(TopologyCluster.class, "clusters", clusters);
+ return ok(root).build();
+ }
+
+ /**
+ * Get details of a specific SCC.
+ *
+ * @param clusterId id of the cluster to query
+ * @return topology cluster details
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("clusters/{id}")
+ public Response getCluster(@PathParam("id") int clusterId) {
+ Topology topology = get(TopologyService.class).currentTopology();
+ TopologyCluster cluster = getTopologyCluster(clusterId, topology);
+ ObjectNode root = codec(TopologyCluster.class).encode(cluster, this);
+ return ok(root).build();
+ }
+
+ private TopologyCluster getTopologyCluster(int clusterId, Topology topology) {
+ return nullIsNotFound(
+ get(TopologyService.class)
+ .getCluster(topology, ClusterId.clusterId(clusterId)),
+ CLUSTER_NOT_FOUND);
+ }
+
+ /**
+ * Get devices in a specific SCC.
+ *
+ * @param clusterId id of the cluster to query
+ * @return topology cluster devices
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("clusters/{id}/devices")
+ public Response getClusterDevices(@PathParam("id") int clusterId) {
+ TopologyService service = get(TopologyService.class);
+ Topology topology = service.currentTopology();
+ TopologyCluster cluster = getTopologyCluster(clusterId, topology);
+
+ List<DeviceId> deviceIds =
+ Lists.newArrayList(service.getClusterDevices(topology, cluster));
+
+ ObjectNode root = mapper().createObjectNode();
+ ArrayNode devicesNode = root.putArray("devices");
+ deviceIds.forEach(id -> devicesNode.add(id.toString()));
+ return ok(root).build();
+ }
+
+ /**
+ * Get links in specific SCC.
+ *
+ * @param clusterId id of the cluster to query
+ * @return topology cluster links
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("clusters/{id}/links")
+ public Response getClusterLinks(@PathParam("id") int clusterId) {
+ Topology topology = get(TopologyService.class).currentTopology();
+ TopologyCluster cluster = getTopologyCluster(clusterId, topology);
+
+ List<Link> links =
+ Lists.newArrayList(get(TopologyService.class)
+ .getClusterLinks(topology, cluster));
+
+ return ok(encodeArray(Link.class, "links", links)).build();
+ }
+
+ /**
+ * Extracts the port number portion of the ConnectPoint.
+ *
+ * @param deviceString string representing the device/port
+ * @return port number as a string, empty string if the port is not found
+ */
+ private static String getPortNumber(String deviceString) {
+ int separator = deviceString.lastIndexOf(':');
+ if (separator <= 0) {
+ return "";
+ }
+ return deviceString.substring(separator + 1, deviceString.length());
+ }
+
+ /**
+ * Extracts the device ID portion of the ConnectPoint.
+ *
+ * @param deviceString string representing the device/port
+ * @return device ID string
+ */
+ private static String getDeviceId(String deviceString) {
+ int separator = deviceString.lastIndexOf(':');
+ if (separator <= 0) {
+ return "";
+ }
+ return deviceString.substring(0, separator);
+ }
+
+ /**
+ * Test if a connect point is in broadcast set.
+ *
+ * @param connectPointString deviceid:portnumber
+ * @return JSON representation of true if the connect point is broadcast,
+ * false otherwise
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("broadcast/{connectPoint}")
+ public Response getConnectPointBroadcast(@PathParam("connectPoint") String connectPointString) {
+ Topology topology = get(TopologyService.class).currentTopology();
+
+ DeviceId deviceId = DeviceId.deviceId(getDeviceId(connectPointString));
+ PortNumber portNumber = PortNumber.portNumber(getPortNumber(connectPointString));
+ ConnectPoint connectPoint = new ConnectPoint(deviceId, portNumber);
+ boolean isBroadcast = get(TopologyService.class).isBroadcastPoint(topology, connectPoint);
+
+ return ok(mapper()
+ .createObjectNode()
+ .put("broadcast", isBroadcast))
+ .build();
+ }
+
+ /**
+ * Test if a connect point is infrastructure or edge.
+ *
+ * @param connectPointString deviceid:portnumber
+ * @return JSON representation of true if the connect point is broadcast,
+ * false otherwise
+ */
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("infrastructure/{connectPoint}")
+ public Response getConnectPointInfrastructure(@PathParam("connectPoint") String connectPointString) {
+ Topology topology = get(TopologyService.class).currentTopology();
+
+ DeviceId deviceId = DeviceId.deviceId(getDeviceId(connectPointString));
+ PortNumber portNumber = PortNumber.portNumber(getPortNumber(connectPointString));
+ ConnectPoint connectPoint = new ConnectPoint(deviceId, portNumber);
+ boolean isInfrastructure = get(TopologyService.class).isInfrastructure(topology, connectPoint);
+
+ return ok(mapper()
+ .createObjectNode()
+ .put("infrastructure", isInfrastructure))
+ .build();
+ }
+
+}
diff --git a/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/package-info.java b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/package-info.java
new file mode 100644
index 00000000..5eb305c9
--- /dev/null
+++ b/framework/src/onos/web/api/src/main/java/org/onosproject/rest/resources/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/**
+ * Set of resources implementing the ONOS REST API.
+ */
+package org.onosproject.rest.resources;