diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/api/src/main/java | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/api/src/main/java')
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; |